跳到內容
Crystal CodeDocs
繁繁體中文

Plugin contract

本頁內容尚未翻譯。

Reference CrystalCode.Plugins. Reference CrystalCode.Tools only when a tool needs ToolHostContext. Do not reference CrystalCode.Engine, CrystalCode, CrystalCode.Display, or CrystalCode.Providers.

Contribute returns tools, client factories, classifiers, commands, hooks, raw hooks, prompt placeholders, and status fields. A null entry is omitted with a note. One bad plugin does not stop the others.

Attach runs after Contribute, once plugins, external tools, and skills are loaded, and again when those catalogs reload. It runs before the session-start hook. The default method does nothing. Reading the snapshot inside Contribute is too early: the host has not finished the other catalogs.

A tool implements IPluginTool: a model-facing Name, Catalogs, an ITool, and an optional IMultimodalTool with the same name. Catalogs is PluginToolCatalogs.Plan, Work, or PlanAndWork. A tool must name at least one catalog. The same choice applies to its multimodal tool.

Names match ^[A-Za-z][A-Za-z0-9_-]*$ (1–64 characters) and must not be a built-in name. The definition name must match Name. Later duplicates are omitted with a note.

Catalog order is built-in plugin tools, disk plugin tools, external tools, then skill when enabled. A disk tool name is reserved against external tools.

The plugin constructs the tool. Host facts arrive per call when the tool implements IHostTool or IHostMultimodalTool. The tool does not receive the question prompt.

IPluginClientFactory.CanCreate returns true only for protocols that factory owns. A factory that returns true for deepseek, openai, responses, anthropic, gemini, or ollama is omitted. Built-in adapters stay first. A custom protocol is a token of 1–64 characters: a letter, then letters, digits, _, or -. Put that token in providers.json as protocol.

PluginClientRequest carries the endpoint, model sampling, and API key for that call. Do not log the key. It also carries declared audioInput and videoInput, plus ServiceTier and Speed when those slots are set. A factory that cannot write a non-null slot must throw NotSupportedException from Create or CreateMultimodal. The host surfaces that when it builds the client, including the first connection and a later /tier or /speed. Those flags do not make Create or CreateMultimodal accept audio or video. CreateMultimodal is used only when the selected model accepts image input and no built-in factory owns the protocol.

IPluginClassifier runs only after the built-in switch misses. It assigns risk and authority for an unknown tool. It does not reclassify read, bash, or the other built-in names.

IPluginCommand adds a /name verb. Built-in verbs and their aliases win. Names use the same pattern as tool names. IPluginOutput.Write and Fail become session notes.

IPluginPlaceholder adds a {{name}} token. The name is 1–64 characters: a letter, then letters, digits, or _. Matching is case-insensitive. The host rejects a name it already owns. Two plugins cannot share a name; the first in load order wins and the later one is omitted with a note.

The host calls Resolve each time it binds a template, including attachments, Review, compaction, and image description. The context carries the mode, workspace, session id, approval mode, provider, model, the current catalog snapshot, and the live session and review model. Strings the host does not have yet are empty. The returned text is inserted as-is. Tokens inside that text are not expanded again. A throw, or a missing return, leaves the original token and records an English note.

Before the first Attach, the snapshot on that context is empty.

IPluginStatusField names one custom status-line field. Add it with PluginContribution.WithStatusFields. The original constructor stays unchanged. The name starts with a letter, then letters, digits, or single hyphens, at most 32 characters. Admission is case-insensitive and the host stores the lowercase form. A built-in status field, on, off, reset, an illegal token, or a duplicate is omitted with a note. The first loaded plugin wins.

The host does not place the field on the adaptive status bar, the progress row, /status, or /stats. The operator adds the lowercase name to statusLine or /statusline. A saved name is kept when the plugin is not loaded; the custom line omits it and /statusline marks it unavailable.

After data directories, the host calls AttachStatus with IPluginStatusUpdates when at least one of this plugin’s fields was accepted. That call happens once for the loaded instance. Reloading tools or changing tool approval does not call it again. A later plugin reload retires the previous sink first.

