The AI assistant and AI help
The AI assistant and AI help
Section intitulée « The AI assistant and AI help »The built-in ai plugin owns the connection settings (API address, key, model,
instructions, history size — Settings › AI assistant), the conversation engine,
the sessions and the tool registry. It talks to any OpenAI-compatible endpoint
through the ai platform service (src/services/ai-openai.ts), so nothing is
hard-coded and the key stays on the machine. It contributes no panel of its
own: it is a factory of assistants.
The service delegates model access to @scfloworg/ai, the neutral package
copied from scflow-core (ADR-0010): the streamed transport, the model
profiles (which parameters a model takes: reasoning models such as o3 or
gpt-5 get no temperature and take max_completion_tokens, so there is no
temperature setting), one retry without a parameter the model refuses, errors
normalized as AiError with a kind (auth, rate-limit, context-length,
unsupported-parameter, model-not-found, network, provider) that the
chat explains to the user, and a usage report per chat
(ctx.getServices().ai.onUsage?.(listener): model, tokens, duration,
outcome). The conversation types of ctx.getServices().ai (AiMessage,
AiChatRequest, AiChunk, AiToolSpec, AiUsage) are those of the package
and come inlined in the plugin API: no plugin needs a copy of @scfloworg/ai.
A tool you declare or register keeps parameters; the ai plugin hands it to
the package as inputSchema (the MCP name). Only code that calls
ai.chat() itself passes tools with inputSchema.
Model tiers. The settings name a fast model (model) and an optional
strong one (strongModel). When the strong model is set and differs from the
fast one, the service gives the connection models: { fast, strong }, so the
tier of an AiChatRequest picks the model (docs/ai.md#model-tiers of
@scfloworg/ai); without it every tier uses model, as before. The assistant
keeps its own loop, because actions wait for the user’s approval between
turns, and follows the escalation rule of runAgent: a request starts
fast, moves to strong for the rest of the request after a turn where a
call failed (arguments that are not JSON, an unknown tool, an execution that
threw; a call the user refused does not count), and asks the same turn again
with strong when the fast request fails with AiError kind
unsupported-parameter. Each switch adds a note line to the conversation,
never sent to the model. The question box offers a switch to ask the strong
model from the start (ai/message.send with strong: true). The pure rules
live in src/plugins/ai/tiers.ts.
Any plugin can give the model tools. Declare them in your ui.json:
"tools": [ { "name": "list_commands", "description": "tool.commands", "kind": "read", "handler": "listCommands", "parameters": { "type": "object", "properties": {} } }]then bind each handler to a function, through the ai plugin’s API:
const ai = ctx.useApi<AiApi>("ai");ai?.registerManifestTools("my-plugin", manifest, { listCommands: () => ({ commands: [] }),});kind: "read" runs as soon as the model asks. kind: "action" is shown in the
conversation and only runs once the user approves it, as a UI transaction.
Tools disappear with their plugin.
Creating your own assistant
Section intitulée « Creating your own assistant »A plugin asks the ai plugin for an assistant; the shell renders the
conversation, so the plugin writes no UI:
const assistant = ai.createAssistant(ctx, { assistantId: "help", // panel id becomes "<pluginId>:help" title: "my-plugin.panel.title", // i18n key icon: "help", surface: "leftSidebar", systemPrompt: ctx.i18n.t("prompt"), toolNames: ["list_commands"], // omit for every registered tool});The panel, its toolbar (new conversation, clear, stop) and the conversations
belong to the calling plugin: disabling it removes them, while the AI settings
stay. Call assistant.dispose() on plugins/deactivated. Several assistants
can coexist — one for user help, another for the business data — each with its
own instructions and its own subset of tools.
ai-help is a small example built on this: it exposes the live commands,
panels and settings of the running app as read tools plus a run_command
action, creates its own Help panel, and adds a command
(View › Help: how do I…?) that opens it.
Declaring assistants in ui.json
Section intitulée « Declaring assistants in ui.json »The same assistant can be declared instead of coded; keys are the plugin’s own
i18n keys. actions become buttons of the assistant panel that open it and
send their prompt:
"assistants": [{ "id": "help", "title": "panel.title", "icon": "help", "surface": "leftSidebar", "order": 40, "systemPrompt": "prompt", "tools": ["list_commands", "run_command"], "actions": [{ "id": "tour", "title": "action.tour", "prompt": "action.tourPrompt", "icon": "help", "order": 5 }]}]const assistants = ai.registerManifestAssistants(ctx, manifest); // dispose() each on deactivationAny other plugin command can be put in the same panel toolbar with a normal
placement: { "commandId": "myCommand", "toolbar": "panel:help" }. Its handler
may call ai.ask("<pluginId>:help", text) to send a computed prompt.
Documentation sous licence CC BY 4.0 · © les contributeurs de SCFlow ·Retour à supplychainflow.org