/** * Extension system types. * * Extensions are TypeScript modules that can: * - Subscribe to agent lifecycle events * - Register LLM-callable tools * - Register commands, keyboard shortcuts, and CLI flags * - Interact with the user via UI primitives */ import type { AgentFailureDiagnostic, AgentMessage, AgentToolResult, AgentToolUpdateCallback, RunSettlementProof, ThinkingLevel } from "@gajae-code/agent-core"; import type { AttemptScope } from "@gajae-code/agent-core/attempt-scope"; import type { CompactionResult } from "@gajae-code/agent-core/compaction"; import type { Api, AssistantMessageEvent, AssistantMessageEventStream, Context, ImageContent, Model, ProviderResponseMetadata, SimpleStreamOptions, Static, TextContent, Tool, TSchema, UsageReport } from "@gajae-code/ai/core"; import type { OAuthCredentials, OAuthLoginCallbacks } from "@gajae-code/ai/utils/oauth/types"; import type * as piCodingAgent from "@gajae-code/coding-agent"; import type { AutocompleteItem, Component, EditorTheme, KeyId, TUI } from "@gajae-code/tui"; import type { KeybindingsManager } from "../../config/keybindings"; import type { ModelRegistry } from "../../config/model-registry"; import type { ModelSelectorValue } from "../../config/model-selector-value"; import type { Settings } from "../../config/settings"; import type { SettingPath, SettingValue } from "../../config/settings-schema"; import type { EditToolDetails } from "../../edit"; import type { PythonResult } from "../../eval/py/executor"; import type { BashResult } from "../../exec/bash-executor"; import type { ExecOptions, ExecResult } from "../../exec/exec"; import type { CustomEditor } from "../../modes/components/custom-editor"; import type { WorkflowGateEmitter } from "../../modes/shared/agent-wire/workflow-gate-broker"; import type { Theme } from "../../modes/theme/theme"; import type { ClientBridge, ClientBridgePermissionOption, ClientBridgePermissionOutcome, ClientBridgePermissionToolCall } from "../../session/client-bridge"; import type { CustomMessage } from "../../session/messages"; import type { ReadonlySessionManager, SessionManager } from "../../session/session-manager"; import type { BashToolDetails, BashToolInput, FindToolDetails, FindToolInput, ReadToolDetails, ReadToolInput, SearchToolDetails, SearchToolInput, WriteToolInput } from "../../tools"; import type { EventBus } from "../../utils/event-bus"; import type { AgentEndEvent, AutoCompactionEndEvent, AutoCompactionStartEvent, AutoRetryEndEvent, AutoRetryStartEvent, ContextEvent, GoalUpdatedEvent, SessionBeforeBranchEvent, SessionBeforeBranchResult, SessionBeforeCompactEvent, SessionBeforeCompactResult, SessionBeforeSwitchEvent, SessionBeforeSwitchResult, SessionBeforeTreeEvent, SessionBeforeTreeResult, SessionBranchEvent, SessionCompactEvent, SessionCompactingEvent, SessionCompactingResult, SessionEvent, SessionShutdownEvent, SessionStartEvent, SessionSwitchEvent, SessionTreeEvent, AgentStartEvent as SharedAgentStartEvent, TodoReminderEvent, ToolCallEventResult, ToolResultEventResult, TtsrTriggeredEvent, TurnEndEvent, TurnStartEvent } from "../shared-events"; import type { SlashCommandInfo } from "../slash-commands"; export type { AppKeybinding, KeybindingsManager } from "../../config/keybindings"; export type { ExecOptions, ExecResult } from "../../exec/exec"; export type { AgentToolResult, AgentToolUpdateCallback }; /** * UI dialog options for extensions. */ export interface ExtensionUIDialogOptions { signal?: AbortSignal; timeout?: number; /** Invoked when the UI times out while waiting for a selection/input */ onTimeout?: () => void; /** Initial cursor position for select dialogs (0-indexed) */ initialIndex?: number; /** Render an outlined list for select dialogs */ outline?: boolean; /** Invoked when user presses left arrow in select dialogs */ onLeft?: () => void; /** Invoked when user presses right arrow in select dialogs */ onRight?: () => void; /** Invoked when user presses the external editor shortcut in select dialogs */ onExternalEditor?: () => void; /** Optional footer hint text rendered by interactive selector */ helpText?: string; /** * For interactive TUI select dialogs, render the focused option across * multiple rows instead of truncating it. This is a select-only rendering * hint; non-TUI clients (SDK, ACP) drop it and do not serialize it. */ wrapFocused?: boolean; /** * For interactive TUI select dialogs, cap the title/prompt area to this * many rows and let PageUp/PageDown scroll that prompt locally. This is a * select-only rendering hint; non-TUI bridges drop it and do not serialize it. */ scrollTitleRows?: number; /** * For interactive TUI select dialogs, handle the option with `optionLabel` * inline: selecting it keeps the title and option list on screen and opens * a free-text input below the list. Submitting calls `onSubmit` with the * typed text and resolves the select with `optionLabel`; Escape returns to * option selection. Non-TUI clients (SDK, ACP) drop it; callers must keep * a fallback path for selects that resolve `optionLabel` without invoking * `onSubmit`. */ customInput?: { optionLabel: string; onSubmit: (text: string) => void; }; /** * Inline free-text input for a non-answer clarification action. It is * separate from `customInput` so callers can distinguish "ask about these * choices" from "provide my own answer". Empty/whitespace-only submissions * may be rejected by the selector when `allowEmpty` is false. */ clarificationInput?: { optionLabel: string; onSubmit: (text: string) => void; allowEmpty?: boolean; }; } /** Raw terminal input listener for extensions. */ export type TerminalInputHandler = (data: string) => { consume?: boolean; data?: string; } | undefined; export type WidgetPlacement = "aboveEditor" | "belowEditor"; export interface ExtensionWidgetOptions { placement?: WidgetPlacement; } export type ExtensionUiComponent = Component & { dispose?(): void; }; export type ExtensionUiComponentFactory = (tui: TUI, theme: Theme) => ExtensionUiComponent; export type ExtensionWidgetContent = string[] | ExtensionUiComponentFactory | undefined; /** * UI context for extensions to request interactive UI. * Interactive, SDK, and headless callers provide their own implementation. */ export interface ExtensionUIContext { /** Show a selector and return the user's choice. */ select(title: string, options: string[], dialogOptions?: ExtensionUIDialogOptions): Promise; /** Show a confirmation dialog. */ confirm(title: string, message: string, dialogOptions?: ExtensionUIDialogOptions): Promise; /** Show a text input dialog. */ input(title: string, placeholder?: string, dialogOptions?: ExtensionUIDialogOptions): Promise; /** Show a notification to the user. */ notify(message: string, type?: "info" | "warning" | "error"): void; /** Listen to raw terminal input (interactive mode only). Returns an unsubscribe function. */ onTerminalInput(handler: TerminalInputHandler): () => void; /** Set status text in the footer/status bar. Pass undefined to clear. */ setStatus(key: string, text: string | undefined): void; /** Set the working/loading message shown during streaming. Call with no argument to restore default. */ setWorkingMessage(message?: string): void; /** Set a widget to display above or below the editor. Accepts string array or component factory. */ setWidget(key: string, content: ExtensionWidgetContent, options?: ExtensionWidgetOptions): void; /** Set a custom footer component, or undefined to restore the built-in footer. */ setFooter(factory: ExtensionUiComponentFactory | undefined): void; /** Set a custom header component, or undefined to restore the built-in header. */ setHeader(factory: ExtensionUiComponentFactory | undefined): void; /** Set the terminal window/tab title. */ setTitle(title: string): void; /** Show a custom component with keyboard focus. */ custom(factory: (tui: TUI, theme: Theme, keybindings: KeybindingsManager, done: (result: T) => void) => ExtensionUiComponent | Promise, options?: { overlay?: boolean; }): Promise; /** Set the text in the core input editor. */ setEditorText(text: string): void; /** * Paste text into the core input editor. * * Interactive mode should route through the editor's paste handling (e.g. large paste markers). * Non-interactive modes may fall back to replacing the editor text. */ pasteToEditor(text: string): void; /** Get the current text from the core input editor. */ getEditorText(): string; /** Show a multi-line editor for text editing. */ editor(title: string, prefill?: string, dialogOptions?: ExtensionUIDialogOptions, editorOptions?: { promptStyle?: boolean; }): Promise; /** * Set a custom editor component via factory function, or `undefined` to restore the default editor. * * The factory must return a {@link CustomEditor} subclass. Plain `EditorComponent`/`Editor` * instances do not implement the action-keys, escape callbacks, and custom-key-handler surface * required by interactive mode. */ setEditorComponent(factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => CustomEditor) | undefined): void; /** Get the current theme for styling. */ readonly theme: Theme; /** Get all available themes with names and paths. */ getAllThemes(): Promise<{ name: string; path: string | undefined; }[]>; /** Load a theme by name without switching to it. */ getTheme(name: string): Promise; /** Set the current theme by name or Theme object. */ setTheme(theme: string | Theme): Promise<{ success: boolean; error?: string; }>; /** Get current tool output expansion state. */ getToolsExpanded(): boolean; /** * Set tool output expansion state. This is an explicit fold choice, pinning * existing tool and read components for their renderer instance lifetime, * the same as the user shortcut. */ setToolsExpanded(expanded: boolean): void; } export interface ExtensionTranscriptEntry { id: string; role: string; textSummary: string; ts: string; body?: string; /** * Durable, image-free message blocks used by rich transcript consumers such * as ACP replay. Binary image payloads intentionally remain unavailable. */ content?: Array<{ type: "text"; text: string; } | { type: "thinking"; thinking: string; } | { type: "toolCall"; id: string; name: string; arguments: unknown; }>; toolCallId?: string; toolName?: string; isError?: boolean; } export interface ContextUsage { /** Context tokens, or null if unknown (e.g. right after compaction, before next LLM response). */ tokens: number | null; contextWindow: number; /** Context usage as percentage of context window, or null if tokens is unknown. */ percent: number | null; /** Provenance: "provider_anchor" = anchored on provider-reported usage (+ heuristic trailing delta); "heuristic" = no provider anchor available, full estimate; "unknown" = post-compaction, no post-compaction provider response yet. */ source: "provider_anchor" | "heuristic" | "unknown"; } export interface CompactOptions { onComplete?: (result: CompactionResult) => void; onError?: (error: Error) => void; } export interface ExtensionSessionMetadata { kind: "main" | "sub"; taskDepth: number; parentTaskPrefix?: string; currentAgentType?: string; } /** Non-sensitive session policy exposed to third-party extensions. */ export interface ExtensionSettings { /** Read an allowlisted non-sensitive setting; blocked secret paths return undefined. */ get

(path: P): SettingValue

| undefined; getModelRole(role: string): ModelSelectorValue | undefined; } /** Create an extension-safe settings facade without exposing credentials or mutation APIs. */ export declare function createExtensionSettings(settings: Pick & Pick): ExtensionSettings; /** * Preserve the historical Settings-shaped custom-tool contract without exposing * mutation APIs or unrestricted settings reads at runtime. */ export declare function createCustomToolSettings(settings: Settings | ExtensionSettings): Settings; /** * Context passed to extension event handlers. */ export interface ExtensionContext { /** UI methods for user interaction */ ui: ExtensionUIContext; /** Get current context usage for the active model. */ getContextUsage(): ContextUsage | undefined; /** Compact the session context (interactive mode shows UI). */ compact(instructionsOrOptions?: string | CompactOptions): Promise; /** Whether an interactive UI is available */ hasUI: boolean; /** Current working directory */ cwd: string; /** Aborted when the runner stops waiting for this handler, including handler timeout. */ signal?: AbortSignal; /** Session manager (read-only) */ sessionManager: ReadonlySessionManager; /** Session classification supplied by the SDK for extension policy decisions. */ sessionMetadata?: ExtensionSessionMetadata; /** Model registry for API key resolution */ modelRegistry: ModelRegistry; /** Non-sensitive session settings for role resolution. */ settings?: ExtensionSettings; /** Credential-selection identity, distinct from logical/provider cache identity. */ credentialSessionId?: string; /** Current model (may be undefined) */ model: Model | undefined; /** Whether the agent is idle (not streaming) */ isIdle(): boolean; /** Stable resource ownership identifier for the active prompt run. */ getActivePromptHandle(): string | undefined; /** Abort the current agent operation */ abort(): void | Promise; /** Abort and prove whether resources for a specific prompt settled. */ abortPromptAndWait?(handle: string, options: { graceMs: number; }): Promise; /** Whether there are queued messages waiting */ hasPendingMessages(): boolean; /** Typed pending-message counts per queue (steering, follow-up, next-turn). */ getPendingMessageCounts(): { steering: number; followUp: number; nextTurn: number; }; /** Read-only session data exposed to extensions and the SDK host. */ getTranscript(): ExtensionTranscriptEntry[]; getTranscriptBody(entryId: string): string | undefined; getGoalState(): unknown; getTodoState(): unknown; getQueuedMessages(): unknown[]; getActiveTools(): string[]; getAllTools(): string[]; /** Resolve display-safe metadata for a configured tool without exposing its implementation. */ resolveTool(name: string): Pick | undefined; /** Session control seams used by the SDK host. */ cycleModel(): Promise<{ model: Model; thinkingLevel: ThinkingLevel | undefined; } | undefined>; setModelProfile?(name: string): Promise; /** Persist a model profile as the global default (SDK host seam). */ setDefaultModelProfile?(name: string, options?: { persistDefault?: boolean; thinkingLevelOverride?: ThinkingLevel; /** Internal SDK host hooks invoked inside the profile activation admission. */ onBeforeActivation?: () => void; onAfterActivation?: () => void; }): Promise; /** The in-session active-profile marker; sole source of logical current state. */ getActiveModelProfile?(): string | undefined; /** Run a control-surface mutation inside the session admission boundary. */ withSdkControlMutation?(body: () => Promise): Promise; cycleThinkingLevel(): ThinkingLevel | undefined; setQueueMode(kind: "steering" | "follow_up" | "interrupt", mode: unknown): boolean; getSkillState(): unknown; getConfigItems(): unknown; getBranchCandidates(): unknown; getExtensions(): unknown; getArtifact(id: string): Uint8Array | string | undefined | Promise; getArtifactRange?(id: string, offset: number, length: number): { bytes: Uint8Array; totalBytes: number; } | undefined | Promise<{ bytes: Uint8Array; totalBytes: number; } | undefined>; getJobs(): unknown; /** Typed skill and mode controls exposed to the SDK host. */ invokeSkill?(name: string, args?: string, options?: { onPreflightAccepted?: () => void; onPreflightAcceptCommit?: () => void | Promise; onSkillPrepared?: (meta: { name: string; path: string; lineCount?: number; cleanedArgs?: string; }) => void; preflightSignal?: AbortSignal; }): Promise; setPlanMode?(on: boolean): unknown; operateGoal?(op: "create" | "get" | "resume" | "pause" | "complete" | "drop", objective?: string): Promise; /** Typed nonvisual session controls exposed to the SDK host. */ sdkControl?(operation: string, input: Record): unknown | Promise; /** Install a permission callback backed by a live SDK reverse provider lease. */ setSdkPermissionProvider?(provider: ((toolCall: ClientBridgePermissionToolCall, options: ClientBridgePermissionOption[], signal?: AbortSignal) => Promise) | undefined): void; /** Install a client bridge backed by a live SDK reverse provider lease. */ setSdkClientBridge?(bridge: ClientBridge | undefined): void; /** Names of session SDK seams actually installed by the active runtime. */ sdkBindings?(): readonly string[]; /** Gracefully shutdown and exit. */ shutdown(): void; /** Get the current effective system prompt. */ getSystemPrompt(): string[]; /** @deprecated Use hasPendingMessages() instead */ hasQueuedMessages(): boolean; /** SDK workflow-gate bridge, when a session has a remote gate responder. */ workflowGate?: WorkflowGateEmitter; /** Clear the active conversation while preserving the saved session identity. */ clearContext(): Promise; } /** * Extended context for command handlers. * Includes session control methods only safe in user-initiated commands. */ export interface ExtensionCommandContext extends ExtensionContext { /** Get current context usage for the active model. */ getContextUsage(): ContextUsage | undefined; /** Wait for the agent to finish streaming */ waitForIdle(): Promise; /** Start a new session, optionally with initialization. */ newSession(options?: { parentSession?: string; setup?: (sessionManager: SessionManager) => Promise; }): Promise<{ cancelled: boolean; }>; /** Branch from a specific entry, creating a new session file. */ branch(entryId: string): Promise<{ cancelled: boolean; }>; /** Navigate to a different point in the session tree. */ navigateTree(targetId: string, options?: { summarize?: boolean; }): Promise<{ cancelled: boolean; }>; /** Switch to a different session file. */ switchSession(sessionPath: string): Promise<{ cancelled: boolean; }>; /** Reload the current session/runtime state. */ reload(): Promise; /** Compact the session context (interactive mode shows UI). */ compact(instructionsOrOptions?: string | CompactOptions): Promise; } /** Rendering options for tool results */ export interface ToolRenderResultOptions { /** Whether the result view is expanded */ expanded: boolean; /** Whether this is a partial/streaming result */ isPartial: boolean; /** Current spinner frame index for animated elements (optional) */ spinnerFrame?: number; } /** Session event for tool onSession lifecycle */ export interface ToolSessionEvent { /** Reason for the session event */ reason: "start" | "switch" | "branch" | "tree" | "shutdown"; /** Previous session file path, or undefined for "start" and "shutdown" */ previousSessionFile: string | undefined; } /** * Tool definition for registerTool(). */ export interface ToolDefinition { /** Tool name (used in LLM tool calls) */ name: string; /** Human-readable label for UI */ label: string; /** Description for LLM */ description: string; /** Parameter schema (Zod, or TypeBox for legacy/extension compat). */ parameters: TParams; /** Tool scheduling mode; exclusive tools do not run in parallel with other tools in a model turn. */ concurrency?: "shared" | "exclusive"; /** If true, tool is excluded unless explicitly listed in --tools or agent's tools field */ hidden?: boolean; /** If true, tool is registered but not auto-included in the initial active set. * The registering extension is responsible for activating/deactivating it via setActiveTools(). */ defaultInactive?: boolean; /** If true, tool may stage deferred changes that require explicit resolve/discard. */ deferrable?: boolean; /** MCP server name for discovery/search metadata when this tool fronts an MCP server. */ mcpServerName?: string; /** Original MCP tool name for discovery/search metadata. */ mcpToolName?: string; /** Execute the tool. */ execute(toolCallId: string, params: Static, signal: AbortSignal | undefined, onUpdate: AgentToolUpdateCallback | undefined, ctx: ExtensionContext): Promise>; /** Called on session lifecycle events - use to reconstruct state or cleanup resources */ onSession?: (event: ToolSessionEvent, ctx: ExtensionContext) => void | Promise; /** Custom rendering for tool call display */ renderCall?: (args: Static, options: ToolRenderResultOptions, theme: Theme) => Component; /** Custom rendering for tool result display */ renderResult?: (result: AgentToolResult, options: ToolRenderResultOptions, theme: Theme, args?: Static) => Component; } /** Fired after session_start to allow extensions to provide additional resource paths. */ export interface ResourcesDiscoverEvent { type: "resources_discover"; cwd: string; reason: "startup" | "reload"; } /** Result from resources_discover event handler */ export interface ResourcesDiscoverResult { skillPaths?: string[]; promptPaths?: string[]; themePaths?: string[]; } export type { SessionBeforeBranchEvent, SessionBeforeCompactEvent, SessionBeforeSwitchEvent, SessionBeforeTreeEvent, SessionBranchEvent, SessionCompactEvent, SessionCompactingEvent, SessionEvent, SessionShutdownEvent, SessionStartEvent, SessionSwitchEvent, SessionTreeEvent, TreePreparation, } from "../shared-events"; export type { ContextEvent } from "../shared-events"; /** Fired before a provider request is sent. Can replace the payload. */ export interface BeforeProviderRequestEvent { type: "before_provider_request"; payload: unknown; } /** Fired after a provider response is received, before its stream body is consumed. */ export interface AfterProviderResponseEvent extends ProviderResponseMetadata { type: "after_provider_response"; } /** Fired after user submits prompt but before agent loop. */ export interface BeforeAgentStartEvent { type: "before_agent_start"; prompt: string; images?: ImageContent[]; systemPrompt: string[]; } export type { AgentEndEvent, TurnEndEvent, TurnStartEvent } from "../shared-events"; /** Fired when an agent loop starts. `sdkRunToken` is an internal SDK queue-owner binding. */ export interface AgentStartEvent extends SharedAgentStartEvent { sdkRunToken?: string; } /** Fired when an agent run fails before emitting agent_end. The error is the * sanitized `{ code, message }` diagnostic from the agent runtime — never the raw * provider error. */ export interface AgentFailedEvent { type: "agent_failed"; error: AgentFailureDiagnostic; /** Attempt correlation for the failing run, when scoped (matches the * agent_start/agent_end scope contract). */ scope?: AttemptScope; } /** Fired when a message starts (user, assistant, or toolResult) */ export interface MessageStartEvent { type: "message_start"; message: AgentMessage; } /** Fired during assistant message streaming with token-by-token updates */ export interface MessageUpdateEvent { type: "message_update"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent; } export interface ReasoningSummaryStartEvent { type: "reasoning_summary_start"; message: AgentMessage; contentIndex: number; } export interface ReasoningSummaryDeltaEvent { type: "reasoning_summary_delta"; message: AgentMessage; contentIndex: number; delta: string; } export interface ReasoningSummaryEndEvent { type: "reasoning_summary_end"; message: AgentMessage; contentIndex: number; content: string; } /** Fired when a message ends */ export interface MessageEndEvent { type: "message_end"; message: AgentMessage; } /** Fired when a tool starts executing */ export interface ToolExecutionStartEvent { type: "tool_execution_start"; toolCallId: string; toolName: string; args: unknown; intent?: string; } /** Fired during tool execution with partial/streaming output */ export interface ToolExecutionUpdateEvent { type: "tool_execution_update"; toolCallId: string; toolName: string; args: unknown; partialResult: unknown; } /** Fired when a tool finishes executing */ export interface ToolExecutionEndEvent { type: "tool_execution_end"; toolCallId: string; toolName: string; result: unknown; isError: boolean; } export type { AutoCompactionEndEvent, AutoCompactionStartEvent, AutoRetryEndEvent, AutoRetryStartEvent, TodoReminderEvent, TtsrTriggeredEvent, } from "../shared-events"; /** Fired when AuthStorage automatically soft-disables a credential (e.g. OAuth `invalid_grant`). Not fired for user-initiated `remove()` or duplicate-credential dedup. */ export interface CredentialDisabledEvent { type: "credential_disabled"; /** Provider id whose credential was disabled (e.g. "anthropic"). */ provider: string; /** Verbatim error captured for forensics (truncated upstream). */ disabledCause: string; } /** Fired when user executes a bash command via ! or !! prefix */ export interface UserBashEvent { type: "user_bash"; /** The command to execute */ command: string; /** True if !! prefix was used (excluded from LLM context) */ excludeFromContext: boolean; /** Current working directory */ cwd: string; } /** Fired when user executes Python code via $ or $$ prefix */ export interface UserPythonEvent { type: "user_python"; /** The Python code to execute */ code: string; /** True if $$ prefix was used (excluded from LLM context) */ excludeFromContext: boolean; /** Current working directory */ cwd: string; } /** Fired when input enters through an interactive, SDK, or extension source. */ export interface InputEvent { type: "input"; text: string; images?: ImageContent[]; source: "interactive" | "sdk" | "extension"; } interface ToolCallEventBase { type: "tool_call"; toolCallId: string; } export interface BashToolCallEvent extends ToolCallEventBase { toolName: "bash"; input: BashToolInput; } export interface ReadToolCallEvent extends ToolCallEventBase { toolName: "read"; input: ReadToolInput; } export interface EditToolCallEvent extends ToolCallEventBase { toolName: "edit"; input: Record; } export interface WriteToolCallEvent extends ToolCallEventBase { toolName: "write"; input: WriteToolInput; } export interface SearchToolCallEvent extends ToolCallEventBase { toolName: "search"; input: SearchToolInput; } export interface FindToolCallEvent extends ToolCallEventBase { toolName: "find"; input: FindToolInput; } export interface CustomToolCallEvent extends ToolCallEventBase { toolName: string; input: Record; } /** Fired before a tool executes. Can block. */ export type ToolCallEvent = BashToolCallEvent | ReadToolCallEvent | EditToolCallEvent | WriteToolCallEvent | SearchToolCallEvent | FindToolCallEvent | CustomToolCallEvent; interface ToolResultEventBase { type: "tool_result"; toolCallId: string; input: Record; content: (TextContent | ImageContent)[]; isError: boolean; } export interface BashToolResultEvent extends ToolResultEventBase { toolName: "bash"; details: BashToolDetails | undefined; } export interface ReadToolResultEvent extends ToolResultEventBase { toolName: "read"; details: ReadToolDetails | undefined; } export interface EditToolResultEvent extends ToolResultEventBase { toolName: "edit"; details: EditToolDetails | undefined; } export interface WriteToolResultEvent extends ToolResultEventBase { toolName: "write"; details: undefined; } export interface SearchToolResultEvent extends ToolResultEventBase { toolName: "search"; details: SearchToolDetails | undefined; } export interface FindToolResultEvent extends ToolResultEventBase { toolName: "find"; details: FindToolDetails | undefined; } export interface CustomToolResultEvent extends ToolResultEventBase { toolName: string; details: unknown; } /** Fired after a tool executes. Can modify result. */ export type ToolResultEvent = BashToolResultEvent | ReadToolResultEvent | EditToolResultEvent | WriteToolResultEvent | SearchToolResultEvent | FindToolResultEvent | CustomToolResultEvent; /** * Type guard for narrowing ToolCallEvent by tool name. * * Built-in tools narrow automatically (no type params needed): * ```ts * if (isToolCallEventType("bash", event)) { * event.input.command; // string * } * ``` * * Custom tools require explicit type parameters: * ```ts * if (isToolCallEventType<"my_tool", MyToolInput>("my_tool", event)) { * event.input.action; // typed * } * ``` * * Note: Direct narrowing via `event.toolName === "bash"` doesn't work because * CustomToolCallEvent.toolName is `string` which overlaps with all literals. */ export declare function isToolCallEventType(toolName: "bash", event: ToolCallEvent): event is BashToolCallEvent; export declare function isToolCallEventType(toolName: "read", event: ToolCallEvent): event is ReadToolCallEvent; export declare function isToolCallEventType(toolName: "edit", event: ToolCallEvent): event is EditToolCallEvent; export declare function isToolCallEventType(toolName: "write", event: ToolCallEvent): event is WriteToolCallEvent; export declare function isToolCallEventType(toolName: "search", event: ToolCallEvent): event is SearchToolCallEvent; export declare function isToolCallEventType(toolName: "find", event: ToolCallEvent): event is FindToolCallEvent; export declare function isToolCallEventType>(toolName: TName, event: ToolCallEvent): event is ToolCallEvent & { toolName: TName; input: TInput; }; /** Union of all event types */ export type ExtensionEvent = ResourcesDiscoverEvent | SessionEvent | ContextEvent | BeforeProviderRequestEvent | AfterProviderResponseEvent | BeforeAgentStartEvent | AgentStartEvent | AgentFailedEvent | AgentEndEvent | TurnStartEvent | TurnEndEvent | MessageStartEvent | MessageUpdateEvent | MessageEndEvent | ReasoningSummaryStartEvent | ReasoningSummaryDeltaEvent | ReasoningSummaryEndEvent | ToolExecutionStartEvent | ToolExecutionUpdateEvent | ToolExecutionEndEvent | AutoCompactionStartEvent | AutoCompactionEndEvent | AutoRetryStartEvent | AutoRetryEndEvent | TtsrTriggeredEvent | TodoReminderEvent | GoalUpdatedEvent | CredentialDisabledEvent | UserBashEvent | UserPythonEvent | InputEvent | ToolCallEvent | ToolResultEvent; export interface ContextEventResult { messages?: AgentMessage[]; } export type BeforeProviderRequestEventResult = unknown; export type { ToolCallEventResult } from "../shared-events"; /** Result from input event handler */ export interface InputEventResult { /** If true, the input was handled and should not continue through normal flow */ handled?: boolean; /** Replace the input text */ text?: string; /** Replace any pending images */ images?: ImageContent[]; } /** Result from user_bash event handler */ export interface UserBashEventResult { /** Full replacement: extension handled execution, use this result */ result?: BashResult; } /** Result from user_python event handler */ export interface UserPythonEventResult { /** Full replacement: extension handled execution, use this result */ result?: PythonResult; } export type { ToolResultEventResult } from "../shared-events"; export interface BeforeAgentStartEventResult { message?: Pick; /** Replace the system prompt for this turn. If multiple extensions return this, they are chained. */ systemPrompt?: string[]; } export type { SessionBeforeBranchResult, SessionBeforeCompactResult, SessionBeforeSwitchResult, SessionBeforeTreeResult, SessionCompactingResult, } from "../shared-events"; export interface MessageRenderOptions { expanded: boolean; } export type MessageRenderer = (message: CustomMessage, options: MessageRenderOptions, theme: Theme) => Component | undefined; export interface RegisteredCommand { name: string; description?: string; getArgumentCompletions?: (argumentPrefix: string) => AutocompleteItem[] | null; handler: (args: string, ctx: ExtensionCommandContext) => Promise; } /** Handler function type for events */ export type ExtensionHandler = (event: E, ctx: ExtensionContext) => Promise | R | void; /** * ExtensionAPI passed to extension factory functions. */ export interface ExtensionAPI { /** File logger for error/warning/debug messages */ logger: typeof import("@gajae-code/utils").logger; /** Injected zod-backed typebox shim for legacy `Type.Object(...)` parameter authoring. */ typebox: typeof import("../typebox"); /** Injected zod module for Zod-authored extension tools (canonical going forward). */ zod: typeof import("zod/v4"); /** Injected gajae-code exports for accessing SDK utilities */ pi: typeof piCodingAgent; on(event: "resources_discover", handler: ExtensionHandler): void; on(event: "session_start", handler: ExtensionHandler): void; on(event: "session_before_switch", handler: ExtensionHandler): void; on(event: "session_switch", handler: ExtensionHandler): void; on(event: "session_before_branch", handler: ExtensionHandler): void; on(event: "session_branch", handler: ExtensionHandler): void; on(event: "session_before_compact", handler: ExtensionHandler): void; on(event: "session.compacting", handler: ExtensionHandler): void; on(event: "session_compact", handler: ExtensionHandler): void; on(event: "session_shutdown", handler: ExtensionHandler): void; on(event: "session_before_tree", handler: ExtensionHandler): void; on(event: "session_tree", handler: ExtensionHandler): void; on(event: "context", handler: ExtensionHandler): void; on(event: "before_provider_request", handler: ExtensionHandler): void; on(event: "after_provider_response", handler: ExtensionHandler): void; on(event: "before_agent_start", handler: ExtensionHandler): void; on(event: "agent_start", handler: ExtensionHandler): void; on(event: "agent_failed", handler: ExtensionHandler): void; on(event: "agent_end", handler: ExtensionHandler): void; on(event: "turn_start", handler: ExtensionHandler): void; on(event: "turn_end", handler: ExtensionHandler): void; on(event: "message_start", handler: ExtensionHandler): void; on(event: "message_update", handler: ExtensionHandler): void; on(event: "reasoning_summary_start", handler: ExtensionHandler): void; on(event: "reasoning_summary_delta", handler: ExtensionHandler): void; on(event: "reasoning_summary_end", handler: ExtensionHandler): void; on(event: "message_end", handler: ExtensionHandler): void; on(event: "tool_execution_start", handler: ExtensionHandler): void; on(event: "tool_execution_update", handler: ExtensionHandler): void; on(event: "tool_execution_end", handler: ExtensionHandler): void; on(event: "auto_compaction_start", handler: ExtensionHandler): void; on(event: "auto_compaction_end", handler: ExtensionHandler): void; on(event: "auto_retry_start", handler: ExtensionHandler): void; on(event: "auto_retry_end", handler: ExtensionHandler): void; on(event: "ttsr_triggered", handler: ExtensionHandler): void; on(event: "todo_reminder", handler: ExtensionHandler): void; on(event: "goal_updated", handler: ExtensionHandler): void; on(event: "credential_disabled", handler: ExtensionHandler): void; on(event: "input", handler: ExtensionHandler): void; on(event: "tool_call", handler: ExtensionHandler): void; on(event: "tool_result", handler: ExtensionHandler): void; on(event: "user_bash", handler: ExtensionHandler): void; on(event: "user_python", handler: ExtensionHandler): void; /** Register a tool that the LLM can call. */ registerTool(tool: ToolDefinition): void; /** Register a custom command. */ registerCommand(name: string, options: { description?: string; getArgumentCompletions?: RegisteredCommand["getArgumentCompletions"]; handler: RegisteredCommand["handler"]; }): void; /** Register a keyboard shortcut. */ registerShortcut(shortcut: KeyId, options: { description?: string; handler: (ctx: ExtensionContext) => Promise | void; }): void; /** Register a CLI flag. */ registerFlag(name: string, options: { description?: string; type: "boolean" | "string"; default?: boolean | string; }): void; /** Set the display label for this extension, or set a label on a specific entry. */ setLabel(entryIdOrLabel: string, label?: string | undefined): void; /** Get the value of a registered CLI flag. */ getFlag(name: string): boolean | string | undefined; /** Register a custom renderer for CustomMessageEntry. */ registerMessageRenderer(customType: string, renderer: MessageRenderer): void; /** * Send a custom message to the session. * * `deliverAs: "nextTurn"` keeps the message hidden from the editable pending-message UI. * If `triggerTurn` is also true while the current turn is still unwinding, the session schedules * an internal continuation that consumes the message on the next turn. */ sendMessage(message: Pick, "customType" | "content" | "display" | "details" | "attribution">, options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn"; }): void; /** Send a user message to the agent, or queue it when deliverAs is set. */ sendUserMessage(content: string | (TextContent | ImageContent)[], options?: { deliverAs?: "steer" | "followUp"; /** Internal SDK signal preserving a busy dispatch across async admission fences. */ queuedAtDispatch?: boolean; onPreflightAccepted?: () => void; onPreflightAcceptCommit?: () => void | Promise; onQueuedPromoted?: (promotion: { startsOwnRun?: boolean; removed?: boolean; }) => void; onDispatchDisposition?: (promotion: { startsOwnRun: boolean; }) => void; preflightSignal?: AbortSignal; /** Internal SDK correlation owner for an exact queued follow-up. */ sdkRunToken?: string; }): Promise; /** Append a custom entry to the session for state persistence (not sent to LLM). */ appendEntry(customType: string, data?: T): void; /** Execute a shell command. */ exec(command: string, args: string[], options?: ExecOptions): Promise; /** Get the list of currently active tool names. */ getActiveTools(): string[]; /** Get all configured tools (built-in + extension tools). */ getAllTools(): string[]; /** Resolve display-safe metadata for a configured tool without exposing its implementation. */ resolveTool(name: string): Pick | undefined; /** Set the active tools by name. */ setActiveTools(toolNames: string[]): Promise; /** Get available slash commands in the current session. */ getCommands(): SlashCommandInfo[]; /** Set the current model. Returns false if no API key available. */ setModel(model: Model): Promise; /** Get current thinking level. */ getThinkingLevel(): ThinkingLevel | undefined; /** Set thinking level for the current session. */ setThinkingLevel(level: ThinkingLevel, persist?: boolean): void; /** Get whether thinking output is visible in the current session. */ getThinkingVisibility(): "visible" | "hidden"; /** Set whether thinking output is visible for the current session. */ setThinkingVisibility(visibility: "visible" | "hidden", persist?: boolean): void; /** Cycle the current model's available thinking levels. */ cycleThinkingLevel(): ThinkingLevel | undefined; /** Set thinking level from a session or durable global control surface. */ setThinkingLevelForControl(level: ThinkingLevel, persist: boolean): Promise; /** Set thinking visibility from a session or durable global control surface. */ setThinkingVisibilityForControl(visibility: "visible" | "hidden", persist: boolean): Promise; /** Set the model for this session only. Returns false when it is unavailable. */ setModelTemporaryForControl(model: Model, expectedSessionId?: string, thinkingLevel?: ThinkingLevel): Promise; /** Fetch provider usage through the session's canonical provider resolution. */ fetchUsageReportsForControl(): Promise; /** Report whether the current effort follows global config or a session override. */ getThinkingScopeForControl(): "session" | "global config"; /** Get the current session name. */ getSessionName(): string | undefined; /** Set the session name. Persists to the session file. */ setSessionName(name: string): Promise; /** * Register or override a model provider. * * If `models` is provided: replaces all existing models for this provider. * If only `baseUrl` is provided: overrides the URL for existing models. * If `streamSimple` is provided: registers a custom API stream handler. * * @example * // Register a new provider with custom models and streaming * pi.registerProvider("google-vertex-Anthropic model", { * baseUrl: "https://us-east5-aiplatform.googleapis.com", * apiKey: "GOOGLE_CLOUD_PROJECT", * api: "vertex-Anthropic model-api", * streamSimple: myStreamFunction, * models: [ * { * id: "Anthropic model-sonnet-4@20250514", * name: "Anthropic model Sonnet 4 (Vertex)", * reasoning: true, * thinking: { mode: "anthropic-adaptive", minLevel: "minimal", maxLevel: "high" }, * input: ["text", "image"], * cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }, * contextWindow: 200000, * maxTokens: 64000, * ] * }); * * @example * // Override baseUrl for an existing provider * pi.registerProvider("anthropic", { * baseUrl: "https://proxy.example.com" * }); */ registerProvider(name: string, config: ProviderConfig): void; /** Shared event bus for extension communication. */ events: EventBus; } /** Configuration for registering a provider via pi.registerProvider(). */ export interface ProviderConfig { /** Base URL for the API endpoint. Required when defining models. */ baseUrl?: string; /** API key or environment variable name. Required when defining models unless oauth is provided. */ apiKey?: string; /** API type identifier. Required when registering streamSimple or when models don't specify one. */ api?: Api; /** Custom streaming function for non-built-in APIs. */ streamSimple?: (model: Model, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream; /** Custom headers to include in requests. */ headers?: Record; /** If true, adds Authorization: Bearer header with the resolved API key. */ authHeader?: boolean; /** Models to register. If provided, replaces all existing models for this provider. */ models?: ProviderModelConfig[]; /** OAuth provider for /login support. */ oauth?: { /** Display name in login UI. */ name: string; /** Run the provider login flow and return credentials (or a plain API key) to persist. */ login(callbacks: OAuthLoginCallbacks): Promise; /** Refresh expired credentials. */ refreshToken?(credentials: OAuthCredentials): Promise; /** Convert credentials to an API key string for requests. */ getApiKey?(credentials: OAuthCredentials): string; /** Optional model rewrite hook for credential-aware routing (e.g., enterprise URLs). */ modifyModels?(models: Model[], credentials: OAuthCredentials): Model[]; }; } /** Configuration for a model within a provider. */ export interface ProviderModelConfig { /** Model ID (e.g., "Anthropic model-sonnet-4@20250514"). */ id: string; /** Display name (e.g., "Anthropic model Sonnet 4 (Vertex)"). */ name: string; /** API type override for this model. */ api?: Api; /** Whether the model supports extended thinking at all. */ reasoning: boolean; /** Optional canonical thinking capability metadata for per-model effort support. */ thinking?: Model["thinking"]; /** Supported input types. */ input: ("text" | "image")[]; /** Cost per million tokens. */ cost: { input: number; output: number; cacheRead: number; cacheWrite: number; }; /** Premium Copilot requests charged per user-initiated request. */ premiumMultiplier?: number; /** Maximum context window size in tokens. */ contextWindow: number; /** Maximum output tokens. */ maxTokens: number; /** Custom headers for this model. */ headers?: Record; /** OpenAI compatibility settings. */ compat?: Model["compat"]; } /** Extension factory function type. Supports both sync and async initialization. */ export type ExtensionFactory = (pi: ExtensionAPI) => void | Promise; export interface RegisteredTool { definition: ToolDefinition; extensionPath: string; } export interface ExtensionFlag { name: string; description?: string; type: "boolean" | "string"; default?: boolean | string; extensionPath: string; } export interface ExtensionShortcut { shortcut: KeyId; description?: string; handler: (ctx: ExtensionContext) => Promise | void; extensionPath: string; } type HandlerFn = (...args: unknown[]) => Promise; export type SendMessageHandler = (message: Pick, "customType" | "content" | "display" | "details" | "attribution">, /** * `deliverAs: "nextTurn"` queues hidden custom context for the next turn. * When paired with `triggerTurn: true` during prompt teardown, the session schedules * an internal continuation without surfacing the message in the editable pending queue. */ options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn"; }) => void; export type SendUserMessageHandler = (content: string | (TextContent | ImageContent)[], options?: { deliverAs?: "steer" | "followUp"; onPreflightAccepted?: () => void; onPreflightAcceptCommit?: () => void | Promise; /** * Fired when a queued submission is consumed. `startsOwnRun: true` means * the batch starts a new run; false means it joins an active run or * maintenance continuation. Consumers must not assume every promotion * grants root abort ownership. */ onQueuedPromoted?: (promotion: { startsOwnRun?: boolean; removed?: boolean; }) => void; preflightSignal?: AbortSignal; /** Internal SDK correlation owner for an exact queued follow-up. */ sdkRunToken?: string; }) => void | Promise; export type AppendEntryHandler = (customType: string, data?: T) => void; export type GetActiveToolsHandler = () => string[]; export type GetAllToolsHandler = () => string[]; export type ResolveToolHandler = (name: string) => Pick | undefined; export type GetCommandsHandler = () => SlashCommandInfo[]; export type SetActiveToolsHandler = (toolNames: string[]) => Promise; export type SetModelHandler = (model: Model) => Promise; export type CycleThinkingLevelHandler = () => ThinkingLevel | undefined; export type SetThinkingLevelForControlHandler = (level: ThinkingLevel, persist: boolean) => Promise; export type SetThinkingVisibilityForControlHandler = (visibility: "visible" | "hidden", persist: boolean) => Promise; export type SetModelTemporaryForControlHandler = (model: Model, expectedSessionId?: string, thinkingLevel?: ThinkingLevel) => Promise; /** Result of activating a model profile as the global default from a control surface. */ export interface DefaultModelProfileActivationResult { changed: boolean; /** The canonical (alias-resolved) profile id. */ id: string; } export type FetchUsageReportsForControlHandler = () => Promise; export type GetThinkingScopeForControlHandler = () => "session" | "global config"; export type GetThinkingLevelHandler = () => ThinkingLevel | undefined; export type SetThinkingLevelHandler = (level: ThinkingLevel, persist?: boolean) => void; export type GetThinkingVisibilityHandler = () => "visible" | "hidden"; export type SetThinkingVisibilityHandler = (visibility: "visible" | "hidden", persist?: boolean) => void; /** Shared state created by loader, used during registration and runtime. */ export interface ExtensionRuntimeState { flagValues: Map; /** Provider registrations queued during extension loading, processed during session initialization */ pendingProviderRegistrations: Array<{ name: string; config: ProviderConfig; sourceId: string; }>; } /** Action implementations for ExtensionAPI methods. */ export interface ExtensionActions { sendMessage: SendMessageHandler; sendUserMessage: SendUserMessageHandler; appendEntry: AppendEntryHandler; setLabel: (targetId: string, label: string | undefined) => void; getActiveTools: GetActiveToolsHandler; getAllTools: GetAllToolsHandler; resolveTool: ResolveToolHandler; setActiveTools: SetActiveToolsHandler; getCommands: GetCommandsHandler; setModel: SetModelHandler; getThinkingLevel: GetThinkingLevelHandler; setThinkingLevel: SetThinkingLevelHandler; getThinkingVisibility: GetThinkingVisibilityHandler; setThinkingVisibility: SetThinkingVisibilityHandler; cycleThinkingLevel: CycleThinkingLevelHandler; setThinkingLevelForControl: SetThinkingLevelForControlHandler; setThinkingVisibilityForControl: SetThinkingVisibilityForControlHandler; setModelTemporaryForControl: SetModelTemporaryForControlHandler; fetchUsageReportsForControl: FetchUsageReportsForControlHandler; getThinkingScopeForControl: GetThinkingScopeForControlHandler; getSessionName: () => string | undefined; setSessionName: (name: string) => Promise; } /** Actions for ExtensionContext (ctx.* in event handlers). */ export interface ExtensionContextActions { getModel: () => Model | undefined; getCredentialSessionId?: () => string; isIdle: () => boolean; /** Stable resource ownership identifier for the active prompt run. */ getActivePromptHandle?: () => string | undefined; abort: () => void | Promise; abortPromptAndWait?: (handle: string, options: { graceMs: number; }) => Promise; hasPendingMessages: () => boolean; /** Typed pending-message counts per queue; optional for embedders without a counted queue. */ getPendingMessageCounts?: () => { steering: number; followUp: number; nextTurn: number; }; getTranscript?: () => ExtensionTranscriptEntry[]; getTranscriptBody?: (entryId: string) => string | undefined; getGoalState?: () => unknown; getTodoState?: () => unknown; getQueuedMessages?: () => unknown[]; getActiveTools?: () => string[]; getAllTools?: () => string[]; resolveTool?: (name: string) => Pick | undefined; shutdown: () => void; getContextUsage: () => ContextUsage | undefined; compact: (instructionsOrOptions?: string | CompactOptions) => Promise; getSystemPrompt: () => string[]; /** SDK workflow-gate bridge, when a session has a remote gate responder. */ getWorkflowGate?: () => WorkflowGateEmitter | undefined; /** Clear the active conversation while preserving the saved session identity. */ clearContext?: () => Promise; /** Session control and query seams exposed to the per-session SDK host. */ cycleModel?: () => Promise<{ model: Model; thinkingLevel: ThinkingLevel | undefined; } | undefined>; setModelProfile?: (name: string) => Promise; setDefaultModelProfile?: (name: string, options?: { persistDefault?: boolean; thinkingLevelOverride?: ThinkingLevel; /** Internal SDK host hooks invoked inside the profile activation admission. */ onBeforeActivation?: () => void; onAfterActivation?: () => void; }) => Promise; getActiveModelProfile?: () => string | undefined; withSdkControlMutation?: (body: () => Promise) => Promise; cycleThinkingLevel?: () => ThinkingLevel | undefined; setQueueMode?: (kind: "steering" | "follow_up" | "interrupt", mode: unknown) => boolean; getSkillState?: () => unknown; getConfigItems?: () => unknown; getBranchCandidates?: () => unknown; getExtensions?: () => unknown; getArtifact?: (id: string) => Uint8Array | string | undefined | Promise; getArtifactRange?: (id: string, offset: number, length: number) => { bytes: Uint8Array; totalBytes: number; } | undefined | Promise<{ bytes: Uint8Array; totalBytes: number; } | undefined>; getJobs?: () => unknown; setSdkPermissionProvider?: (provider: ((toolCall: ClientBridgePermissionToolCall, options: ClientBridgePermissionOption[], signal?: AbortSignal) => Promise) | undefined) => void; setSdkClientBridge?: (bridge: ClientBridge | undefined) => void; sdkControl?: (operation: string, input: Record) => unknown | Promise; invokeSkill?: (name: string, args?: string, options?: { onPreflightAccepted?: () => void; onPreflightAcceptCommit?: () => void | Promise; onSkillPrepared?: (meta: { name: string; path: string; lineCount?: number; cleanedArgs?: string; }) => void; preflightSignal?: AbortSignal; }) => Promise; setPlanMode?: (on: boolean) => unknown; operateGoal?: (op: "create" | "get" | "resume" | "pause" | "complete" | "drop", objective?: string) => Promise; } /** Actions for ExtensionCommandContext (ctx.* in command handlers). */ export interface ExtensionCommandContextActions { getContextUsage: () => ContextUsage | undefined; waitForIdle: () => Promise; newSession: (options?: { parentSession?: string; setup?: (sessionManager: SessionManager) => Promise; }) => Promise<{ cancelled: boolean; }>; branch: (entryId: string) => Promise<{ cancelled: boolean; }>; navigateTree: (targetId: string, options?: { summarize?: boolean; }) => Promise<{ cancelled: boolean; }>; compact: (instructionsOrOptions?: string | CompactOptions) => Promise; switchSession: (sessionPath: string) => Promise<{ cancelled: boolean; }>; reload: () => Promise; } /** Full runtime = state + actions. */ export interface ExtensionRuntime extends ExtensionRuntimeState, ExtensionActions { } /** Loaded extension with all registered items. */ export interface Extension { path: string; resolvedPath: string; label?: string; handlers: Map; tools: Map>; messageRenderers: Map; commands: Map; flags: Map; shortcuts: Map; } /** Result of loading extensions. */ export interface LoadExtensionsResult { extensions: Extension[]; errors: Array<{ path: string; error: string; }>; runtime: ExtensionRuntime; } export interface ExtensionError { extensionPath: string; event: string; error: string; stack?: string; }