Set replaces that field’s text. Clear, or blank text, removes it. The plugin pushes. The host does not poll and does not run a status timer. A plugin may call Set from any thread it already has, including a timer it owns. The host keeps one line: control characters and escapes are dropped, newlines and tabs become spaces, and text longer than 64 characters is cut. The description shown by /statusline is at most 80 characters. The same text is not published again. A name this plugin did not register is ignored, and that name is noted once. A sink from an older catalog ignores later calls. If AttachStatus throws, that sink is closed and any text it already published is removed. The plugin does not choose color, markup, or an extra row.

IPluginEnvironment is read-only. It does not enable, disable, invoke, or reorder anything, and it does not declare a load dependency.

List Contains
Plugins Every discovered plugin directory, including this plugin. Directory, source (home or project), display name, enabled, effective, loaded, and skip reason. No assembly path, type name, or manifest path.
ToolSets Every discovered external tool set, with the same directory fields.
ExternalTools Tools that loaded: name, set, source, and whether the tool is in Plan, Work, or both.
Skills Skills the skill tool can load right now: name and description. One entry per name. Empty when Skills is off. No file body and no path.

enabled is null when the manifest could not say. Display name is empty until that plugin loads. Built-in tools are not listed.

AttachSession runs with Attach, after Contribute, and again when that plugin instance is loaded again. The object stays live. /model, /approval model, /approval thinking, /thinking, /tier, and /speed show up on the next read. The host does not call AttachSession again for those changes.

Session carries the provider, protocol, model name, context window, maximum tokens, temperature, top-p, image input, declared audio input, declared video input, the current thinking gear, and the selected service tier and speed. Audio and video flags copy the model entry. They do not make a turn, a tool result, or CreateMultimodal accept audio or video. default and off are host sentinels for thinking. Other thinking values are effort tokens the model lists. Tokens are stored in lowercase and forwarded unchanged. max and maximum are different tokens. ServiceTier and Speed are null when that slot is unset. A listed default is a service-tier token, not the thinking sentinel. The plugin sees the token, not the JSON field name.

Review.Independent is true only when review uses its own model. That model’s thinking gear is its stored thinkingEffort, default when unset. Its service tier and speed are the tokens saved for that model. While the switch is off, review uses the session model, Model is null, and a saved provider or model name is omitted. The facts include no API key, endpoint, organization, or project.

PluginPlaceholderContext.Models is the same live view. It is empty until the session publishes it.

A model call spends the operator’s credentials. It does not enter the transcript, the approval path, or the turn budget. The entry type receives clients only when it implements IPluginModelClient. The host names that plugin when it loads, for example Plugin 'Acme' can call the session and review models. Built-in plugins do not receive these clients.

AttachClients runs after AttachSession. The clients are new instances on the same provider and model. They are kept apart from the instances inside a turn or a review.

Member Meaning
Session Text client for the session model.
Images Image client for the session model, or null when that model does not accept images.
IndependentReview True only when review uses its own model. Reading it does not create a client.
Review Text client for the review model while IndependentReview is true. Null while review uses the session model.

When IndependentReview is true and the review client cannot be created, reading Review fails. The host leaves the session client in place.

The first read creates a client. Later reads return that instance until the host drops it. The host drops the session and image clients when it rebuilds the session client, and drops the review client when it rebuilds the review client or the switch turns off. Read the property again after that.

IPluginClientFactory remains the other direction. It builds a client for a protocol the built-in adapters do not own.

A plugin that keeps its own runtime state implements CrystalCode.Plugins.Data.IPluginDataDirectory. The host calls AttachDataDirectories(globalDataDirectory, projectDataDirectory) after Attach, and again when the plugin instance is loaded again. The interface is opt-in, like IPluginModelClient.

The global directory is {home}/data/plugins/<directory> and is shared across workspaces. The project directory is <workspace>/.crystal/data/plugins/<directory> and belongs to the current workspace. The host creates both before that call; a directory that cannot be created is not reported, so create it when it is missing. They are separate from the plugins/ install directory and are not a secret store: do not write credentials there, and the project directory is inside the workspace.

A plugin tool that implements IHostTool or IHostMultimodalTool also receives the same two directories on ToolHostContext.