extensions-types.ts
58 KB1695 lines
extensions-types.ts
1/**2 * Extension system types.3 *4 * Extensions are TypeScript modules that can:5 * - Subscribe to agent lifecycle events6 * - Register LLM-callable tools7 * - Register commands, keyboard shortcuts, and CLI flags8 * - Interact with the user via UI primitives9 */1011import type {12 AgentMessage,13 AgentToolResult,14 AgentToolUpdateCallback,15 ThinkingLevel,16 ToolExecutionMode,17} from "@earendil-works/pi-agent-core";18import type {19 Api,20 AssistantMessageEvent,21 AssistantMessageEventStream,22 ConstrainedSamplingConfig,23 Context,24 ImageContent,25 Model,26 OAuthCredentials,27 OAuthLoginCallbacks,28 Provider,29 ProviderHeaders,30 RefreshModelsContext,31 SimpleStreamOptions,32 TextContent,33 ToolResultMessage,34 Usage,35} from "@earendil-works/pi-ai";36import type {37 AutocompleteItem,38 AutocompleteProvider,39 Component,40 EditorComponent,41 EditorTheme,42 KeyId,43 OverlayHandle,44 OverlayOptions,45 TUI,46} from "@earendil-works/pi-tui";47import type { Static, TSchema } from "typebox";48import type { Theme } from "../../modes/interactive/theme/theme.ts";49import type { BashResult } from "../bash-executor.ts";50import type { CompactionPreparation, CompactionResult } from "../compaction/index.ts";51import type { EventBus } from "../event-bus.ts";52import type { ExecOptions, ExecResult } from "../exec.ts";53import type { ReadonlyFooterDataProvider } from "../footer-data-provider.ts";54import type { KeybindingsManager } from "../keybindings.ts";55import type { CustomMessage } from "../messages.ts";56import type { ModelRegistry } from "../model-registry.ts";57import type {58 BranchSummaryEntry,59 CompactionEntry,60 CustomEntry,61 ReadonlySessionManager,62 SessionEntry,63 SessionManager,64} from "../session-manager.ts";65import type { SlashCommandInfo } from "../slash-commands.ts";66import type { SourceInfo } from "../source-info.ts";67import type { BuildSystemPromptOptions } from "../system-prompt.ts";68import type { BashOperations } from "../tools/bash.ts";69import type { EditToolDetails } from "../tools/edit.ts";70import type {71 BashToolDetails,72 BashToolInput,73 EditToolInput,74 FindToolDetails,75 FindToolInput,76 GrepToolDetails,77 GrepToolInput,78 LsToolDetails,79 LsToolInput,80 ReadToolDetails,81 ReadToolInput,82 WriteToolInput,83} from "../tools/index.ts";8485export type { ExecOptions, ExecResult } from "../exec.ts";86export type { BuildSystemPromptOptions } from "../system-prompt.ts";87export type { AgentToolResult, AgentToolUpdateCallback, ToolExecutionMode };88export type { AppKeybinding, KeybindingsManager } from "../keybindings.ts";8990// ============================================================================91// UI Context92// ============================================================================9394/** Options for extension UI dialogs. */95export interface ExtensionUIDialogOptions {96 /** AbortSignal to programmatically dismiss the dialog. */97 signal?: AbortSignal;98 /** Timeout in milliseconds. Dialog auto-dismisses with live countdown display. */99 timeout?: number;100}101102/** Placement for extension widgets. */103export type WidgetPlacement = "aboveEditor" | "belowEditor";104105/** Options for extension widgets. */106export interface ExtensionWidgetOptions {107 /** Where the widget is rendered. Defaults to "aboveEditor". */108 placement?: WidgetPlacement;109}110111/** Raw terminal input listener for extensions. */112export type TerminalInputHandler = (data: string) => { consume?: boolean; data?: string } | undefined;113114/** Working indicator configuration for the interactive streaming loader. */115export interface WorkingIndicatorOptions {116 /** Animation frames. Use an empty array to hide the indicator entirely. Custom frames are rendered verbatim. */117 frames?: string[];118 /** Frame interval in milliseconds for animated indicators. */119 intervalMs?: number;120}121122/** Wrap the current autocomplete provider with additional behavior. */123export type AutocompleteProviderFactory = (current: AutocompleteProvider) => AutocompleteProvider;124export type EditorFactory = (tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => EditorComponent;125126/**127 * UI context for extensions to request interactive UI.128 * Each mode (interactive, RPC, print) provides its own implementation.129 */130export interface ExtensionUIContext {131 /** Show a selector and return the user's choice. */132 select(title: string, options: string[], opts?: ExtensionUIDialogOptions): Promise<string | undefined>;133134 /** Show a confirmation dialog. */135 confirm(title: string, message: string, opts?: ExtensionUIDialogOptions): Promise<boolean>;136137 /** Show a text input dialog. */138 input(title: string, placeholder?: string, opts?: ExtensionUIDialogOptions): Promise<string | undefined>;139140 /** Show a notification to the user. */141 notify(message: string, type?: "info" | "warning" | "error"): void;142143 /** Listen to raw terminal input (interactive mode only). Returns an unsubscribe function. */144 onTerminalInput(handler: TerminalInputHandler): () => void;145146 /** Set status text in the footer/status bar. Pass undefined to clear. */147 setStatus(key: string, text: string | undefined): void;148149 /** Set the working/loading message shown during streaming. Call with no argument to restore default. */150 setWorkingMessage(message?: string): void;151152 /** Show or hide the built-in interactive working loader row during streaming. */153 setWorkingVisible(visible: boolean): void;154155 /**156 * Configure the interactive working indicator shown during streaming.157 *158 * - Omit the argument to restore the default animated spinner.159 * - Use `frames: ["●"]` for a static indicator.160 * - Use `frames: []` to hide the indicator entirely.161 * - Custom frames are rendered as provided, so extensions must add their own colors.162 */163 setWorkingIndicator(options?: WorkingIndicatorOptions): void;164165 /** Set the label shown for hidden thinking blocks. Call with no argument to restore default. */166 setHiddenThinkingLabel(label?: string): void;167168 /** Set a widget to display above or below the editor. Accepts string array or component factory. */169 setWidget(key: string, content: string[] | undefined, options?: ExtensionWidgetOptions): void;170 setWidget(171 key: string,172 content: ((tui: TUI, theme: Theme) => Component & { dispose?(): void }) | undefined,173 options?: ExtensionWidgetOptions,174 ): void;175176 /** Set a custom footer component, or undefined to restore the built-in footer.177 *178 * The factory receives a FooterDataProvider for data not otherwise accessible:179 * git branch and extension statuses from setStatus(). Token stats, model info,180 * etc. are available via ctx.sessionManager and ctx.model.181 */182 setFooter(183 factory:184 | ((tui: TUI, theme: Theme, footerData: ReadonlyFooterDataProvider) => Component & { dispose?(): void })185 | undefined,186 ): void;187188 /** Set a custom header component (shown at startup, above chat), or undefined to restore the built-in header. */189 setHeader(factory: ((tui: TUI, theme: Theme) => Component & { dispose?(): void }) | undefined): void;190191 /** Set the terminal window/tab title. */192 setTitle(title: string): void;193194 /** Show a custom component with keyboard focus. */195 custom<T>(196 factory: (197 tui: TUI,198 theme: Theme,199 keybindings: KeybindingsManager,200 done: (result: T) => void,201 ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,202 options?: {203 overlay?: boolean;204 /** Overlay positioning/sizing options. Can be static or a function for dynamic updates. */205 overlayOptions?: OverlayOptions | (() => OverlayOptions);206 /** Called with the overlay handle after the overlay is shown. Use to control visibility. */207 onHandle?: (handle: OverlayHandle) => void;208 },209 ): Promise<T>;210211 /** Paste text into the editor, triggering paste handling (collapse for large content). */212 pasteToEditor(text: string): void;213214 /** Set the text in the core input editor. */215 setEditorText(text: string): void;216217 /** Get the current text from the core input editor. */218 getEditorText(): string;219220 /** Show a multi-line editor for text editing. */221 editor(title: string, prefill?: string): Promise<string | undefined>;222223 /** Stack additional autocomplete behavior on top of the built-in provider. */224 addAutocompleteProvider(factory: AutocompleteProviderFactory): void;225226 /**227 * Set a custom editor component via factory function.228 * Pass undefined to restore the default editor.229 *230 * The factory receives:231 * - `theme`: EditorTheme for styling borders and autocomplete232 * - `keybindings`: KeybindingsManager for app-level keybindings233 *234 * For full app keybinding support (escape, ctrl+d, model switching, etc.),235 * extend `CustomEditor` from `@earendil-works/pi-coding-agent` and call236 * `super.handleInput(data)` for keys you don't handle.237 *238 * @example239 * ```ts240 * import { CustomEditor } from "@earendil-works/pi-coding-agent";241 *242 * class VimEditor extends CustomEditor {243 * private mode: "normal" | "insert" = "insert";244 *245 * handleInput(data: string): void {246 * if (this.mode === "normal") {247 * // Handle vim normal mode keys...248 * if (data === "i") { this.mode = "insert"; return; }249 * }250 * super.handleInput(data); // App keybindings + text editing251 * }252 * }253 *254 * ctx.ui.setEditorComponent((tui, theme, keybindings) =>255 * new VimEditor(tui, theme, keybindings)256 * );257 * ```258 */259 setEditorComponent(factory: EditorFactory | undefined): void;260261 /** Get the currently configured custom editor factory, or undefined when using the default editor. */262 getEditorComponent(): EditorFactory | undefined;263264 /** Get the current theme for styling. */265 readonly theme: Theme;266267 /** Get all available themes with their names and file paths. */268 getAllThemes(): { name: string; path: string | undefined }[];269270 /** Load a theme by name without switching to it. Returns undefined if not found. */271 getTheme(name: string): Theme | undefined;272273 /** Set the current theme by name or Theme object. */274 setTheme(theme: string | Theme): { success: boolean; error?: string };275276 /** Get current tool output expansion state. */277 getToolsExpanded(): boolean;278279 /** Set tool output expansion state. */280 setToolsExpanded(expanded: boolean): void;281}282283// ============================================================================284// Extension Context285// ============================================================================286287export interface ContextUsage {288 /** Estimated context tokens, or null if unknown (e.g. right after compaction, before next LLM response). */289 tokens: number | null;290 contextWindow: number;291 /** Context usage as percentage of context window, or null if tokens is unknown. */292 percent: number | null;293}294295export interface CompactOptions {296 customInstructions?: string;297 onComplete?: (result: CompactionResult) => void;298 onError?: (error: Error) => void;299}300301/**302 * Context passed to extension event handlers.303 */304export type ExtensionMode = "tui" | "rpc" | "json" | "print";305306export interface ExtensionContext {307 /** UI methods for user interaction */308 ui: ExtensionUIContext;309 /** Current run mode. Use "tui" to guard terminal-only UI such as custom components. */310 mode: ExtensionMode;311 /** Whether dialog-capable UI is available (true in TUI and RPC modes) */312 hasUI: boolean;313 /** Current working directory */314 cwd: string;315 /** Session manager (read-only) */316 sessionManager: ReadonlySessionManager;317 /** Model registry for API key resolution */318 modelRegistry: ModelRegistry;319 /** Current model (may be undefined) */320 model: Model<any> | undefined;321 /** Current thinking level, when provided by the session runtime. */322 thinkingLevel?: ThinkingLevel;323 /** Whether the agent is idle (not streaming) */324 isIdle(): boolean;325 /** Whether project-local trust is active for this context. */326 isProjectTrusted(): boolean;327 /** The current abort signal, or undefined when the agent is not streaming. */328 signal: AbortSignal | undefined;329 /** Abort the current agent operation */330 abort(): void;331 /** Whether there are queued messages waiting */332 hasPendingMessages(): boolean;333 /** Gracefully shutdown pi and exit. Available in all contexts. */334 shutdown(): void;335 /** Get current context usage for the active model. */336 getContextUsage(): ContextUsage | undefined;337 /** Trigger compaction without awaiting completion. */338 compact(options?: CompactOptions): void;339 /** Get the current effective system prompt. */340 getSystemPrompt(): string;341}342343/**344 * Extended context for command handlers.345 * Includes session control methods only safe in user-initiated commands.346 */347export interface ExtensionCommandContext extends ExtensionContext {348 /** Get the current base system-prompt construction options. */349 getSystemPromptOptions(): BuildSystemPromptOptions;350351 /** Wait for the agent to finish streaming */352 waitForIdle(): Promise<void>;353354 /** Start a new session, optionally with initialization. */355 newSession(options?: {356 parentSession?: string;357 setup?: (sessionManager: SessionManager) => Promise<void>;358 withSession?: (ctx: ReplacedSessionContext) => Promise<void>;359 }): Promise<{ cancelled: boolean }>;360361 /** Fork from a specific entry, creating a new session file. */362 fork(363 entryId: string,364 options?: { position?: "before" | "at"; withSession?: (ctx: ReplacedSessionContext) => Promise<void> },365 ): Promise<{ cancelled: boolean }>;366367 /** Navigate to a different point in the session tree. */368 navigateTree(369 targetId: string,370 options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string },371 ): Promise<{ cancelled: boolean }>;372373 /** Switch to a different session file. */374 switchSession(375 sessionPath: string,376 options?: { withSession?: (ctx: ReplacedSessionContext) => Promise<void> },377 ): Promise<{ cancelled: boolean }>;378379 /** Reload extensions, skills, prompts, themes, and context files. */380 reload(): Promise<void>;381}382383/**384 * Fresh command-capable context bound to the replacement session after a session switch.385 *386 * This is passed to `withSession()` callbacks on `newSession()`, `fork()`, and `switchSession()`.387 */388export interface ReplacedSessionContext extends ExtensionCommandContext {389 sendMessage<T = unknown>(390 message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,391 options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },392 ): Promise<void>;393394 sendUserMessage(395 content: string | (TextContent | ImageContent)[],396 options?: { deliverAs?: "steer" | "followUp" },397 ): Promise<void>;398}399400// ============================================================================401// Tool Types402// ============================================================================403404/** Rendering options for tool results */405export interface ToolRenderResultOptions {406 /** Whether the result view is expanded */407 expanded: boolean;408 /** Whether this is a partial/streaming result */409 isPartial: boolean;410}411412/** Context passed to tool renderers. */413export interface ToolRenderContext<TState = any, TArgs = any> {414 /** Current tool call arguments. Shared across call/result renders for the same tool call. */415 args: TArgs;416 /** Unique id for this tool execution. Stable across call/result renders for the same tool call. */417 toolCallId: string;418 /** Invalidate just this tool execution component for redraw. */419 invalidate: () => void;420 /** Previously returned component for this render slot, if any. */421 lastComponent: Component | undefined;422 /** Shared renderer state for this tool row. Initialized by tool-execution.ts. */423 state: TState;424 /** Working directory for this tool execution. */425 cwd: string;426 /** Whether the tool execution has started. */427 executionStarted: boolean;428 /** Whether the tool call arguments are complete. */429 argsComplete: boolean;430 /** Whether the tool result is partial/streaming. */431 isPartial: boolean;432 /** Whether the result view is expanded. */433 expanded: boolean;434 /** Whether inline images are currently shown in the TUI. */435 showImages: boolean;436 /** Whether the current result is an error. */437 isError: boolean;438}439440/**441 * Tool definition for registerTool().442 */443export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = unknown, TState = any> {444 /** Tool name (used in LLM tool calls) */445 name: string;446 /** Human-readable label for UI */447 label: string;448 /** Description for LLM */449 description: string;450 /** Optional one-line snippet for the Available tools section in the default system prompt. Custom tools are omitted from that section when this is not provided. */451 promptSnippet?: string;452 /** Optional guideline bullets appended to the default system prompt Guidelines section when this tool is active. */453 promptGuidelines?: string[];454 /** Parameter schema (TypeBox) */455 parameters: TParams;456 /** Optional provider-side constrained sampling request for this tool. Set false to explicitly disable it, equivalent to leaving it undefined. */457 constrainedSampling?: false | ConstrainedSamplingConfig;458 /** Controls whether ToolExecutionComponent renders the standard colored shell or the tool renders its own framing. */459 renderShell?: "default" | "self";460461 /** Optional compatibility shim to prepare raw tool call arguments before schema validation. Must return an object conforming to TParams. */462 prepareArguments?: (args: unknown) => Static<TParams>;463464 /**465 * Per-tool execution mode override.466 * - "sequential": this tool must execute one at a time with other tool calls.467 * - "parallel": this tool can execute concurrently with other tool calls.468 *469 * If omitted, the default execution mode applies.470 */471 executionMode?: ToolExecutionMode;472473 /** Execute the tool. */474 execute(475 toolCallId: string,476 params: Static<TParams>,477 signal: AbortSignal | undefined,478 onUpdate: AgentToolUpdateCallback<TDetails> | undefined,479 ctx: ExtensionContext,480 ): Promise<AgentToolResult<TDetails>>;481482 /** Custom rendering for tool call display */483 renderCall?: (args: Static<TParams>, theme: Theme, context: ToolRenderContext<TState, Static<TParams>>) => Component;484485 /** Custom rendering for tool result display */486 renderResult?: (487 result: AgentToolResult<TDetails>,488 options: ToolRenderResultOptions,489 theme: Theme,490 context: ToolRenderContext<TState, Static<TParams>>,491 ) => Component;492}493494type AnyToolDefinition = ToolDefinition<any, any, any>;495496/**497 * Preserve parameter inference for standalone tool definitions.498 *499 * Use this when assigning a tool to a variable or passing it through arrays such500 * as `customTools`, where contextual typing would otherwise widen params to501 * `unknown`.502 */503export function defineTool<TParams extends TSchema, TDetails = unknown, TState = any>(504 tool: ToolDefinition<TParams, TDetails, TState>,505): ToolDefinition<TParams, TDetails, TState> & AnyToolDefinition {506 return tool as ToolDefinition<TParams, TDetails, TState> & AnyToolDefinition;507}508509// ============================================================================510// Startup/Resource Events511// ============================================================================512513export interface ProjectTrustEvent {514 type: "project_trust";515 cwd: string;516}517518export type ProjectTrustEventDecision = "yes" | "no" | "undecided";519520export interface ProjectTrustEventResult {521 trusted: ProjectTrustEventDecision;522 remember?: boolean;523}524525export interface ProjectTrustContext {526 cwd: string;527 mode: ExtensionMode;528 hasUI: boolean;529 ui: Pick<ExtensionUIContext, "select" | "confirm" | "input" | "notify">;530}531532export type ProjectTrustHandler = (533 event: ProjectTrustEvent,534 ctx: ProjectTrustContext,535) => Promise<ProjectTrustEventResult> | ProjectTrustEventResult;536537/** Fired after session_start to allow extensions to provide additional resource paths. */538export interface ResourcesDiscoverEvent {539 type: "resources_discover";540 cwd: string;541 reason: "startup" | "reload";542}543544/** Result from resources_discover event handler */545export interface ResourcesDiscoverResult {546 skillPaths?: string[];547 promptPaths?: string[];548 themePaths?: string[];549}550551// ============================================================================552// Session Events553// ============================================================================554555/** Fired when a session is started, loaded, or reloaded */556export interface SessionStartEvent {557 type: "session_start";558 /** Why this session start happened. */559 reason: "startup" | "reload" | "new" | "resume" | "fork";560 /** Previously active session file. Present for "new", "resume", and "fork". */561 previousSessionFile?: string;562}563564/** Fired when the current session metadata changes. */565export interface SessionInfoChangedEvent {566 type: "session_info_changed";567 /** Current normalized session name. Undefined when the name is cleared. */568 name: string | undefined;569}570571/** Fired before switching to another session (can be cancelled) */572export interface SessionBeforeSwitchEvent {573 type: "session_before_switch";574 reason: "new" | "resume";575 targetSessionFile?: string;576}577578/** Fired before forking a session (can be cancelled) */579export interface SessionBeforeForkEvent {580 type: "session_before_fork";581 entryId: string;582 position: "before" | "at";583}584585/** Fired before context compaction (can be cancelled or customized) */586export interface SessionBeforeCompactEvent {587 type: "session_before_compact";588 preparation: CompactionPreparation;589 branchEntries: SessionEntry[];590 customInstructions?: string;591 /** What triggered the compaction: manual /compact, the context threshold, or context overflow recovery */592 reason: "manual" | "threshold" | "overflow";593 /** True when the aborted turn is retried after this compaction (overflow recovery) */594 willRetry: boolean;595 signal: AbortSignal;596}597598/** Fired after context compaction */599export interface SessionCompactEvent {600 type: "session_compact";601 compactionEntry: CompactionEntry;602 fromExtension: boolean;603 /** What triggered the compaction: manual /compact, the context threshold, or context overflow recovery */604 reason: "manual" | "threshold" | "overflow";605 /** True when the aborted turn is retried after this compaction (overflow recovery) */606 willRetry: boolean;607}608609/** Fired before an extension runtime is torn down due to quit, reload, or session replacement. */610export interface SessionShutdownEvent {611 type: "session_shutdown";612 reason: "quit" | "reload" | "new" | "resume" | "fork";613 /** Destination session file when shutting down due to session replacement. */614 targetSessionFile?: string;615}616617/** Preparation data for tree navigation */618export interface TreePreparation {619 targetId: string;620 oldLeafId: string | null;621 commonAncestorId: string | null;622 entriesToSummarize: SessionEntry[];623 userWantsSummary: boolean;624 /** Custom instructions for summarization */625 customInstructions?: string;626 /** If true, customInstructions replaces the default prompt instead of being appended */627 replaceInstructions?: boolean;628 /** Label to attach to the branch summary entry */629 label?: string;630}631632/** Fired before navigating in the session tree (can be cancelled) */633export interface SessionBeforeTreeEvent {634 type: "session_before_tree";635 preparation: TreePreparation;636 signal: AbortSignal;637}638639/** Fired after navigating in the session tree */640export interface SessionTreeEvent {641 type: "session_tree";642 newLeafId: string | null;643 oldLeafId: string | null;644 summaryEntry?: BranchSummaryEntry;645 fromExtension?: boolean;646}647648export type SessionEvent =649 | SessionStartEvent650 | SessionInfoChangedEvent651 | SessionBeforeSwitchEvent652 | SessionBeforeForkEvent653 | SessionBeforeCompactEvent654 | SessionCompactEvent655 | SessionShutdownEvent656 | SessionBeforeTreeEvent657 | SessionTreeEvent;658659// ============================================================================660// Agent Events661// ============================================================================662663/** Fired before each LLM call. Can modify messages. */664export interface ContextEvent {665 type: "context";666 messages: AgentMessage[];667}668669/** Fired before a provider request is sent. Can replace the payload. */670export interface BeforeProviderRequestEvent {671 type: "before_provider_request";672 payload: unknown;673}674675/**676 * Fired after request headers are assembled, before the provider HTTP call.677 * Handlers mutate `headers` in place (e.g. to inject tracing/session headers);678 * the return value is ignored. A `null` value deletes that header.679 */680export interface BeforeProviderHeadersEvent {681 type: "before_provider_headers";682 headers: ProviderHeaders;683}684685/** Fired after a provider response is received and before the response stream is consumed. */686export interface AfterProviderResponseEvent {687 type: "after_provider_response";688 status: number;689 headers: Record<string, string>;690}691692/** Fired after user submits prompt but before agent loop. */693export interface BeforeAgentStartEvent {694 type: "before_agent_start";695 /** The raw user prompt text (after expansion). */696 prompt: string;697 /** Images attached to the user prompt, if any. */698 images?: ImageContent[];699 /** The fully assembled system prompt string. */700 systemPrompt: string;701 /** Structured options used to build the system prompt. Extensions can inspect this to understand what Pi loaded without re-discovering resources. */702 systemPromptOptions: BuildSystemPromptOptions;703}704705/** Fired when an agent loop starts */706export interface AgentStartEvent {707 type: "agent_start";708}709710/** Fired when an agent loop ends */711export interface AgentEndEvent {712 type: "agent_end";713 messages: AgentMessage[];714}715716/** Fired after an agent run has fully settled and no automatic retry, compaction, or queued continuation will run. */717export interface AgentSettledEvent {718 type: "agent_settled";719}720721/** Fired at the start of each turn */722export interface TurnStartEvent {723 type: "turn_start";724 turnIndex: number;725 timestamp: number;726}727728/** Fired at the end of each turn */729export interface TurnEndEvent {730 type: "turn_end";731 turnIndex: number;732 message: AgentMessage;733 toolResults: ToolResultMessage[];734}735736/** Fired when a message starts (user, assistant, or toolResult) */737export interface MessageStartEvent {738 type: "message_start";739 message: AgentMessage;740}741742/** Fired during assistant message streaming with token-by-token updates */743export interface MessageUpdateEvent {744 type: "message_update";745 message: AgentMessage;746 assistantMessageEvent: AssistantMessageEvent;747}748749/** Fired when a message ends */750export interface MessageEndEvent {751 type: "message_end";752 message: AgentMessage;753}754755/** Fired when a tool starts executing */756export interface ToolExecutionStartEvent {757 type: "tool_execution_start";758 toolCallId: string;759 toolName: string;760 args: any;761}762763/** Fired during tool execution with partial/streaming output */764export interface ToolExecutionUpdateEvent {765 type: "tool_execution_update";766 toolCallId: string;767 toolName: string;768 args: any;769 partialResult: any;770}771772/** Fired when a tool finishes executing */773export interface ToolExecutionEndEvent {774 type: "tool_execution_end";775 toolCallId: string;776 toolName: string;777 result: any;778 isError: boolean;779}780781// ============================================================================782// Model Events783// ============================================================================784785export type ModelSelectSource = "set" | "cycle" | "restore";786787/** Fired when a new model is selected */788export interface ModelSelectEvent {789 type: "model_select";790 model: Model<any>;791 previousModel: Model<any> | undefined;792 source: ModelSelectSource;793}794795/** Fired when a new thinking level is selected */796export interface ThinkingLevelSelectEvent {797 type: "thinking_level_select";798 level: ThinkingLevel;799 previousLevel: ThinkingLevel;800}801802// ============================================================================803// User Bash Events804// ============================================================================805806/** Fired when user executes a bash command via ! or !! prefix */807export interface UserBashEvent {808 type: "user_bash";809 /** The command to execute */810 command: string;811 /** True if !! prefix was used (excluded from LLM context) */812 excludeFromContext: boolean;813 /** Current working directory */814 cwd: string;815}816817// ============================================================================818// Input Events819// ============================================================================820821/** Source of user input */822export type InputSource = "interactive" | "rpc" | "extension";823824/** Fired when user input is received, before agent processing */825export interface InputEvent {826 type: "input";827 /** The input text */828 text: string;829 /** Attached images, if any */830 images?: ImageContent[];831 /** Where the input came from */832 source: InputSource;833 /** How the input will be delivered during streaming, or undefined when idle */834 streamingBehavior?: "steer" | "followUp";835}836837/** Result from input event handler */838export type InputEventResult =839 | { action: "continue" }840 | { action: "transform"; text: string; images?: ImageContent[] }841 | { action: "handled" };842843// ============================================================================844// Tool Events845// ============================================================================846847interface ToolCallEventBase {848 type: "tool_call";849 toolCallId: string;850}851852export interface BashToolCallEvent extends ToolCallEventBase {853 toolName: "bash";854 input: BashToolInput;855}856857export interface ReadToolCallEvent extends ToolCallEventBase {858 toolName: "read";859 input: ReadToolInput;860}861862export interface EditToolCallEvent extends ToolCallEventBase {863 toolName: "edit";864 input: EditToolInput;865}866867export interface WriteToolCallEvent extends ToolCallEventBase {868 toolName: "write";869 input: WriteToolInput;870}871872export interface GrepToolCallEvent extends ToolCallEventBase {873 toolName: "grep";874 input: GrepToolInput;875}876877export interface FindToolCallEvent extends ToolCallEventBase {878 toolName: "find";879 input: FindToolInput;880}881882export interface LsToolCallEvent extends ToolCallEventBase {883 toolName: "ls";884 input: LsToolInput;885}886887export interface CustomToolCallEvent extends ToolCallEventBase {888 toolName: string;889 input: Record<string, unknown>;890}891892/**893 * Fired before a tool executes. Can block.894 *895 * `event.input` is mutable. Mutate it in place to patch tool arguments before execution.896 * Later `tool_call` handlers see earlier mutations. No re-validation is performed after mutation.897 */898export type ToolCallEvent =899 | BashToolCallEvent900 | ReadToolCallEvent901 | EditToolCallEvent902 | WriteToolCallEvent903 | GrepToolCallEvent904 | FindToolCallEvent905 | LsToolCallEvent906 | CustomToolCallEvent;907908interface ToolResultEventBase {909 type: "tool_result";910 toolCallId: string;911 input: Record<string, unknown>;912 content: (TextContent | ImageContent)[];913 isError: boolean;914 /** Usage from the tool execution itself, if available. */915 usage?: Usage;916}917918export interface BashToolResultEvent extends ToolResultEventBase {919 toolName: "bash";920 details: BashToolDetails | undefined;921}922923export interface ReadToolResultEvent extends ToolResultEventBase {924 toolName: "read";925 details: ReadToolDetails | undefined;926}927928export interface EditToolResultEvent extends ToolResultEventBase {929 toolName: "edit";930 details: EditToolDetails | undefined;931}932933export interface WriteToolResultEvent extends ToolResultEventBase {934 toolName: "write";935 details: undefined;936}937938export interface GrepToolResultEvent extends ToolResultEventBase {939 toolName: "grep";940 details: GrepToolDetails | undefined;941}942943export interface FindToolResultEvent extends ToolResultEventBase {944 toolName: "find";945 details: FindToolDetails | undefined;946}947948export interface LsToolResultEvent extends ToolResultEventBase {949 toolName: "ls";950 details: LsToolDetails | undefined;951}952953export interface CustomToolResultEvent extends ToolResultEventBase {954 toolName: string;955 details: unknown;956}957958/** Fired after a tool executes. Can modify result. */959export type ToolResultEvent =960 | BashToolResultEvent961 | ReadToolResultEvent962 | EditToolResultEvent963 | WriteToolResultEvent964 | GrepToolResultEvent965 | FindToolResultEvent966 | LsToolResultEvent967 | CustomToolResultEvent;968969// Type guards for ToolResultEvent970export function isBashToolResult(e: ToolResultEvent): e is BashToolResultEvent {971 return e.toolName === "bash";972}973export function isReadToolResult(e: ToolResultEvent): e is ReadToolResultEvent {974 return e.toolName === "read";975}976export function isEditToolResult(e: ToolResultEvent): e is EditToolResultEvent {977 return e.toolName === "edit";978}979export function isWriteToolResult(e: ToolResultEvent): e is WriteToolResultEvent {980 return e.toolName === "write";981}982export function isGrepToolResult(e: ToolResultEvent): e is GrepToolResultEvent {983 return e.toolName === "grep";984}985export function isFindToolResult(e: ToolResultEvent): e is FindToolResultEvent {986 return e.toolName === "find";987}988export function isLsToolResult(e: ToolResultEvent): e is LsToolResultEvent {989 return e.toolName === "ls";990}991992/**993 * Type guard for narrowing ToolCallEvent by tool name.994 *995 * Built-in tools narrow automatically (no type params needed):996 * ```ts997 * if (isToolCallEventType("bash", event)) {998 * event.input.command; // string999 * }1000 * ```1001 *1002 * Custom tools require explicit type parameters:1003 * ```ts1004 * if (isToolCallEventType<"my_tool", MyToolInput>("my_tool", event)) {1005 * event.input.action; // typed1006 * }1007 * ```1008 *1009 * Note: Direct narrowing via `event.toolName === "bash"` doesn't work because1010 * CustomToolCallEvent.toolName is `string` which overlaps with all literals.1011 */1012export function isToolCallEventType(toolName: "bash", event: ToolCallEvent): event is BashToolCallEvent;1013export function isToolCallEventType(toolName: "read", event: ToolCallEvent): event is ReadToolCallEvent;1014export function isToolCallEventType(toolName: "edit", event: ToolCallEvent): event is EditToolCallEvent;1015export function isToolCallEventType(toolName: "write", event: ToolCallEvent): event is WriteToolCallEvent;1016export function isToolCallEventType(toolName: "grep", event: ToolCallEvent): event is GrepToolCallEvent;1017export function isToolCallEventType(toolName: "find", event: ToolCallEvent): event is FindToolCallEvent;1018export function isToolCallEventType(toolName: "ls", event: ToolCallEvent): event is LsToolCallEvent;1019export function isToolCallEventType<TName extends string, TInput extends Record<string, unknown>>(1020 toolName: TName,1021 event: ToolCallEvent,1022): event is ToolCallEvent & { toolName: TName; input: TInput };1023export function isToolCallEventType(toolName: string, event: ToolCallEvent): boolean {1024 return event.toolName === toolName;1025}10261027/** Union of all event types */1028export type ExtensionEvent =1029 | ProjectTrustEvent1030 | ResourcesDiscoverEvent1031 | SessionEvent1032 | ContextEvent1033 | BeforeProviderRequestEvent1034 | BeforeProviderHeadersEvent1035 | AfterProviderResponseEvent1036 | BeforeAgentStartEvent1037 | AgentStartEvent1038 | AgentEndEvent1039 | AgentSettledEvent1040 | TurnStartEvent1041 | TurnEndEvent1042 | MessageStartEvent1043 | MessageUpdateEvent1044 | MessageEndEvent1045 | ToolExecutionStartEvent1046 | ToolExecutionUpdateEvent1047 | ToolExecutionEndEvent1048 | ModelSelectEvent1049 | ThinkingLevelSelectEvent1050 | UserBashEvent1051 | InputEvent1052 | ToolCallEvent1053 | ToolResultEvent;10541055// ============================================================================1056// Event Results1057// ============================================================================10581059export interface ContextEventResult {1060 messages?: AgentMessage[];1061}10621063export type BeforeProviderRequestEventResult = unknown;10641065export interface ToolCallEventResult {1066 /** Block tool execution. To modify arguments, mutate `event.input` in place instead. */1067 block?: boolean;1068 reason?: string;1069}10701071/** Result from user_bash event handler */1072export interface UserBashEventResult {1073 /** Custom operations to use for execution */1074 operations?: BashOperations;1075 /** Full replacement: extension handled execution, use this result */1076 result?: BashResult;1077}10781079export interface ToolResultEventResult {1080 content?: (TextContent | ImageContent)[];1081 details?: unknown;1082 isError?: boolean;1083 usage?: Usage;1084}10851086export interface MessageEndEventResult {1087 /** Replace the finalized message. The replacement must keep the original message role. */1088 message?: AgentMessage;1089}10901091export interface BeforeAgentStartEventResult {1092 message?: Pick<CustomMessage, "customType" | "content" | "display" | "details">;1093 /** Replace the system prompt for this turn. If multiple extensions return this, they are chained. */1094 systemPrompt?: string;1095}10961097export interface SessionBeforeSwitchResult {1098 cancel?: boolean;1099}11001101export interface SessionBeforeForkResult {1102 cancel?: boolean;1103 skipConversationRestore?: boolean;1104}11051106export interface SessionBeforeCompactResult {1107 cancel?: boolean;1108 compaction?: CompactionResult;1109}11101111export interface SessionBeforeTreeResult {1112 cancel?: boolean;1113 summary?: {1114 summary: string;1115 details?: unknown;1116 usage?: Usage;1117 };1118 /** Override custom instructions for summarization */1119 customInstructions?: string;1120 /** Override whether customInstructions replaces the default prompt */1121 replaceInstructions?: boolean;1122 /** Override label to attach to the branch summary entry */1123 label?: string;1124}11251126// ============================================================================1127// Message and Entry Rendering1128// ============================================================================11291130export interface MessageRenderOptions {1131 expanded: boolean;1132 /** Horizontal padding configured by the outputPad setting. */1133 outputPad: number;1134}11351136export interface EntryRenderOptions {1137 expanded: boolean;1138}11391140export type MessageRenderer<T = unknown> = (1141 message: CustomMessage<T>,1142 options: MessageRenderOptions,1143 theme: Theme,1144) => Component | undefined;11451146export type EntryRenderer<T = unknown> = (1147 entry: CustomEntry<T>,1148 options: EntryRenderOptions,1149 theme: Theme,1150) => Component | undefined;11511152// ============================================================================1153// Command Registration1154// ============================================================================11551156export interface RegisteredCommand {1157 name: string;1158 sourceInfo: SourceInfo;1159 description?: string;1160 getArgumentCompletions?: (argumentPrefix: string) => AutocompleteItem[] | null | Promise<AutocompleteItem[] | null>;1161 handler: (args: string, ctx: ExtensionCommandContext) => Promise<void>;1162}11631164export interface ResolvedCommand extends RegisteredCommand {1165 invocationName: string;1166}11671168// ============================================================================1169// Extension API1170// ============================================================================11711172/** Handler function type for events */1173// biome-ignore lint/suspicious/noConfusingVoidType: void allows bare return statements1174export type ExtensionHandler<E, R = undefined> = (event: E, ctx: ExtensionContext) => Promise<R | void> | R | void;11751176/**1177 * ExtensionAPI passed to extension factory functions.1178 */1179export interface ExtensionAPI {1180 // =========================================================================1181 // Event Subscription1182 // =========================================================================11831184 on(event: "project_trust", handler: ProjectTrustHandler): void;1185 on(event: "resources_discover", handler: ExtensionHandler<ResourcesDiscoverEvent, ResourcesDiscoverResult>): void;1186 on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;1187 on(event: "session_info_changed", handler: ExtensionHandler<SessionInfoChangedEvent>): void;1188 on(1189 event: "session_before_switch",1190 handler: ExtensionHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>,1191 ): void;1192 on(event: "session_before_fork", handler: ExtensionHandler<SessionBeforeForkEvent, SessionBeforeForkResult>): void;1193 on(1194 event: "session_before_compact",1195 handler: ExtensionHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>,1196 ): void;1197 on(event: "session_compact", handler: ExtensionHandler<SessionCompactEvent>): void;1198 on(event: "session_shutdown", handler: ExtensionHandler<SessionShutdownEvent>): void;1199 on(event: "session_before_tree", handler: ExtensionHandler<SessionBeforeTreeEvent, SessionBeforeTreeResult>): void;1200 on(event: "session_tree", handler: ExtensionHandler<SessionTreeEvent>): void;1201 on(event: "context", handler: ExtensionHandler<ContextEvent, ContextEventResult>): void;1202 on(1203 event: "before_provider_request",1204 handler: ExtensionHandler<BeforeProviderRequestEvent, BeforeProviderRequestEventResult>,1205 ): void;1206 on(event: "before_provider_headers", handler: ExtensionHandler<BeforeProviderHeadersEvent>): void;1207 on(event: "after_provider_response", handler: ExtensionHandler<AfterProviderResponseEvent>): void;1208 on(event: "before_agent_start", handler: ExtensionHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult>): void;1209 on(event: "agent_start", handler: ExtensionHandler<AgentStartEvent>): void;1210 on(event: "agent_end", handler: ExtensionHandler<AgentEndEvent>): void;1211 on(event: "agent_settled", handler: ExtensionHandler<AgentSettledEvent>): void;1212 on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): void;1213 on(event: "turn_end", handler: ExtensionHandler<TurnEndEvent>): void;1214 on(event: "message_start", handler: ExtensionHandler<MessageStartEvent>): void;1215 on(event: "message_update", handler: ExtensionHandler<MessageUpdateEvent>): void;1216 on(event: "message_end", handler: ExtensionHandler<MessageEndEvent, MessageEndEventResult>): void;1217 on(event: "tool_execution_start", handler: ExtensionHandler<ToolExecutionStartEvent>): void;1218 on(event: "tool_execution_update", handler: ExtensionHandler<ToolExecutionUpdateEvent>): void;1219 on(event: "tool_execution_end", handler: ExtensionHandler<ToolExecutionEndEvent>): void;1220 on(event: "model_select", handler: ExtensionHandler<ModelSelectEvent>): void;1221 on(event: "thinking_level_select", handler: ExtensionHandler<ThinkingLevelSelectEvent>): void;1222 on(event: "tool_call", handler: ExtensionHandler<ToolCallEvent, ToolCallEventResult>): void;1223 on(event: "tool_result", handler: ExtensionHandler<ToolResultEvent, ToolResultEventResult>): void;1224 on(event: "user_bash", handler: ExtensionHandler<UserBashEvent, UserBashEventResult>): void;1225 on(event: "input", handler: ExtensionHandler<InputEvent, InputEventResult>): void;12261227 // =========================================================================1228 // Tool Registration1229 // =========================================================================12301231 /** Register a tool that the LLM can call. */1232 registerTool<TParams extends TSchema = TSchema, TDetails = unknown, TState = any>(1233 tool: ToolDefinition<TParams, TDetails, TState>,1234 ): void;12351236 // =========================================================================1237 // Command, Shortcut, Flag Registration1238 // =========================================================================12391240 /** Register a custom command. */1241 registerCommand(name: string, options: Omit<RegisteredCommand, "name" | "sourceInfo">): void;12421243 /** Register a keyboard shortcut. */1244 registerShortcut(1245 shortcut: KeyId,1246 options: {1247 description?: string;1248 handler: (ctx: ExtensionContext) => Promise<void> | void;1249 },1250 ): void;12511252 /** Register a CLI flag. */1253 registerFlag(1254 name: string,1255 options: {1256 description?: string;1257 type: "boolean" | "string";1258 default?: boolean | string;1259 },1260 ): void;12611262 /** Get the value of a registered CLI flag. */1263 getFlag(name: string): boolean | string | undefined;12641265 // =========================================================================1266 // Message Rendering1267 // =========================================================================12681269 /** Register a custom renderer for CustomMessageEntry. */1270 registerMessageRenderer<T = unknown>(customType: string, renderer: MessageRenderer<T>): void;12711272 /** Register a custom renderer for CustomEntry. Custom entries do not participate in LLM context. */1273 registerEntryRenderer<T = unknown>(customType: string, renderer: EntryRenderer<T>): void;12741275 // =========================================================================1276 // Actions1277 // =========================================================================12781279 /** Send a custom message to the session. */1280 sendMessage<T = unknown>(1281 message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,1282 options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },1283 ): void;12841285 /**1286 * Send a user message to the agent. Always triggers a turn.1287 * When the agent is streaming, use deliverAs to specify how to queue the message.1288 */1289 sendUserMessage(1290 content: string | (TextContent | ImageContent)[],1291 options?: { deliverAs?: "steer" | "followUp" },1292 ): void;12931294 /** Append a custom entry to the session for state persistence (not sent to LLM). */1295 appendEntry<T = unknown>(customType: string, data?: T): void;12961297 // =========================================================================1298 // Session Metadata1299 // =========================================================================13001301 /** Set the session display name (shown in session selector). */1302 setSessionName(name: string): void;13031304 /** Get the current session name, if set. */1305 getSessionName(): string | undefined;13061307 /** Set or clear a label on an entry. Labels are user-defined markers for bookmarking/navigation. */1308 setLabel(entryId: string, label: string | undefined): void;13091310 /** Execute a shell command. */1311 exec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;13121313 /** Get the list of currently active tool names. */1314 getActiveTools(): string[];13151316 /** Get all configured tools with parameter schema, prompt guidelines, and source metadata. */1317 getAllTools(): ToolInfo[];13181319 /** Set the active tools by name. */1320 setActiveTools(toolNames: string[]): void;13211322 /** Get available slash commands in the current session. */1323 getCommands(): SlashCommandInfo[];13241325 // =========================================================================1326 // Model and Thinking Level1327 // =========================================================================13281329 /** Set the current model. Returns false if no API key available. */1330 setModel(model: Model<any>): Promise<boolean>;13311332 /** Get current thinking level. */1333 getThinkingLevel(): ThinkingLevel;13341335 /** Set thinking level (clamped to model capabilities). */1336 setThinkingLevel(level: ThinkingLevel): void;13371338 // =========================================================================1339 // Provider Registration1340 // =========================================================================13411342 /**1343 * Register or override a model provider.1344 *1345 * If `models` is provided: replaces all existing models for this provider.1346 * If only `baseUrl` is provided: overrides the URL for existing models.1347 * If `oauth` is provided: registers OAuth provider for /login support.1348 * If `streamSimple` is provided: registers a custom API stream handler.1349 *1350 * During initial extension load this call is queued and applied once the1351 * runner has bound its context. After that it takes effect immediately, so1352 * it is safe to call from command handlers or event callbacks without1353 * requiring a `/reload`.1354 *1355 * @example1356 * // Register a new provider with custom models1357 * pi.registerProvider("my-proxy", {1358 * baseUrl: "https://proxy.example.com",1359 * apiKey: "$PROXY_API_KEY",1360 * api: "anthropic-messages",1361 * models: [1362 * {1363 * id: "claude-sonnet-4-20250514",1364 * name: "Claude 4 Sonnet (proxy)",1365 * reasoning: false,1366 * input: ["text", "image"],1367 * cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },1368 * contextWindow: 200000,1369 * maxTokens: 163841370 * }1371 * ]1372 * });1373 *1374 * @example1375 * // Override baseUrl for an existing provider1376 * pi.registerProvider("anthropic", {1377 * baseUrl: "https://proxy.example.com"1378 * });1379 *1380 * @example1381 * // Register provider with OAuth support1382 * pi.registerProvider("corporate-ai", {1383 * baseUrl: "https://ai.corp.com",1384 * api: "openai-responses",1385 * models: [...],1386 * oauth: {1387 * name: "Corporate AI (SSO)",1388 * async login(callbacks) { ... },1389 * async refreshToken(credentials) { ... },1390 * getApiKey(credentials) { return credentials.access; }1391 * }1392 * });1393 */1394 registerProvider(provider: Provider): void;1395 registerProvider(name: string, config: ProviderConfig): void;13961397 /**1398 * Unregister a previously registered provider.1399 *1400 * Removes all models belonging to the named provider and restores any1401 * built-in models that were overridden by it. Has no effect if the provider1402 * is not currently registered.1403 *1404 * Like `registerProvider`, this takes effect immediately when called after1405 * the initial load phase.1406 *1407 * @example1408 * pi.unregisterProvider("my-proxy");1409 */1410 unregisterProvider(name: string): void;14111412 /** Shared event bus for extension communication. */1413 events: EventBus;1414}14151416// ============================================================================1417// Provider Registration Types1418// ============================================================================14191420/** Configuration for registering a provider via pi.registerProvider(). */1421export interface ProviderConfig {1422 /** Display name for the provider in UI. */1423 name?: string;1424 /** Base URL for the API endpoint. Required when defining models. */1425 baseUrl?: string;1426 /** API key literal, env interpolation ($ENV_VAR or ${ENV_VAR}), or leading !command. Required when defining models (unless oauth provided). */1427 apiKey?: string;1428 /** API type. Required at provider or model level when defining models. */1429 api?: Api;1430 /** Optional streamSimple handler for custom APIs. */1431 streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;1432 /** Custom headers to include in requests. */1433 headers?: Record<string, string>;1434 /** If true, adds Authorization: Bearer header with the resolved API key. */1435 authHeader?: boolean;1436 /** Models to register. If provided, replaces all existing models for this provider. */1437 models?: ProviderModelConfig[];1438 /**1439 * Refresh this provider's model list. The returned list replaces extension-provided models.1440 * Use context.store explicitly when the catalog should persist across sessions.1441 */1442 refreshModels?(context: RefreshModelsContext): Promise<ProviderModelConfig[]>;1443 /** OAuth provider for /login support. The `id` is set automatically from the provider name. */1444 oauth?: {1445 /** Display name for the provider in login UI. */1446 name: string;1447 /** @deprecated Retained for source compatibility; canonical auth flows ignore it. */1448 usesCallbackServer?: boolean;1449 /** Run the login flow, return credentials to persist. */1450 login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;1451 /** Refresh expired credentials, return updated credentials to persist. */1452 refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials>;1453 /** Convert credentials to API key string for the provider. */1454 getApiKey(credentials: OAuthCredentials): string;1455 /** Legacy synchronous credential-dependent model projection. */1456 modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];1457 };1458}14591460/** Configuration for a model within a provider. */1461export interface ProviderModelConfig {1462 /** Model ID (e.g., "claude-sonnet-4-20250514"). */1463 id: string;1464 /** Display name (e.g., "Claude 4 Sonnet"). */1465 name: string;1466 /** API type override for this model. */1467 api?: Api;1468 /** API endpoint URL override for this model. */1469 baseUrl?: string;1470 /** Whether the model supports extended thinking. */1471 reasoning: boolean;1472 /** Maps pi thinking levels to provider/model-specific values; null marks a level unsupported. */1473 thinkingLevelMap?: Model<Api>["thinkingLevelMap"];1474 /** Supported input types. */1475 input: ("text" | "image")[];1476 /** Per-million-token cost rates and optional request-wide input pricing tiers. */1477 cost: Model<Api>["cost"];1478 /** Maximum context window size in tokens. */1479 contextWindow: number;1480 /** Maximum output tokens. */1481 maxTokens: number;1482 /** Custom headers for this model. */1483 headers?: Record<string, string>;1484 /** OpenAI compatibility settings. */1485 compat?: Model<Api>["compat"];1486}14871488/** Extension factory function type. Supports both sync and async initialization. */1489export type ExtensionFactory = (pi: ExtensionAPI) => void | Promise<void>;14901491export type InlineExtension =1492 | ExtensionFactory1493 | {1494 /** Display name shown as `<inline:name>` in the startup Extensions list. */1495 name: string;1496 factory: ExtensionFactory;1497 /** Omit this extension from the startup Extensions list. */1498 hidden?: boolean;1499 };15001501// ============================================================================1502// Loaded Extension Types1503// ============================================================================15041505export interface RegisteredTool {1506 definition: ToolDefinition;1507 sourceInfo: SourceInfo;1508}15091510export interface ExtensionFlag {1511 name: string;1512 description?: string;1513 type: "boolean" | "string";1514 default?: boolean | string;1515 extensionPath: string;1516}15171518export interface ExtensionShortcut {1519 shortcut: KeyId;1520 description?: string;1521 handler: (ctx: ExtensionContext) => Promise<void> | void;1522 extensionPath: string;1523}15241525type HandlerFn = (...args: unknown[]) => Promise<unknown>;15261527export type SendMessageHandler = <T = unknown>(1528 message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,1529 options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },1530) => void;15311532export type SendUserMessageHandler = (1533 content: string | (TextContent | ImageContent)[],1534 options?: { deliverAs?: "steer" | "followUp" },1535) => void;15361537export type AppendEntryHandler = <T = unknown>(customType: string, data?: T) => void;15381539export type SetSessionNameHandler = (name: string) => void;15401541export type GetSessionNameHandler = () => string | undefined;15421543export type GetActiveToolsHandler = () => string[];15441545/** Tool info with name, description, parameter schema, prompt guidelines, and source metadata. */1546export type ToolInfo = Pick<ToolDefinition, "name" | "description" | "parameters" | "promptGuidelines"> & {1547 sourceInfo: SourceInfo;1548};15491550export type GetAllToolsHandler = () => ToolInfo[];15511552export type GetCommandsHandler = () => SlashCommandInfo[];15531554export type SetActiveToolsHandler = (toolNames: string[]) => void;15551556export type RefreshToolsHandler = () => void;15571558export type SetModelHandler = (model: Model<any>) => Promise<boolean>;15591560export type GetThinkingLevelHandler = () => ThinkingLevel;15611562export type SetThinkingLevelHandler = (level: ThinkingLevel) => void;15631564export type SetLabelHandler = (entryId: string, label: string | undefined) => void;15651566/**1567 * Shared state created by loader, used during registration and runtime.1568 * Contains flag values (defaults set during registration, CLI values set after).1569 */1570export interface ExtensionRuntimeState {1571 flagValues: Map<string, boolean | string>;1572 /** Legacy provider-config registrations queued during extension loading, processed when runner binds. */1573 pendingProviderRegistrations: Array<{ name: string; config: ProviderConfig; extensionPath: string }>;1574 /** Native pi-ai provider registrations queued during extension loading, processed when runner binds. */1575 pendingNativeProviderRegistrations: Array<{ provider: Provider; extensionPath: string }>;1576 /** Throws when this extension instance is stale after runtime replacement. */1577 assertActive: () => void;1578 /** Marks this extension instance as stale after runtime replacement or reload. */1579 invalidate: (message?: string) => void;1580 /**1581 * Register or unregister a provider.1582 *1583 * Before bindCore(): queues registrations / removes from queue.1584 * After bindCore(): calls ModelRegistry directly for immediate effect.1585 */1586 registerProvider: (name: string, config: ProviderConfig, extensionPath?: string) => void;1587 registerNativeProvider: (provider: Provider, extensionPath?: string) => void;1588 unregisterProvider: (name: string, extensionPath?: string) => void;1589}15901591/**1592 * Action implementations for pi.* API methods.1593 * Provided to runner.initialize(), copied into the shared runtime.1594 */1595export interface ExtensionActions {1596 sendMessage: SendMessageHandler;1597 sendUserMessage: SendUserMessageHandler;1598 appendEntry: AppendEntryHandler;1599 setSessionName: SetSessionNameHandler;1600 getSessionName: GetSessionNameHandler;1601 setLabel: SetLabelHandler;1602 getActiveTools: GetActiveToolsHandler;1603 getAllTools: GetAllToolsHandler;1604 setActiveTools: SetActiveToolsHandler;1605 refreshTools: RefreshToolsHandler;1606 getCommands: GetCommandsHandler;1607 setModel: SetModelHandler;1608 getThinkingLevel: GetThinkingLevelHandler;1609 setThinkingLevel: SetThinkingLevelHandler;1610}16111612/**1613 * Actions for ExtensionContext (ctx.* in event handlers).1614 * Required by all modes.1615 */1616export interface ExtensionContextActions {1617 getModel: () => Model<any> | undefined;1618 isIdle: () => boolean;1619 isProjectTrusted: () => boolean;1620 getSignal: () => AbortSignal | undefined;1621 abort: () => void;1622 hasPendingMessages: () => boolean;1623 shutdown: () => void;1624 getContextUsage: () => ContextUsage | undefined;1625 compact: (options?: CompactOptions) => void;1626 getSystemPrompt: () => string;1627 getSystemPromptOptions?: () => BuildSystemPromptOptions;1628}16291630/**1631 * Actions for ExtensionCommandContext (ctx.* in command handlers).1632 * Only needed for interactive mode where extension commands are invokable.1633 */1634export interface ExtensionCommandContextActions {1635 waitForIdle: () => Promise<void>;1636 newSession: (options?: {1637 parentSession?: string;1638 setup?: (sessionManager: SessionManager) => Promise<void>;1639 withSession?: (ctx: ReplacedSessionContext) => Promise<void>;1640 }) => Promise<{ cancelled: boolean }>;1641 fork: (1642 entryId: string,1643 options?: { position?: "before" | "at"; withSession?: (ctx: ReplacedSessionContext) => Promise<void> },1644 ) => Promise<{ cancelled: boolean }>;1645 navigateTree: (1646 targetId: string,1647 options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string },1648 ) => Promise<{ cancelled: boolean }>;1649 switchSession: (1650 sessionPath: string,1651 options?: { withSession?: (ctx: ReplacedSessionContext) => Promise<void> },1652 ) => Promise<{ cancelled: boolean }>;1653 reload: () => Promise<void>;1654}16551656/**1657 * Full runtime = state + actions.1658 * Created by loader with throwing action stubs, completed by runner.initialize().1659 */1660export interface ExtensionRuntime extends ExtensionRuntimeState, ExtensionActions {}16611662/** Loaded extension with all registered items. */1663export interface Extension {1664 path: string;1665 resolvedPath: string;1666 hidden?: boolean;1667 sourceInfo: SourceInfo;1668 handlers: Map<string, HandlerFn[]>;1669 tools: Map<string, RegisteredTool>;1670 messageRenderers: Map<string, MessageRenderer>;1671 entryRenderers?: Map<string, EntryRenderer>;1672 commands: Map<string, RegisteredCommand>;1673 flags: Map<string, ExtensionFlag>;1674 shortcuts: Map<KeyId, ExtensionShortcut>;1675}16761677/** Result of loading extensions. */1678export interface LoadExtensionsResult {1679 extensions: Extension[];1680 errors: Array<{ path: string; error: string }>;1681 /** Shared runtime - actions are throwing stubs until runner.initialize() */1682 runtime: ExtensionRuntime;1683}16841685// ============================================================================1686// Extension Error1687// ============================================================================16881689export interface ExtensionError {1690 extensionPath: string;1691 event: string;1692 error: string;1693 stack?: string;1694}1695