Plugins in the architecture
本頁內容尚未翻譯。
First-party IPlugin contributes tools, chat-client factories, approval
classifiers, or slash commands through PluginContribution. A tool
contribution always has a text ITool implementation and may additionally
provide an IMultimodalTool with the same definition name; the latter may
return generic image content while reusing the host approval policy.
Built-in tools and all six wire protocol adapters register through that
table. PluginRegistry does not load assemblies from disk.
Disk plugins use CrystalCode.Plugins.IPlugin and are loaded by
PluginCatalog from plugins/. One non-collectible load context serves
each plugin. Shared contracts (Crystal, Crystal.Tools,
CrystalCode.Tools, and CrystalCode.Plugins) come from the host.
CrystalCode, CrystalCode.Engine, CrystalCode.Display, and
CrystalCode.Providers are refused. A disk plugin may contribute tools,
and a tool that implements IHostTool or IHostMultimodalTool receives a
ToolHostContext captured when that call starts. It may also contribute
a protocol factory for a protocol the built-in adapters do not own,
classifiers for unknown tools, slash commands that do not reuse a
built-in verb, hooks, raw hooks, and prompt placeholders. After plugins,
external tools, and skills have loaded, the host calls IPlugin.Attach
with a read-only snapshot and calls it again when those catalogs reload.
The snapshot lists discovered plugin directories and external tool sets
(directory, source, enabled, effective, loaded, and skip reason), loaded
external tools (name, set, source, and Plan or Work), and the skills the
skill tool can load (name and description). It includes the calling
plugin. It does not include assembly paths, manifest paths, built-in
tools, or skill file bodies. Contribute runs before that snapshot
exists. The snapshot does not enable, disable, invoke, or reorder other
plugins or tools. The host then calls IPlugin.AttachSession with a live
view of the session model and the review model. Each read returns the
current provider, protocol, model name, context window, maximum tokens,
temperature, top-p, image input, declared audio input, declared video
input, thinking gear, and the selected service tier and speed when set.
The plugin model’s ContextWindow describes the
configured maximum model capability, not the selected local session budget.
/context does not change that capability value; chrome and usage events
carry the selected budget used by compaction and /status.
Audio and video flags copy the model entry.
They do not make a turn, a tool result, or a plugin multimodal client
accept audio or video. Review.Independent
is true only when review uses its own model. While it is false, review
uses the session model and a stored selection is omitted. The view has no
API key, endpoint, organization, or project. A plugin that implements
IPluginModelClient also receives AttachClients. The host names that
plugin when it loads. Those clients are new instances on the same provider
and model, kept apart from the instances inside a turn or a review. A plugin
that implements IPluginDataDirectory receives its global and project
runtime-data directories through AttachDataDirectories.
A plugin that admitted at least one status field then receives
AttachStatus. The sink accepts only that plugin’s names. A later catalog
reload retires the previous sink. Tool reloads do not attach again. A failed
attach drops any text that sink already published. PluginRegistry, external tool sets, and
crystal run do not receive one.
IndependentReview matches the review flag. Review is absent while that
flag is false. When the flag is true and the review client cannot be
created, the read fails and the session client stays in place. The host
drops a side-channel client when it rebuilds the session client or the
review client, and when review stops using its own model. Contribute runs before either call.
PluginRegistry does not use them. Placeholder resolution receives the
same model view. Hooks append prompt and compaction text,
rewrite a user message before it is stored, and revise the text of one
outbound model request without writing the archive, so the model can see
text the archive does not store. Hooks also read
one returned model response
without changing the candidate the host uses, rewrite a tool call before
approval runs again, replace a tool result, and raise approval risk or
require another prompt. They do not lower risk, skip approval, or replace
Work, Plan, or Review. Session start and end follow the coding session.
/clear, /resume, and /fork end the session being left and start the
one that replaces it. A workspace change ends in the directory being left,
reloads plugins, and starts in the new directory. A resume that also
changes workspace is a single end and start.
Authority is decided per method, and a method with extra authority lives
on its own interface with its own registration list. IPluginRawHook is
that interface. A plugin registers it on PluginContribution.RawHooks,
apart from Hooks, so the host can tell which plugins hold privileged
methods without inspecting types. The host announces each such plugin
with a note and asks the operator for nothing. RebuildModelAsync runs
before ordinary model hooks, in plugin load order. It rebuilds one outbound
request in any way the host can represent: drop, reorder, add, or rewrite
items of any kind, including the live system prompt. The host refuses only
what it cannot represent: a repeated item id, empty reasoning text, or an
image the session does not hold, which includes any added image on a request
that cannot carry images (PluginModelRequest.AcceptsImages). It does not
check provider validity. When a work or plan call fails after a raw hook
changed its request, the engine writes a hedged note naming the hook. That
rebuild does not write the archive.
RewritePrompt and RewriteCompaction run after the ordinary append for
that text. Each may replace the full string with any text, including an
empty string. RewritePrompt replaces the composed system text for work,
plan, review, and compaction. Work and plan text becomes the live system
message and is archived; the files under ~/.crystal/prompts are not
written. RewriteCompaction replaces the compaction user prompt, or the
summary body that is stored. It does not choose which history is folded.
RewriteApproval runs after ordinary approval hooks and may set any risk,
any authority, any summary, and either prompt requirement. The approval
policy then uses that classification. Ordinary hooks still cannot lower
risk or replace those prompts. A raw hook is not held to those rules. A
throwing raw hook is skipped. New raw methods join this interface and never
ordinary hooks. More hook methods will be added later on these interfaces.
They are not reserved as empty types in advance. Whether plugins declare
dependencies on one another, and related loading questions, are under
consideration and are not decided.
A later module system may sit below plugins and raw hooks and reach the
host through reflection or another mechanism. It is not in this build. Its
behavior and ownership are not defined, and this build does not reserve
types for it. Catalog order is
built-in tools, disk plugin tools, external tools, then skill.
/plugins enable|disable|show and /tools enable|disable|show change one
directory. home or project selects the tree; otherwise a project
directory wins. crystal plugins and crystal tools perform the same
manifest edit outside a session. Their default output is a Spectre.Console
table. --format text prints aligned plain text. They write enabled on
that manifest and do not write config.json or start a session. The
global discovery switches stay separate.
Operator tool sets are not plugins. They are discovered from tools/ and
wrapped by ExternalCatalog. An exec child starts in the workspace root
and receives CRYSTAL_WORKSPACE, CRYSTAL_SESSION, CRYSTAL_APPROVAL,
CRYSTAL_TOOL_DATA, and CRYSTAL_PROJECT_DATA
on that process only. "output": "content" reads one JSON object from
stdout (text and optional fenced images). A dotnet set uses one non-collectible
AssemblyLoadContext for that directory only. Shared contract types
(Crystal, Crystal.Tools, CrystalCode.Tools, and already-loaded
System.* / Microsoft.*) come from the host context that already
loaded Crystal.Tools. Public ITool and IMultimodalTool
implementations are loaded directly. A type may implement either contract
or both; when it implements both, both definitions must match. A type may
also implement IHostTool or IHostMultimodalTool. The wrapper then
passes a ToolHostContext captured at the start of that call (workspace
root, session id, approval mode, and the set’s global and project data
directories). Other tools keep the original
InvokeAsync. Native multimodal tools join
the active catalog only for an image-capable model and provider. The external-tool loader does not implement CrystalCode.Plugins.IPlugin
and does not scan plugins/.
Environment variables:
CRYSTAL_HOMEoverrides the data directory.DEEPSEEK_API_KEY,OPENAI_API_KEY,GEMINI_API_KEY, andCRYSTAL_API_KEYoverridecredentials.jsonfor that process. A provider-specific variable wins overCRYSTAL_API_KEY. The persistent store iscredentials.json.