Extension API reference
ExtensionContext
All handlers receivectx: ExtensionContext.
ctx.ui
UI methods for user interaction. See Custom UI for full details.ctx.hasUI
false in print mode (-p) and JSON mode. true in interactive and RPC mode. In RPC mode, dialog methods (select, confirm, input, editor) work via the extension UI sub-protocol, and fire-and-forget methods (notify, setStatus, setWidget, setTitle, setEditorText) emit requests to the client. Some TUI-specific methods are no-ops or return defaults (see RPC mode).
ctx.isPresentationOnly
true in the local presentation host when the isolated interactive engine owns execution. Keep UI registration active, but skip background execution and durable recovery in lifecycle hooks in this context to avoid starting the same work in both processes. Normal SDK, print, RPC, and engine contexts leave this flag false or unset.
ctx.cwd
Current working directory. Built-in cwd-sensitive tools (read, write, edit, search, find, ls, bash, powershell) resolve relative paths against ctx.cwd when an extension invokes them, falling back to the cwd captured when the tool was created. An extension that registers a tool and forwards its own context therefore gets paths resolved against the live session cwd rather than a stale one.
Use CONFIG_DIR_NAME instead of hardcoding .atomic (or legacy .pi) when constructing project-local config paths. Rebranded distributions can use a different config directory name.
ctx.isProjectTrusted()
Returns whether project-local trust is active for the current session context. This includes temporary trust decisions and CLI trust overrides, not just saved decisions in the global trust store. Use this before reading project-local extension configuration that should only be honored for trusted projects.ctx.sessionManager
Read-only access to session state. See Session Format for the full SessionManager API and entry types. Fortool_call, this state is synchronized through the current assistant message before handlers run. In parallel tool execution mode it is still not guaranteed to include sibling tool results from the same assistant message.
ctx.modelRegistry / ctx.model / ctx.scopedModels
Access models, auth state, and provider-aware requests. Usectx.modelRegistry.complete() for an extension model request that must use Atomic’s provider composition. It dispatches through the active ModelRuntime, retaining registered custom providers and resolved request auth: the credential-specific baseUrl, headers (including null suppression markers), and environment values.
Use ctx.modelRegistry.generateImages(model, context, options) to run an image model with request-time authentication. It resolves to the generated image blocks and usage, and reports provider errors through stopReason and errorMessage instead of rejecting.
For streaming requests, use ctx.modelRegistry.streamSimple(model, context, options) with provider-neutral options, or stream() with API-specific options. Both use configured providers and request-time authentication, including extension registrations. Iterate the returned AssistantMessageEventStream for events and await .result() for the final message. Setup failures produce error events and error results. The global compatibility streaming functions do not see extension provider registrations.
getApiKeyAndHeaders() only when an extension must inspect auth before dispatch; normal requests do not need to resolve or overlay auth themselves.
OpenRouter Chat Completions and Anthropic Messages requests send x-session-id by default when sessionId is supplied and prompt caching is enabled. Set the model’s compat.sendSessionAffinityHeaders to false to opt out, or set cacheRetention: "none" on the request to disable cache-related affinity. Explicit request headers override generated headers.
await ctx.modelRegistry.refresh(options) returns { aborted, errors }, not just completion. errors is a per-provider map, so extensions can report partial refresh failures; aborted reports cancellation. Host integrations that call ModelRuntime.setRuntimeApiKey(providerId, apiKey, options) must note that it records the runtime credential but does not refresh the catalog; call refresh({ providers: [providerId], signal }) explicitly when a fresh catalog is needed.
ctx.scopedModels is the read-only list of models scoped to the current session — the same set the /scoped-models command shows. It is resolved from the --models CLI flag and the enabledModels setting, matched against the available catalogue. It is empty when no scoping is configured, meaning every available model is usable. Each entry is { model, thinkingLevel? }, where thinkingLevel is set only when a pattern pinned it (for example anthropic/*:high). Use it to populate a model picker that mirrors the built-in one instead of enumerating the whole catalogue.
The value is resolved at access time, so it tracks session replacement. Under the isolated interactive engine it reflects the engine’s catalogue rather than a stale host snapshot.
ctx.scopedModels reports scope but cannot change it. It is a getter with no setter, typed readonly ScopedModel[], so assigning to it or pushing an entry is a compile error.
Each read also returns fresh, frozen copies of the array, every { model, thinkingLevel } entry, and each entry’s model. Even JavaScript extensions or code that casts away readonly cannot widen the scope or replace a selected model. Those mutations throw. Change scope through the commands and settings that own it.
ScopedModel for one entry, ExtensionScopedModels for the accessor’s own type. They are declared at the public extension type path (core/extensions/types.ts) and re-exported from the package root, so an extension never reaches into an internal module to describe what it just read.
ctx.signal
The current agent abort signal, orundefined when no agent turn is active.
Use this for abort-aware nested work started by extension handlers, for example:
fetch(..., { signal: ctx.signal })- model calls that accept
signal - file or process helpers that accept
AbortSignal
ctx.signal is typically defined during active turn events such as tool_call, tool_result, message_update, and turn_end.
It is usually undefined in idle or non-turn contexts such as session events, extension commands, and shortcuts fired while Atomic is idle.
ctx.sessionSignal
A read-onlyAbortSignal for the session lifetime, available even while the agent is idle. It aborts when reload or shutdown begins, before Atomic drains admitted commands and emits session_shutdown. Unlike ctx.signal, it does not end when an agent turn finishes. A replacement session generation receives a new signal.
Custom UI mounted through ctx.ui.custom is dismissed automatically when the session ends; an explicit caller signal can still dismiss it earlier. Use ctx.sessionSignal for other long-running command resources, such as browser windows, instead of waiting for session_shutdown, which cannot run until those commands settle. Cancellation should stop waiting for input, not abandon a session write already started: keep awaiting that write so teardown can drain it safely.
ctx.isIdle() / ctx.abort() / ctx.hasPendingMessages()
Control flow helpers.ctx.isIdle() reports whether the agent is streaming a response. It stays true while the session compacts, because a sendUserMessage() sent then waits for compaction to end instead of starting a turn.
ctx.isProjectTrusted()
Returns whether project-local trust is active for the current extension context. Use this before reading project-local config, loading project-local resources, or exposing actions that should only run after the user has trusted the cwd.ctx.shutdown()
Request a graceful shutdown of Atomic.- Interactive mode: Deferred until the agent becomes idle (after processing all queued steering and follow-up messages).
- RPC mode: Runs when the session is idle and all command responses have been written. An idle request does not need another client command to trigger it. Requests during startup wait for input setup; requests during a turn wait for it to settle. If an extension starts compaction outside a turn, shutdown waits for the next command or turn after compaction finishes.
- Print mode: No-op. The process exits automatically when all prompts are processed.
session_shutdown event to all extensions before exiting. Available in all contexts (event handlers, tools, commands, shortcuts).
ctx.getContextUsage()
Returns current context usage for the active model. Uses last assistant usage when available, then estimates tokens for trailing messages.ctx.compact()
Trigger Atomic’s verbatim line compactor without awaiting completion. The planner emits numbered deleted-line ranges only; Atomic validates them and reconstructs retained text mechanically. Usecompression_ratio (fraction of compactable lines to keep), client-side preserve_recent (an exact context-visible message count), and query to tune the run, compactionModel to pick an exact compaction model for this run without changing settings, and onComplete/onError for follow-up actions.
query parameter guides relevance selection inside the fixed prompt; it is not replacement prose. Extensions that need an offline replacement can return compactedText from session_before_compact.
ctx.getSystemPrompt()
Returns Atomic’s current system prompt string.- During
before_agent_start, this reflects chained system-prompt changes made so far for the current turn. - It does not include later
contextmessage mutations. - It does not include
before_provider_requestpayload rewrites. - If later-loaded extensions run after yours, they can still change what is ultimately sent.
ctx.getSkillCatalog()
Returns the current loader-owned skill catalog when the host provides one. Use it to resolve exact skill selectors, including source-qualified names such astdd@builtin, without falling back to the bare precedence winner.
pi.getCommands() already includes the same advertised /skill:name and /skill:name@source names. See Skill Commands.
ExtensionToolContext
Toolexecute callbacks receive an ExtensionToolContext, which extends ExtensionContext with tools and executeTool(name, args, options?). Event and command contexts do not provide these tool-only methods. See structured results and nested tools for permissions, cancellation, result hooks, and exclusive-call requirements.
ExtensionCommandContext
Command handlers receiveExtensionCommandContext, which extends ExtensionContext with session control methods. These are only available in commands because they can deadlock if called from event handlers.
ctx.waitForIdle()
Wait for the agent to finish streaming:ctx.newSession(options?)
Create a new session:parentSession: parent session ID to record in the new session headersetup: mutate the new session’sSessionManagerbeforewithSessionrunswithSession: run post-switch work against a fresh replacement-session context. Do not use captured oldpi/ commandctx; see Session replacement lifecycle and footguns.
ctx.fork(entryId, options?)
Fork from a specific entry, creating a new session file:position:"before"(default) forks before the selected user message, restoring that prompt into the editorposition:"at"duplicates the active path through the selected entry without restoring editor textwithSession: run post-switch work against a fresh replacement-session context. Do not use captured oldpi/ commandctx; see Session replacement lifecycle and footguns.
ctx.navigateTree(targetId, options?)
Navigate to a different point in the session tree. Navigation rejects while a response, compaction, or branch summarization is active, even withsummarize: false. Rejection leaves the active branch unchanged. Wait for the active operation to finish and retry.
summarize: Whether to generate a summary of the abandoned branchcustomInstructions: Custom instructions for the summarizerreplaceInstructions: If true,customInstructionsreplaces the default prompt instead of being appendedlabel: Label to attach to the branch summary entry (or target entry if not summarizing)
ctx.switchSession(sessionId, options?)
Switch to a different session by its exact ID:withSession: run post-switch work against a fresh replacement-session context. Do not use captured oldpi/ commandctx; see Session replacement lifecycle and footguns.
SessionManager.list() or SessionManager.listAll() methods:
Session replacement lifecycle and footguns
withSession receives a fresh ReplacedSessionContext, which extends ExtensionCommandContext with async sendMessage() and sendUserMessage() helpers bound to the replacement session.
Lifecycle and footguns:
withSessionruns only after the old session has emittedsession_shutdown, the old runtime has been torn down, the replacement session has been rebound, and the new extension instance has already receivedsession_start.- The callback still executes in the original closure, not inside the new extension instance. That means your old extension instance may already have run its shutdown cleanup before
withSessionstarts. - Captured old
pi/ old commandctxsession-bound objects are stale after replacement and will throw if used. Use only thectxpassed towithSessionfor session-bound work. - Previously extracted raw objects are still your responsibility. For example, if you capture
const sm = ctx.sessionManagerbefore replacement,smis still the oldSessionManagerobject. Do not reuse it after replacement. - Code in
withSessionshould assume any state invalidated by yoursession_shutdownhandler is already gone. Only capture plain data that survives shutdown cleanly, such as strings, ids, and serialized config. - Long-lived callbacks that need to classify a stale API error should use
isStaleExtensionContextError(error)from@bastani/atomic, not match the host’s error message.
ctx.reload()
Run the same reload flow as/reload.
await ctx.reload()emitssession_shutdownfor the current extension runtime- It then reloads resources and emits
session_startwithreason: "reload"andresources_discoverwith reason"reload" - The currently running command handler still continues in the old call frame
- Code after
await ctx.reload()still runs from the pre-reload version - Code after
await ctx.reload()must not assume old in-memory extension state is still valid - After the handler returns, future commands/events/tool calls use the new extension version
await ctx.reload(); return;).
Tools run with ExtensionContext, so they cannot call ctx.reload() directly. Use a command as the reload entrypoint, then expose a tool that queues that command as a follow-up user message.
Example tool the LLM can call to trigger reload:
ExtensionAPI Methods
pi.on(event, handler)
Subscribe to events. See Events for event types and return values.pi.registerTool(definition)
Register a custom tool callable by the LLM. See Custom Tools for full details.pi.registerTool() works both during extension load and after startup. You can call it inside session_start, command handlers, or other event handlers. New tools are refreshed immediately in the same session, so they appear in pi.getAllTools() and are callable by the LLM without /reload.
Use pi.setActiveTools() to enable or disable registered tools, including dynamically added tools and Intercom. Tools suppressed by a session allowlist or exclusions cannot be activated this way.
Use promptSnippet to opt a custom tool into a one-line entry in Available tools, and promptGuidelines to append tool-specific bullets to the default Guidelines section when the tool is active.
Important: promptGuidelines bullets are appended flat to the Guidelines section with no tool name prefix. Each guideline must name the tool it refers to — avoid “Use this tool when…” because the LLM cannot tell which tool “this” means. Write “Use my_tool when…” instead.
See dynamic-tools.ts for a full example.
Built-in tool prompt contributions
Atomic exports immutable prompt metadata for its built-in coding tools. Use these constants when a custom harness or tool registry needs the same prompt entries as the built-in factories:snippet for the Available tools section and readonly guidelines for the active tool’s Guidelines entries. The seven exports are bash, edit, find, ls, read, search, and write; Atomic’s public search export is the corresponding surface for pi’s upstream grep tool. The built-in factories use these values directly, so consumers do not need to duplicate prompt text.
Use Atomic’s export rather than importing StringEnum directly from Pi. It preserves Pi’s Google-compatible runtime schema while keeping the schema typed against Atomic’s direct TypeBox version.
pi.sendMessage(message, options?)
Inject a custom message into the session. The call returnsvoid | Promise<void> for compatibility with synchronous hosts; use await Promise.resolve(pi.sendMessage(...)) when admission or routing failure must be observed. Atomic’s AgentSession runtime returns an admission receipt: it settles after the message is accepted by the local queue or workflow late-message route, without waiting for the resulting model turn to finish.
deliverAs- Delivery mode:"steer"(default) - Queues the message while streaming. Delivered after the current assistant turn finishes executing its tool calls, before the next LLM call."followUp"- Waits for agent to finish. Delivered only when agent has no more tool calls."nextTurn"- Queued for next user prompt. Does not interrupt or trigger anything."interrupt"- WithtriggerTurn: true, aborts an active streaming turn and immediately starts a new turn with the custom message. When idle, behaves like a triggered custom message.
triggerTurn: true- If agent is idle, trigger an LLM response immediately. Required for"interrupt"; ignored for"nextTurn".excludeFromContext: true- Render and persist the custom message without adding it to LLM context. With nodeliverAs, this remains display-only even while the agent is streaming.interruptAbortMessage- Optional text used to replace generic abort results (for exampleOperation aborted) whendeliverAs: "interrupt"aborts an active turn.
pi.sendMessages(messages, options?)
Atomically admit a batch of custom messages in array order. The call returnsvoid | Promise<void> for compatibility with synchronous hosts; use await Promise.resolve(pi.sendMessages(...)) when admission or routing failure must be observed. The promise is an admission receipt and does not wait for the resulting model turn. Admission is indivisible; use this when a prelude and terminal notice must stay contiguous without globally serializing other extension work.
triggerTurn, excludeFromContext, and deliverAs: "steer" | "followUp" | "nextTurn". Interrupt delivery remains a single-message operation.
pi.sendUserMessage(content, options?)
Send a user message to the agent. UnlikesendMessage() which sends custom messages, this sends an actual user message that appears as if typed by the user. Always triggers a turn.
-
deliverAs- Required when agent is streaming:"steer"- Queues the message for delivery after the current assistant turn finishes executing its tool calls"followUp"- Waits for agent to finish all tools
-
expandPromptTemplates- Dispatch extension commands and expand skill commands and prompt templates instead of sending the text literally. Defaults tofalse, so an extension-authored message is sent as-is unless it opts in; an unknown command falls through to a literal send.
deliverAs, throws an error. While the session compacts, the message waits, shows as queued, and is sent once compaction ends, after messages sent before it; if one of those has started a turn by then, a message without deliverAs is queued behind it as steering.
See send-user-message.ts for a complete example.
pi.appendEntry(customType, data?)
Persist extension state (does NOT participate in LLM context).pi.appendEntry before using the new entry. The promise resolves after the write commits and rejects with the store’s typed error if it fails. Setup entries also commit before the promise resolves. Appending emits entry_appended with the committed entry. This lets extensions react to session entries without polling.
During a transactional reload’s session_start handler, pi.appendEntry, pi.setSessionName, and pi.setLabel stage their writes instead. Awaiting them confirms staging; the writes and their notifications are published in call order only after reload succeeds. Rejected reloads discard these changes. Do not depend on reading the staged changes back during that handler.
pi.registerEntryRenderer(customType, renderer)
Register a TUI renderer for durable custom entries created bypi.appendEntry(). These entries render in the transcript but do not enter model context.
SDK hosts can inspect the original callback with session.extensionRunner.getEntryRenderer(customType). To render through the owning session, call session.extensionRunner.renderEntry(customType, entry, { expanded }, theme); retired runners reject rendering.
pi.setSessionName(name)
Set the session display name (shown in session selector instead of first message).pi.getSessionName()
Get the current session name, if set.pi.setLabel(entryId, label)
Set or clear a label on an entry. Labels are user-defined markers for bookmarking and navigation (shown in/tree selector).
pi.registerCommand(name, options)
Register a command. If multiple extensions register the same command name, Atomic keeps them all and assigns numeric invocation suffixes in load order, for example/review:1 and /review:2.
/command ...:
pi.getCommands()
Get the slash commands available for invocation viaprompt in the current session. Includes extension commands, prompt templates, and skill commands.
The list matches the RPC get_commands ordering: extensions first, then templates, then skills.
sourceInfo as the canonical provenance field. Do not infer ownership from command names or from ad hoc path parsing.
Built-in interactive commands (like /model and /settings) are not included here. They are handled only in interactive
mode and would not execute if sent via prompt.
pi.registerMessageRenderer(customType, renderer)
Register a custom TUI renderer for messages with yourcustomType. The renderer options contain expanded and the current numeric outputPad, so custom output can align with built-in messages. The same options are provided in normal and isolated-engine rendering. See Custom UI.
pi.registerMarkdownTransformer(transformer)
Register a synchronous, display-only transformer for Markdown before Atomic renders it in interactive mode. Atomic runs transformers in extension load order. Each extension retains one transformer, so a later call from that extension replaces its prior transformer. Each transformer receives the Markdown returned by the prior transformer, then Atomic renders the final value with its built-in Markdown renderer. The optional second argument,pi.registerMarkdownTransformer(transformer, options), takes options.messageTypes. By default a transformer runs on normal user text, assistant text, and thinking blocks; messageTypes lists the surfaces it runs on instead:
The transformer receives the Markdown string and a context with:
messageType— one of the surfaces aboveisStreaming—truefor partial assistant updates;falseotherwiseavailableWidth— exact terminal columns available for the transformed Markdown contentmessageId— the session entry id when the message is rendered from the saved session (resume,/tree, rebuilds);undefinedfor live messages and the changelogtimestamp— the session entry’s ISO timestamp under the same conditions;undefinedfor live messages and the changelog
pi.registerShortcut(shortcut, options)
Register a keyboard shortcut. See Keybindings for the shortcut format and built-in keybindings.preferEditor: true to skip a shortcut when another resolved editor binding uses that key. Omitting it preserves the usual extension conflict policy. Optional keybinding selects the keys for an existing named action from keybindings.json instead of the literal registration key; an empty action binding disables it. The builtin workflow extension uses app.workflows.scrollUp and app.workflows.scrollDown with preferEditor: true, so custom Vim editor bindings take precedence.
pi.registerFlag(name, options)
Register a CLI flag.pi.getWorkflowHostModules()
Returns a promise for the running host’s in-memory module namespaces. The workflows adapter uses this capability to avoid re-evaluating the host’s package graphs for each workflow file. The returned object has exactly these keys:@bastani/atomic@bastani/pi-ai, backed by the host’s compatibility API@bastani/pi-ai/providers/all
AuthStorage and sharing module state. This also supports standalone-binary projects without those packages in node_modules. Other deep subpaths and third-party dependencies still resolve from disk. Workflow files and their relative imports are evaluated fresh on /workflow reload.
getWorkflowHostModules() is required on the current Atomic ExtensionAPI. It is optional on the workflows adapter’s host interface so older or alternative hosts can omit it. Integrations that support those hosts should check for the method before calling it, for example await pi.getWorkflowHostModules?.(). Without it, the workflows adapter retains filesystem package resolution and needs resolvable packages for these imports.
pi.exec(command, args, options?)
Execute a command without a shell.signal and timeout terminate the command, cwd sets the working directory, and maxBuffer limits stdout and stderr combined (default 256 MiB). When output exceeds maxBuffer, Atomic terminates the command and returns the output up to the limit with truncated: true, killed: true, and a nonzero code. killed is true whenever Atomic terminated the command: timeout, abort, or maxBuffer. A command that ignores SIGTERM gets SIGKILL after 5 seconds.
pi.getActiveTools() / pi.getAllTools() / pi.setActiveTools(names)
Manage active tools. This works for both built-in tools and dynamically registered tools.pi.getActiveTools() returns the active tool names as string[]; pi.getAllTools() returns metadata for all configured tools.
pi.getAllTools() returns name, description, parameters, promptGuidelines, exposure, namespace, annotations, and sourceInfo. See tool exposure before changing activation for script-callable tools.
Typical sourceInfo.source values:
builtinfor built-in toolssdkfor tools passed viacreateAgentSession({ customTools })- extension source metadata for tools registered by extensions
pi.setModel(model)
Set the model for the current session. The change is recorded in session history and restored when that session is resumed, but it does not change the configureddefaultProvider or defaultModel used by new sessions. Returns false if authentication is not configured for the model’s provider. See Custom models for configuring custom models.
pi.getThinkingLevel() / pi.setThinkingLevel(level)
Get the current thinking level. Level is clamped to model capabilities (non-reasoning models always use"off"; "xhigh" and "max" require model support). Changes emit thinking_level_select.
pi.setThinkingLevel() changes the thinking level for the current session. The change is recorded in session history and restored when that session is resumed, but it does not change the configured default used by new sessions.
pi.events
Shared event bus for communication between active extensions. A subscription made withon() is removed automatically when its extension reloads or the session disposes. Use the returned function if you need to stop listening sooner:
pi.events belongs to the extension instance that received it. Register listeners again when that instance reloads, and do not retain the object for later use: calling on() or emit() through a captured handle after reload or disposal throws. To keep in-memory state across /reload, pass this facade to sessionScopedExtensionState; do not capture the facade itself.
If you implement an ExtensionRuntime for an embedded host, provide trackEventBusSubscription(unsubscribe) and retain each returned subscription until that extension runtime is reloaded or disposed. ExtensionUIContext.getChatRenderSettings() must return markdownTransformers; it may also return renderLatex to control terminal math rendering. These fields keep event subscriptions and display transforms scoped to the active extension instance.
Native providers
In addition toregisterProvider(name, config), extensions can register a complete native Provider from @bastani/pi-ai with pi.registerProvider(provider). Use the native overload for provider-owned authentication, catalog refresh, and transport behavior; use the config overload for ordinary proxies and custom endpoints.
pi.registerProvider(name, config)
Register or override a model provider dynamically. Await registration, aliases, and removal before starting dependent work. Useful for proxies, custom endpoints, or team-wide model configurations. Calls made during the extension factory function are queued and applied once the runner initialises. Calls made after that — for example from a command handler following a user setup flow — take effect immediately without requiring a/reload.
If you need to discover models from a remote endpoint, prefer an async extension factory over deferring the fetch to session_start. Atomic waits for the factory before startup continues, so the registered models are available immediately, including to atomic --list-models.
refreshToken receives a signal that Atomic does not abort on a timeout: once the token request is sent, the provider may already have rotated the refresh token, and only its response carries the replacement. The caller stops waiting after 60 seconds, but the exchange keeps running and Atomic saves its result when it arrives. When Atomic exits normally, it waits up to three seconds for an exchange still in flight. Cancelling a request or model refresh likewise stops only that caller’s wait. Do not add your own timeout or abort to the token request, and do not send the same refresh token again after the provider may have received it. Return the rotated credentials, even when the caller that started the refresh has stopped waiting.
Config options:
name- Display name for the provider in UI such as/login.baseUrl- API endpoint URL. Required when defining models.apiKey- API key literal or explicit environment variable reference ($ENV_VARor${ENV_VAR}). Required when defining models (unlessoauthprovided).api- API type:"anthropic-messages","openai-completions","openai-responses", etc.headers- Custom headers to include in requests.authHeader- If true, addsAuthorization: Bearerheader automatically.models- Array of model definitions. If provided, replaces all existing models for this provider. Model definitions can setbaseUrlto override the provider endpoint for that model.oauth- OAuth provider config for/loginsupport. When provided, the provider appears in the login menu.auth.apiKey- Provider-owned API-key or connection setup for/login. Itsnameappears in the provider list andlogin({ signal, prompt })returns the credential Atomic persists. Extension providers registered only in the isolated interactive engine child are synchronized into the host’s/loginlist; their login callback and credential-dependent model refresh still execute in the child, while prompts are rendered by the terminal host.streamSimple- Custom streaming implementation for non-standard APIs.
pi.unregisterProvider(name)
Remove a previously registered provider and its models. Built-in models that were overridden by the provider are restored. Has no effect if the provider was not registered. LikeregisterProvider, this takes effect immediately when called after the initial load phase, so a /reload is not required.
pi.registerMcpServer(name, config)
Contribute an MCP server whose configuration is only known at runtime, such as the URL of a local service the extension starts.config uses the same schema as an entry in mcp.json, including url, command/args/env/cwd, headers, OAuth settings, exposure, and per-request timeout in seconds. URLs support ${VAR} and $env:VAR interpolation.
session_start. Load order does not matter: registrations made after the MCP adapter has started are picked up without a reload. Registering the same name again replaces the earlier registration.
Registered servers are the lowest-precedence MCP layer. They replace a same-named server from a package manifest, but a user or trusted-project MCP config entry replaces them. Set "enabled": false on a complete file-configured entry to keep it without connecting. /mcp shows the registering extension next to the server name.
pi.getMcpServerContributions() returns the package-manifest and registered servers for the session, each with its name, config, origin ("package" or "extension"), and sourceInfo.
Error Handling
- Extension errors are logged, agent continues
tool_callerrors block the tool (fail-safe)- Tool
executeerrors must be signaled by throwing; the thrown error is caught, reported to the LLM withisError: true, and execution continues