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
*/11
import type {12
AgentMessage,13
AgentTool,14
AgentToolCallOutcome,15
AgentToolResult,16
AgentToolUpdateCallback,17
ThinkingLevel,18
ToolExecutionMode,19
} from "@earendil-works/pi-agent-core";20
import type {21
AnyModel,22
Api,23
AssistantMessageEvent,24
AssistantMessageEventStream,25
ClassifierApi,26
ConstrainedSamplingConfig,27
ImageApi,28
ImageContent,29
JsonValue,30
Message,31
Model,32
OAuthCredentials,33
OAuthLoginCallbacks,34
Provider,35
ProviderClassifier,36
ProviderHeaders,37
ProviderId,38
ProviderImages,39
RefreshModelsContext,40
SimpleStreamOptions,41
TextContent,42
ToolResultMessage,43
TranscriptContext,44
Usage,45
} from "@earendil-works/pi-ai";46
import type {47
AutocompleteItem,48
AutocompleteProvider,49
Component,50
EditorComponent,51
EditorTheme,52
KeyId,53
OverlayHandle,54
OverlayOptions,55
TUI,56
} from "@earendil-works/pi-tui";57
import type { Static, TSchema } from "typebox";58
import type { Theme } from "../../modes/interactive/theme/theme.ts";59
import type { BashResult } from "../bash-executor.ts";60
import type { CacheWarmingDecisionEvent, CacheWarmingDecisionEventResult } from "../cache-warmer.ts";61
import type { CompactionPreparation, CompactionResult } from "../compaction/index.ts";62
import type { EventBus } from "../event-bus.ts";63
import type { ExecOptions, ExecResult } from "../exec.ts";64
import type { ReadonlyFooterDataProvider } from "../footer-data-provider.ts";65
import type { KeybindingsManager } from "../keybindings.ts";66
import type { McpServerConfig, McpServerRegistry, RegisteredMcpServer } from "../mcp-servers.ts";67
import type { CustomMessage } from "../messages.ts";68
import type { ModelRegistry } from "../model-registry.ts";69
import type { ScopedModel } from "../model-resolver.ts";70
import type {71
BranchSummaryEntry,72
CompactionEntry,73
ContextEditEntry,74
CustomEntry,75
ProjectedSessionEntry,76
ReadonlySessionManager,77
SessionEntry,78
SessionManager,79
} from "../session-manager.ts";80
import type { Settings } from "../settings-manager.ts";81
import type { SlashCommandInfo } from "../slash-commands.ts";82
import type { SourceInfo } from "../source-info.ts";83
import type { BuildSystemPromptOptions, NormalizedBuildSystemPromptOptions } from "../system-prompt.ts";84
import type { BashOperations } from "../tools/bash.ts";85
import type { EditToolDetails } from "../tools/edit.ts";86
import type {87
BashToolDetails,88
BashToolInput,89
EditToolInput,90
FindToolDetails,91
FindToolInput,92
GrepToolDetails,93
GrepToolInput,94
LsToolDetails,95
LsToolInput,96
PowerShellToolDetails,97
PowerShellToolInput,98
ReadToolDetails,99
ReadToolInput,100
WriteToolInput,101
} from "../tools/index.ts";102
import type { ModelRoute, ModelRouteRequest, VirtualModelDefinition } from "../virtual-models.ts";104
export type { ExecOptions, ExecResult } from "../exec.ts";105
export type { BuildSystemPromptOptions, NormalizedBuildSystemPromptOptions } from "../system-prompt.ts";106
export type { AgentToolResult, AgentToolUpdateCallback, ToolExecutionMode };107
export type { AppKeybinding, KeybindingsManager } from "../keybindings.ts";109
// ============================================================================110
// UI Context111
// ============================================================================113
/** Options for extension UI dialogs. */114
export interface ExtensionUIDialogOptions {115
/** AbortSignal to programmatically dismiss the dialog. */116
signal?: AbortSignal;117
/** Timeout in milliseconds. Dialog auto-dismisses with live countdown display. */118
timeout?: number;119
}121
/** Placement for extension widgets. */122
export type WidgetPlacement = "aboveEditor" | "belowEditor";124
/** Options for extension widgets. */125
export interface ExtensionWidgetOptions {126
/** Where the widget is rendered. Defaults to "aboveEditor". */127
placement?: WidgetPlacement;128
}130
/** Raw terminal input listener for extensions. */131
export type TerminalInputHandler = (data: string) => { consume?: boolean; data?: string } | undefined;133
/** Working indicator configuration for the interactive streaming loader. */134
export interface WorkingIndicatorOptions {135
/** Animation frames. Use an empty array to hide the indicator entirely. Custom frames are rendered verbatim. */136
frames?: string[];137
/** Frame interval in milliseconds for animated indicators. */138
intervalMs?: number;139
}141
/** Wrap the current autocomplete provider with additional behavior. */142
export type AutocompleteProviderFactory = (current: AutocompleteProvider) => AutocompleteProvider;143
export type EditorFactory = (tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => EditorComponent;145
/**146
* UI context for extensions to request interactive UI.147
* Each mode (interactive, RPC, print) provides its own implementation.148
*/149
export interface ExtensionUIContext {150
/** Show a selector and return the user's choice. */151
select(title: string, options: string[], opts?: ExtensionUIDialogOptions): Promise<string | undefined>;153
/** Show a confirmation dialog. */154
confirm(title: string, message: string, opts?: ExtensionUIDialogOptions): Promise<boolean>;156
/** Show a text input dialog. */157
input(title: string, placeholder?: string, opts?: ExtensionUIDialogOptions): Promise<string | undefined>;159
/** Show a notification to the user. */160
notify(message: string, type?: "info" | "warning" | "error"): void;162
/** Listen to raw terminal input (interactive mode only). Returns an unsubscribe function. */163
onTerminalInput(handler: TerminalInputHandler): () => void;165
/** Set status text in the footer/status bar. Pass undefined to clear. */166
setStatus(key: string, text: string | undefined): void;168
/** Set the working/loading message shown during streaming. Call with no argument to restore default. */169
setWorkingMessage(message?: string): void;171
/** Show or hide the built-in interactive working loader row during streaming. */172
setWorkingVisible(visible: boolean): void;174
/**175
* Configure the interactive working indicator shown during streaming.176
*177
* - Omit the argument to restore the default animated spinner.178
* - Use `frames: ["●"]` for a static indicator.179
* - Use `frames: []` to hide the indicator entirely.180
* - Custom frames are rendered as provided, so extensions must add their own colors.181
*/182
setWorkingIndicator(options?: WorkingIndicatorOptions): void;184
/** Set the label shown for hidden thinking blocks. Call with no argument to restore default. */185
setHiddenThinkingLabel(label?: string): void;187
/** Set a widget to display above or below the editor. Accepts string array or component factory. */188
setWidget(key: string, content: string[] | undefined, options?: ExtensionWidgetOptions): void;189
setWidget(190
key: string,191
content: ((tui: TUI, theme: Theme) => Component & { dispose?(): void }) | undefined,192
options?: ExtensionWidgetOptions,193
): void;195
/** Set a custom footer component, or undefined to restore the built-in footer.196
*197
* The factory receives a FooterDataProvider for data not otherwise accessible:198
* git branch and extension statuses from setStatus(). Context usage is on199
* ctx.getContextUsage(), token stats on ctx.sessionManager.getEntries(), model info on ctx.model.200
*/201
setFooter(202
factory:203
| ((tui: TUI, theme: Theme, footerData: ReadonlyFooterDataProvider) => Component & { dispose?(): void })204
| undefined,205
): void;207
/** Set a custom header component (shown at startup, above chat), or undefined to restore the built-in header. */208
setHeader(factory: ((tui: TUI, theme: Theme) => Component & { dispose?(): void }) | undefined): void;210
/** Set the terminal window/tab title. */211
setTitle(title: string): void;213
/** Show a custom component with keyboard focus. */214
custom<T>(215
factory: (216
tui: TUI,217
theme: Theme,218
keybindings: KeybindingsManager,219
done: (result: T) => void,220
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,221
options?: {222
overlay?: boolean;223
/** Overlay positioning/sizing options. Can be static or a function for dynamic updates. */224
overlayOptions?: OverlayOptions | (() => OverlayOptions);225
/** Called with the overlay handle after the overlay is shown. Use to control visibility. */226
onHandle?: (handle: OverlayHandle) => void;227
},228
): Promise<T>;230
/** Paste text into the editor, triggering paste handling (collapse for large content). */231
pasteToEditor(text: string): void;233
/** Set the text in the core input editor. */234
setEditorText(text: string): void;236
/** Get the current text from the core input editor. */237
getEditorText(): string;239
/** Show a multi-line editor for text editing. */240
editor(title: string, prefill?: string): Promise<string | undefined>;242
/** Stack additional autocomplete behavior on top of the built-in provider. */243
addAutocompleteProvider(factory: AutocompleteProviderFactory): void;245
/**246
* Set a custom editor component via factory function.247
* Pass undefined to restore the default editor.248
*249
* The factory receives:250
* - `theme`: EditorTheme for styling borders and autocomplete251
* - `keybindings`: KeybindingsManager for app-level keybindings252
*253
* For full app keybinding support (escape, ctrl+d, model switching, etc.),254
* extend `CustomEditor` from `@earendil-works/pi-coding-agent` and call255
* `super.handleInput(data)` for keys you don't handle.256
*257
* @example258
* ```ts259
* import { CustomEditor } from "@earendil-works/pi-coding-agent";260
*261
* class VimEditor extends CustomEditor {262
* private mode: "normal" | "insert" = "insert";263
*264
* handleInput(data: string): void {265
* if (this.mode === "normal") {266
* // Handle vim normal mode keys...267
* if (data === "i") { this.mode = "insert"; return; }268
* }269
* super.handleInput(data); // App keybindings + text editing270
* }271
* }272
*273
* ctx.ui.setEditorComponent((tui, theme, keybindings) =>274
* new VimEditor(tui, theme, keybindings)275
* );276
* ```277
*/278
setEditorComponent(factory: EditorFactory | undefined): void;280
/** Get the currently configured custom editor factory, or undefined when using the default editor. */281
getEditorComponent(): EditorFactory | undefined;283
/** Get the current theme for styling. */284
readonly theme: Theme;286
/** Get all available themes with their names and file paths. */287
getAllThemes(): { name: string; path: string | undefined }[];289
/** Load a theme by name without switching to it. Returns undefined if not found. */290
getTheme(name: string): Theme | undefined;292
/** Set the current theme by name or Theme object. */293
setTheme(theme: string | Theme): { success: boolean; error?: string };295
/** Get current tool output expansion state. */296
getToolsExpanded(): boolean;298
/** Set tool output expansion state. */299
setToolsExpanded(expanded: boolean): void;300
}302
// ============================================================================303
// Extension Context304
// ============================================================================306
export interface ContextUsage {307
/** Estimated context tokens, or null if unknown (e.g. right after compaction, before next LLM response). */308
tokens: number | null;309
contextWindow: number;310
/** Context usage as percentage of context window, or null if tokens is unknown. */311
percent: number | null;312
}314
export interface CompactOptions {315
customInstructions?: string;316
onComplete?: (result: CompactionResult) => void;317
onError?: (error: Error) => void;318
}320
/**321
* Context passed to extension event handlers.322
*/323
export type ExtensionMode = "tui" | "rpc" | "json" | "print";325
export interface ExtensionContext {326
/** UI methods for user interaction */327
ui: ExtensionUIContext;328
/** Current run mode. Use "tui" to guard terminal-only UI such as custom components. */329
mode: ExtensionMode;330
/** Whether dialog-capable UI is available (true in TUI and RPC modes) */331
hasUI: boolean;332
/** Current working directory */333
cwd: string;334
/** Session manager (read-only) */335
sessionManager: ReadonlySessionManager;336
/** Model registry for API key resolution */337
modelRegistry: ModelRegistry;338
/** Current model (may be undefined) */339
model: Model<any> | undefined;340
/** Models scoped to this session (resolved from `--models` /341
* `enabledModels` settings against the available catalogue). Same set342
* the `/scoped-models` command shows. Empty when no scoping is343
* configured (all available models are usable). Read-only snapshot. */344
scopedModels: readonly ScopedModel[];345
/** Current thinking level, when provided by the session runtime. */346
thinkingLevel?: ThinkingLevel;347
/** Whether the agent is idle (not streaming) */348
isIdle(): boolean;349
/** Whether project-local trust is active for this context. */350
isProjectTrusted(): boolean;351
/** The current abort signal, or undefined when the agent is not streaming. */352
signal: AbortSignal | undefined;353
/** Abort the current agent operation */354
abort(): void;355
/** Whether there are queued messages waiting */356
hasPendingMessages(): boolean;357
/** Gracefully shutdown pi and exit. Available in all contexts. */358
shutdown(): void;359
/** Get current context usage for the active model. */360
getContextUsage(): ContextUsage | undefined;361
/** Trigger compaction without awaiting completion. */362
compact(options?: CompactOptions): void;363
/** Get the current effective system prompt. */364
getSystemPrompt(): string;365
}367
/** Options for {@link ExtensionToolContext.executeTool}. */368
export interface ExecuteToolOptions {369
/** Defaults to the calling tool's signal. */370
signal?: AbortSignal;371
/** Receives partial results of the nested tool, in addition to `tool_execution_update` events. */372
onUpdate?: AgentToolUpdateCallback;373
}375
/**376
* Context passed to tool `execute()` in a session: the extension context plus `executeTool()`377
* for running other tools through the same validation, hooks, and permission checks as378
* model-issued calls.379
*380
* A tool wrapped with `wrapToolDefinition()` without a context factory, such as a built-in tool381
* created with `createBashTool()` and run in a plain `Agent` or called directly, gets no context.382
*/383
export interface ExtensionToolContext extends ExtensionContext {384
/** Tools {@link executeTool} can call. */385
readonly tools: readonly AgentTool[];386
/**387
* Run another tool. The call gets the id `<calling id>/<n>`, and the `tool_call`, `tool_result`,388
* and `tool_execution_*` events carry `parentToolCallId`. It does not appear in the transcript;389
* a bounded record of it is kept as `nestedCalls` on the calling tool's result message.390
*391
* Never rejects for tool failures: unknown tools, validation errors, blocked calls, and thrown392
* errors come back as `isError: true`.393
*/394
executeTool(name: string, args: unknown, options?: ExecuteToolOptions): Promise<AgentToolCallOutcome>;395
}397
/**398
* Extended context for command handlers.399
* Includes session control methods only safe in user-initiated commands.400
*/401
export interface ExtensionCommandContext extends ExtensionContext {402
/** Get the current base system-prompt construction options. */403
getSystemPromptOptions(): BuildSystemPromptOptions;405
/** Wait for the agent to finish streaming */406
waitForIdle(): Promise<void>;408
/** Start a new session, optionally with initialization. */409
newSession(options?: {410
parentSession?: string;411
setup?: (sessionManager: SessionManager) => Promise<void>;412
withSession?: (ctx: ReplacedSessionContext) => Promise<void>;413
}): Promise<{ cancelled: boolean }>;415
/** Fork from a specific entry, creating a new session file. */416
fork(417
entryId: string,418
options?: { position?: "before" | "at"; withSession?: (ctx: ReplacedSessionContext) => Promise<void> },419
): Promise<{ cancelled: boolean }>;421
/** Navigate to a different point in the session tree. */422
navigateTree(423
targetId: string,424
options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string },425
): Promise<{ cancelled: boolean }>;427
/** Switch to a different session file. */428
switchSession(429
sessionPath: string,430
options?: { withSession?: (ctx: ReplacedSessionContext) => Promise<void> },431
): Promise<{ cancelled: boolean }>;433
/** Reload extensions, skills, prompts, themes, and context files. */434
reload(): Promise<void>;435
}437
/**438
* Fresh command-capable context bound to the replacement session after a session switch.439
*440
* This is passed to `withSession()` callbacks on `newSession()`, `fork()`, and `switchSession()`.441
*/442
export interface ReplacedSessionContext extends ExtensionCommandContext {443
sendMessage<T = unknown>(444
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,445
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },446
): Promise<void>;448
sendUserMessage(449
content: string | (TextContent | ImageContent)[],450
options?: { deliverAs?: "steer" | "followUp"; expandPromptTemplates?: boolean },451
): Promise<void>;452
}454
// ============================================================================455
// Tool Types456
// ============================================================================458
/** Rendering options for tool results */459
export interface ToolRenderResultOptions {460
/** Whether the result view is expanded */461
expanded: boolean;462
/** Whether this is a partial/streaming result */463
isPartial: boolean;464
}466
/** Context passed to tool renderers. */467
export interface ToolRenderContext<TState = any, TArgs = any> {468
/** Current tool call arguments. Shared across call/result renders for the same tool call. */469
args: TArgs;470
/** Unique id for this tool execution. Stable across call/result renders for the same tool call. */471
toolCallId: string;472
/** Invalidate just this tool execution component for redraw. */473
invalidate: () => void;474
/** Previously returned component for this render slot, if any. */475
lastComponent: Component | undefined;476
/** Shared renderer state for this tool row. Initialized by tool-execution.ts. */477
state: TState;478
/** Working directory for this tool execution. */479
cwd: string;480
/** Whether the tool execution has started. */481
executionStarted: boolean;482
/** Whether the tool call arguments are complete. */483
argsComplete: boolean;484
/** Whether the tool result is partial/streaming. */485
isPartial: boolean;486
/** Whether the result view is expanded. */487
expanded: boolean;488
/** Whether inline images are currently shown in the TUI. */489
showImages: boolean;490
/** Whether the current result is an error. */491
isError: boolean;492
}494
/**495
* How the model reaches a tool. "Callable" means callable from other tools through496
* `ctx.executeTool()`, as the `codemode` tool does.497
*498
* - `direct`: declared to the model while active, and callable while active.499
* - `model-only`: declared to the model while active, never callable. Use it for orchestrating or500
* interactive tools.501
* - `codemode`: callable whenever registered. Not declared to the model unless explicitly502
* activated. Codemode tools list it in their description.503
* - `deferred`: like `codemode`, but codemode tools do not list it; tool search can find it.504
* - `hidden`: registered but unreachable. Activating it has no effect.505
*506
* `direct` and `model-only` tools are activated when they are registered; the others are not.507
* The active tool set (`getActiveTools`/`setActiveTools`) is the set declared to the model.508
*/509
export type ToolExposure = "direct" | "model-only" | "codemode" | "deferred" | "hidden";511
/**512
* Hints about what a tool does, with the meaning of MCP tool annotations. They come from the tool's513
* author and are not verified; permission extensions can use them to decide which calls to confirm.514
*/515
export interface ToolAnnotations {516
/** The tool does not modify its environment. */517
readOnlyHint?: boolean;518
/** The tool may delete or overwrite data, rather than only add to it. Meaningful when not read-only. */519
destructiveHint?: boolean;520
/** Repeating a call with the same arguments has no further effect. Meaningful when not read-only. */521
idempotentHint?: boolean;522
/** The tool reaches an open world of external entities, such as the web, rather than a closed domain. */523
openWorldHint?: boolean;524
}526
/** A group of related tools, such as the tools of one MCP server. Codemode tools list them together. */527
export interface ToolNamespace {528
/** For example `mcp__docs`. */529
name: string;530
/** Short summary shown once with the group in model-facing tool listings. */531
description?: string;532
/**533
* Longer usage guidance, such as MCP server instructions. Not part of tool listings; tools that534
* describe the namespace on request (codemode's `describeNamespace()`) return it.535
*/536
instructions?: string;537
}539
/** The tools of a session as {@link ToolDefinition.prepareLoadout} sees them. */540
export interface ToolLoadout {541
/** Tools declared to the model (the active tools), in order, with their original descriptions. */542
readonly declared: readonly AgentTool[];543
/** Tools callable through `ctx.executeTool()`. */544
readonly callable: readonly AgentTool[];545
/** Every registered tool. */546
readonly registered: readonly AgentTool[];547
getExposure(name: string): ToolExposure;548
getNamespace(name: string): ToolNamespace | undefined;549
}551
/** Changes {@link ToolDefinition.prepareLoadout} makes to what the model sees. */552
export interface ToolLoadoutChanges {553
/** Model-facing descriptions of declared tools, by tool name. */554
descriptions?: Readonly<Record<string, string>>;555
/**556
* Declared tools whose declarations requests leave out. They stay active and callable, and the557
* transcript still declares them, so the active set survives `/tree` and resume.558
*/559
hiddenDeclarations?: readonly string[];560
}562
/**563
* Tool definition for registerTool().564
*/565
export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = unknown, TState = any> {566
/** Tool name (used in LLM tool calls) */567
name: string;568
/** Human-readable label for UI */569
label: string;570
/** Description for LLM */571
description: string;572
/** 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. */573
promptSnippet?: string;574
/** Optional guideline bullets appended to the default system prompt Guidelines section when this tool is active. */575
promptGuidelines?: string[];576
/** Parameter schema (TypeBox) */577
parameters: TParams;578
/** Optional provider-side constrained sampling request for this tool. Set false to explicitly disable it, equivalent to leaving it undefined. */579
constrainedSampling?: false | ConstrainedSamplingConfig;580
/** Controls whether ToolExecutionComponent renders the standard colored shell or the tool renders its own framing. */581
renderShell?: "default" | "self";583
/** Optional compatibility shim to prepare raw tool call arguments before schema validation. Must return an object conforming to TParams. */584
prepareArguments?: (args: unknown) => Static<TParams>;586
/**587
* JSON Schema of `structuredContent` in successful results. Tools that declare it should always588
* set `structuredContent`; codemode scripts then receive it instead of the text content.589
*/590
outputSchema?: TSchema;592
/**593
* How the model reaches the tool. Default: `"direct"`. See {@link ToolExposure}.594
*/595
exposure?: ToolExposure;597
/** Group the tool belongs to, for example its MCP server. */598
namespace?: ToolNamespace;600
/** Hints about what the tool does, for example from an MCP server. */601
annotations?: ToolAnnotations;603
/**604
* Whether registering the tool activates it. Default: `true` for `direct` and `model-only` tools;605
* other exposures are never activated on registration. A tool with `defaultActive: false` is606
* activated by naming it in `--tools` or the `defaultTools` setting, or with `setActiveTools()`.607
*/608
defaultActive?: boolean;610
/**611
* Adjust how the loadout is presented to the model while this tool is active. Called whenever612
* the active tools change. Tools that orchestrate other tools use it, for example to list the613
* callable tools in their own description.614
*/615
prepareLoadout?: (loadout: ToolLoadout) => ToolLoadoutChanges | undefined;617
/**618
* Per-tool execution mode override.619
* - "sequential": this tool must execute one at a time with other tool calls.620
* - "parallel": this tool can execute concurrently with other tool calls.621
*622
* If omitted, the default execution mode applies.623
*/624
executionMode?: ToolExecutionMode;626
/** Execute the tool. */627
execute(628
toolCallId: string,629
params: Static<TParams>,630
signal: AbortSignal | undefined,631
onUpdate: AgentToolUpdateCallback<TDetails> | undefined,632
ctx: ExtensionToolContext,633
): Promise<AgentToolResult<TDetails>>;635
/** Custom rendering for tool call display */636
renderCall?: (args: Static<TParams>, theme: Theme, context: ToolRenderContext<TState, Static<TParams>>) => Component;638
/** Custom rendering for tool result display */639
renderResult?: (640
result: AgentToolResult<TDetails>,641
options: ToolRenderResultOptions,642
theme: Theme,643
context: ToolRenderContext<TState, Static<TParams>>,644
) => Component;645
}647
type AnyToolDefinition = ToolDefinition<any, any, any>;649
/**650
* Preserve parameter inference for standalone tool definitions.651
*652
* Use this when assigning a tool to a variable or passing it through arrays such653
* as `customTools`, where contextual typing would otherwise widen params to654
* `unknown`.655
*/656
export function defineTool<TParams extends TSchema, TDetails = unknown, TState = any>(657
tool: ToolDefinition<TParams, TDetails, TState>,658
): ToolDefinition<TParams, TDetails, TState> & AnyToolDefinition {659
return tool as ToolDefinition<TParams, TDetails, TState> & AnyToolDefinition;660
}662
// ============================================================================663
// Startup/Resource Events664
// ============================================================================666
export interface ProjectTrustEvent {667
type: "project_trust";668
cwd: string;669
}671
export type ProjectTrustEventDecision = "yes" | "no" | "undecided";673
export interface ProjectTrustEventResult {674
trusted: ProjectTrustEventDecision;675
remember?: boolean;676
}678
export interface ProjectTrustContext {679
cwd: string;680
mode: ExtensionMode;681
hasUI: boolean;682
ui: Pick<ExtensionUIContext, "select" | "confirm" | "input" | "notify">;683
}685
export type ProjectTrustHandler = (686
event: ProjectTrustEvent,687
ctx: ProjectTrustContext,688
) => Promise<ProjectTrustEventResult> | ProjectTrustEventResult;690
/** Fired after session_start to allow extensions to provide additional resource paths. */691
export interface ResourcesDiscoverEvent {692
type: "resources_discover";693
cwd: string;694
reason: "startup" | "reload";695
}697
/** Result from resources_discover event handler */698
export interface ResourcesDiscoverResult {699
skillPaths?: string[];700
promptPaths?: string[];701
themePaths?: string[];702
}704
/**705
* Fired when an extension registers or unregisters an MCP server after the extensions are bound706
* (see {@link ExtensionAPI.registerMcpServer}). Servers registered while extensions load are read707
* with `pi.getMcpServers()` on `session_start`. Handling this event marks an extension as the one708
* that connects registered servers.709
*/710
export interface McpServersChangeEvent {711
type: "mcp_servers_change";712
/** Every registered server after the change. */713
servers: RegisteredMcpServer[];714
}716
// ============================================================================717
// Session Events718
// ============================================================================720
/** Fired when a session is started, loaded, or reloaded */721
export interface SessionStartEvent {722
type: "session_start";723
/** Why this session start happened. */724
reason: "startup" | "reload" | "new" | "resume" | "fork";725
/** Previously active session file. Present for "new", "resume", and "fork". */726
previousSessionFile?: string;727
}729
/** Fired when the current session metadata changes. */730
export interface SessionInfoChangedEvent {731
type: "session_info_changed";732
/** Current normalized session name. Undefined when the name is cleared. */733
name: string | undefined;734
}736
/** Fired before switching to another session (can be cancelled) */737
export interface SessionBeforeSwitchEvent {738
type: "session_before_switch";739
reason: "new" | "resume";740
targetSessionFile?: string;741
}743
/** Fired before forking a session (can be cancelled) */744
export interface SessionBeforeForkEvent {745
type: "session_before_fork";746
entryId: string;747
position: "before" | "at";748
}750
/** Fired before context compaction (can be cancelled or customized) */751
export interface SessionBeforeCompactEvent {752
type: "session_before_compact";753
preparation: CompactionPreparation;754
branchEntries: SessionEntry[];755
customInstructions?: string;756
/** What triggered the compaction: manual /compact, the context threshold, or context overflow recovery */757
reason: "manual" | "threshold" | "overflow";758
/** True when the aborted turn is retried after this compaction (overflow recovery) */759
willRetry: boolean;760
signal: AbortSignal;761
}763
/** Fired after context compaction succeeds */764
export interface SessionCompactEvent {765
type: "session_compact";766
compactionEntry: CompactionEntry;767
fromExtension: boolean;768
/** What triggered the compaction: manual /compact, the context threshold, or context overflow recovery */769
reason: "manual" | "threshold" | "overflow";770
/** True when the aborted turn is retried after this compaction (overflow recovery) */771
willRetry: boolean;772
}774
/** Fired after context compaction fails or is aborted */775
export interface SessionCompactFailedEvent {776
type: "session_compact_failed";777
/** What triggered the compaction: manual /compact, the context threshold, or context overflow recovery */778
reason: "manual" | "threshold" | "overflow";779
/** Error text when compaction failed for a non-abort reason. */780
errorMessage?: string;781
/** True when compaction was cancelled or aborted. */782
aborted: boolean;783
/** True when the aborted turn would have been retried after this compaction (overflow recovery) */784
willRetry: boolean;785
/** True when the failing compaction content came from a session_before_compact handler. */786
fromExtension: boolean;787
}789
/** Fired before an extension runtime is torn down due to quit, reload, or session replacement. */790
export interface SessionShutdownEvent {791
type: "session_shutdown";792
reason: "quit" | "reload" | "new" | "resume" | "fork";793
/** Destination session file when shutting down due to session replacement. */794
targetSessionFile?: string;795
}797
/** Preparation data for tree navigation */798
export interface TreePreparation {799
targetId: string;800
oldLeafId: string | null;801
commonAncestorId: string | null;802
entriesToSummarize: SessionEntry[];803
userWantsSummary: boolean;804
/** Custom instructions for summarization */805
customInstructions?: string;806
/** If true, customInstructions replaces the default prompt instead of being appended */807
replaceInstructions?: boolean;808
/** Label to attach to the branch summary entry */809
label?: string;810
}812
/** Fired before navigating in the session tree (can be cancelled) */813
export interface SessionBeforeTreeEvent {814
type: "session_before_tree";815
preparation: TreePreparation;816
signal: AbortSignal;817
}819
/** Fired after navigating in the session tree */820
export interface SessionTreeEvent {821
type: "session_tree";822
newLeafId: string | null;823
oldLeafId: string | null;824
summaryEntry?: BranchSummaryEntry;825
fromExtension?: boolean;826
}828
export type SessionEvent =829
| SessionStartEvent830
| SessionInfoChangedEvent831
| SessionBeforeSwitchEvent832
| SessionBeforeForkEvent833
| SessionBeforeCompactEvent834
| SessionCompactEvent835
| SessionCompactFailedEvent836
| SessionShutdownEvent837
| SessionBeforeTreeEvent838
| SessionTreeEvent;840
// ============================================================================841
// Agent Events842
// ============================================================================844
/**845
* Fired before each LLM call. Can modify messages.846
*847
* `messages` holds the conversation without system messages. The prompt and tool state848
* belong to Pi: it restores them after the handler returns, so a handler cannot drop849
* them and does not need to preserve them.850
*/851
export interface ContextEvent {852
type: "context";853
messages: AgentMessage[];854
}856
/**857
* Fired before each LLM call, after every `context` handler has run and Pi has restored858
* the prompt and tool state. `messages` is the full transcript including system messages,859
* and the result is sent as returned: the handler owns the prompt and tool declarations.860
*/861
export interface ContextWithSystemEvent {862
type: "context_with_system";863
messages: AgentMessage[];864
}866
/** Fired before a provider request is sent. Can replace the payload. */867
export interface BeforeProviderRequestEvent {868
type: "before_provider_request";869
payload: unknown;870
}872
/**873
* Fired after request headers are assembled, before the provider HTTP call.874
* Handlers mutate `headers` in place (e.g. to inject tracing/session headers);875
* the return value is ignored. A `null` value deletes that header.876
*/877
export interface BeforeProviderHeadersEvent {878
type: "before_provider_headers";879
headers: ProviderHeaders;880
}882
/** Fired after a provider response is received and before the response stream is consumed. */883
export interface AfterProviderResponseEvent {884
type: "after_provider_response";885
status: number;886
headers: Record<string, string>;887
}889
/** Fired for a parsed provider stream event before Pi normalizes it. */890
export interface ProviderStreamEvent {891
type: "provider_stream_event";892
provider: ProviderId;893
api: Api;894
model: string;895
data: unknown;896
}898
/** Fired after user submits prompt but before agent loop. */899
export interface BeforeAgentStartEvent {900
type: "before_agent_start";901
/** The raw user prompt text (after expansion). */902
prompt: string;903
/** Images attached to the user prompt, if any. */904
images?: ImageContent[];905
/** The current system prompt, rendered from systemPromptOptions and earlier handler changes. */906
readonly systemPrompt: string;907
/** Mutable prompt sections. Later handlers observe mutations made by earlier handlers. */908
systemPromptOptions: NormalizedBuildSystemPromptOptions;909
}911
/** Fired when an agent loop starts */912
export interface AgentStartEvent {913
type: "agent_start";914
}916
/** Fired when an agent loop ends */917
export interface AgentEndEvent {918
type: "agent_end";919
messages: AgentMessage[];920
}922
export type AgentActivityOutcome = "completed" | "aborted" | "error";924
export interface CustomEntryDraft {925
type: "custom";926
customType: string;927
data?: unknown;928
}930
export interface CustomMessageEntryDraft {931
type: "custom_message";932
customType: string;933
content: string | (TextContent | ImageContent)[];934
display: boolean;935
details?: unknown;936
}938
export interface ContextEditEntryDraft {939
type: "context_edit";940
targetId: string;941
replacement: ContextEditEntry["replacement"];942
}944
export interface CompactionEntryDraft {945
type: "compaction";946
summary: string;947
/** Null creates a self-retaining compaction that keeps no preceding entries. */948
firstKeptEntryId: string | null;949
details?: unknown;950
usage?: Usage;951
}953
export type SessionBoundaryDraft =954
| CustomEntryDraft955
| CustomMessageEntryDraft956
| ContextEditEntryDraft957
| CompactionEntryDraft;959
export interface BoundaryContextPreview {960
contextEntries: ProjectedSessionEntry[];961
contextMessages: AgentMessage[];962
llmMessages: Message[];963
pendingMessages: AgentMessage[];964
canContinue: boolean;965
}967
export interface BoundaryState {968
entries: SessionBoundaryDraft[];969
continue: boolean;970
context: BoundaryContextPreview;971
outcome: AgentActivityOutcome;972
}974
export interface BoundaryResult {975
entries?: SessionBoundaryDraft[];976
continue?: boolean;977
}979
/** Fired before final settlement. May append entries and ensure one next provider request. */980
export interface AgentBeforeSettleEvent extends BoundaryState {981
type: "agent_before_settle";982
}984
/** Fired after an agent run has fully settled and no automatic retry, compaction, or queued continuation will run. */985
export interface AgentSettledEvent {986
type: "agent_settled";987
}989
export type UIPromptKind = "select" | "confirm" | "input" | "editor" | "custom";991
/** Fired when Pi starts waiting on a blocking user-facing extension UI prompt. */992
export interface UIPromptStartEvent {993
type: "ui_prompt_start";994
reason: "ui_prompt";995
kind: UIPromptKind;996
title?: string;997
}999
/** Fired when Pi is no longer waiting on a blocking user-facing extension UI prompt. */1000
export interface UIPromptEndEvent {1001
type: "ui_prompt_end";1002
reason: "ui_prompt";1003
kind: UIPromptKind;1004
title?: string;1005
}1007
/** Fired at the start of each turn */1008
export interface TurnStartEvent {1009
type: "turn_start";1010
turnIndex: number;1011
timestamp: number;1012
}1014
/** Fired at the end of each turn */1015
export interface TurnEndEvent extends BoundaryState {1016
type: "turn_end";1017
turnIndex: number;1018
message: AgentMessage;1019
toolResults: ToolResultMessage[];1020
messageEntryId: string;1021
toolResultEntryIds: string[];1022
}1024
/** Fired when a message starts (user, assistant, or toolResult) */1025
export interface MessageStartEvent {1026
type: "message_start";1027
message: AgentMessage;1028
}1030
/** Fired during assistant message streaming with token-by-token updates */1031
export interface MessageUpdateEvent {1032
type: "message_update";1033
message: AgentMessage;1034
assistantMessageEvent: AssistantMessageEvent;1035
}1037
/** Fired when a message ends */1038
export interface MessageEndEvent {1039
type: "message_end";1040
message: AgentMessage;1041
}1043
/** Fired when a tool starts executing */1044
export interface ToolExecutionStartEvent {1045
type: "tool_execution_start";1046
toolCallId: string;1047
toolName: string;1048
args: any;1049
/** Set when another tool (for example a codemode script) made this call. */1050
parentToolCallId?: string;1051
}1053
/** Fired during tool execution with partial/streaming output */1054
export interface ToolExecutionUpdateEvent {1055
type: "tool_execution_update";1056
toolCallId: string;1057
toolName: string;1058
args: any;1059
partialResult: any;1060
/** Set when another tool (for example a codemode script) made this call. */1061
parentToolCallId?: string;1062
}1064
/** Fired when a tool finishes executing */1065
export interface ToolExecutionEndEvent {1066
type: "tool_execution_end";1067
toolCallId: string;1068
toolName: string;1069
result: any;1070
isError: boolean;1071
/** Set when another tool (for example a codemode script) made this call. */1072
parentToolCallId?: string;1073
}1075
// ============================================================================1076
// Model Events1077
// ============================================================================1079
export type ModelSelectSource = "set" | "cycle" | "restore";1081
/** Fired when a new model is selected */1082
export interface ModelSelectEvent {1083
type: "model_select";1084
model: Model<any>;1085
previousModel: Model<any> | undefined;1086
source: ModelSelectSource;1087
}1089
/** Fired when a new thinking level is selected */1090
export interface ThinkingLevelSelectEvent {1091
type: "thinking_level_select";1092
level: ThinkingLevel;1093
previousLevel: ThinkingLevel;1094
}1096
// ============================================================================1097
// User Bash Events1098
// ============================================================================1100
/** Fired when user executes a bash command via ! or !! prefix */1101
export interface UserBashEvent {1102
type: "user_bash";1103
/** The command to execute */1104
command: string;1105
/** True if !! prefix was used (excluded from LLM context) */1106
excludeFromContext: boolean;1107
/** Current working directory */1108
cwd: string;1109
}1111
// ============================================================================1112
// Input Events1113
// ============================================================================1115
/** Source of user input */1116
export type InputSource = "interactive" | "rpc" | "extension";1118
/** Fired when user input is received, before agent processing */1119
export interface InputEvent {1120
type: "input";1121
/** The input text */1122
text: string;1123
/** Attached images, if any */1124
images?: ImageContent[];1125
/** Where the input came from */1126
source: InputSource;1127
/** How the input will be delivered during streaming, or undefined when idle */1128
streamingBehavior?: "steer" | "followUp";1129
}1131
/** Result from input event handler */1132
export type InputEventResult =1133
| { action: "continue" }1134
| { action: "transform"; text: string; images?: ImageContent[] }1135
| { action: "handled" };1137
// ============================================================================1138
// Tool Events1139
// ============================================================================1141
interface ToolCallEventBase {1142
type: "tool_call";1143
/**1144
* The call's id. For calls another tool made (with `parentToolCallId` set), pi assigns1145
* `<parent id>/<n>`; such ids never appear as tool calls or tool results in the transcript, only1146
* in the parent result's `nestedCalls` record.1147
*/1148
toolCallId: string;1149
/** Set when another tool (for example a codemode script) issued this call. */1150
parentToolCallId?: string;1151
}1153
export interface BashToolCallEvent extends ToolCallEventBase {1154
toolName: "bash";1155
input: BashToolInput;1156
}1158
export interface PowerShellToolCallEvent extends ToolCallEventBase {1159
toolName: "powershell";1160
input: PowerShellToolInput;1161
}1163
export interface ReadToolCallEvent extends ToolCallEventBase {1164
toolName: "read";1165
input: ReadToolInput;1166
}1168
export interface EditToolCallEvent extends ToolCallEventBase {1169
toolName: "edit";1170
input: EditToolInput;1171
}1173
export interface WriteToolCallEvent extends ToolCallEventBase {1174
toolName: "write";1175
input: WriteToolInput;1176
}1178
export interface GrepToolCallEvent extends ToolCallEventBase {1179
toolName: "grep";1180
input: GrepToolInput;1181
}1183
export interface FindToolCallEvent extends ToolCallEventBase {1184
toolName: "find";1185
input: FindToolInput;1186
}1188
export interface LsToolCallEvent extends ToolCallEventBase {1189
toolName: "ls";1190
input: LsToolInput;1191
}1193
export interface CustomToolCallEvent extends ToolCallEventBase {1194
toolName: string;1195
input: Record<string, unknown>;1196
}1198
/**1199
* Fired before a tool executes. Can block.1200
*1201
* `event.input` is mutable. Mutate it in place to patch tool arguments before execution.1202
* Later `tool_call` handlers see earlier mutations. No re-validation is performed after mutation.1203
*/1204
export type ToolCallEvent =1205
| BashToolCallEvent1206
| PowerShellToolCallEvent1207
| ReadToolCallEvent1208
| EditToolCallEvent1209
| WriteToolCallEvent1210
| GrepToolCallEvent1211
| FindToolCallEvent1212
| LsToolCallEvent1213
| CustomToolCallEvent;1215
interface ToolResultEventBase {1216
type: "tool_result";1217
/** The call's id; `<parent id>/<n>` for nested calls, see `ToolCallEvent`. */1218
toolCallId: string;1219
/** Set when another tool (for example a codemode script) issued this call. */1220
parentToolCallId?: string;1221
input: Record<string, unknown>;1222
content: (TextContent | ImageContent)[];1223
/**1224
* Machine-readable result for tools that declare an `outputSchema`. Handlers that redact1225
* `content` should also replace this; replacing `content` alone drops it.1226
*/1227
structuredContent?: JsonValue;1228
isError: boolean;1229
/** Usage from the tool execution itself, if available. */1230
usage?: Usage;1231
}1233
export interface BashToolResultEvent extends ToolResultEventBase {1234
toolName: "bash";1235
details: BashToolDetails | undefined;1236
}1238
export interface PowerShellToolResultEvent extends ToolResultEventBase {1239
toolName: "powershell";1240
details: PowerShellToolDetails | undefined;1241
}1243
export interface ReadToolResultEvent extends ToolResultEventBase {1244
toolName: "read";1245
details: ReadToolDetails | undefined;1246
}1248
export interface EditToolResultEvent extends ToolResultEventBase {1249
toolName: "edit";1250
details: EditToolDetails | undefined;1251
}1253
export interface WriteToolResultEvent extends ToolResultEventBase {1254
toolName: "write";1255
details: undefined;1256
}1258
export interface GrepToolResultEvent extends ToolResultEventBase {1259
toolName: "grep";1260
details: GrepToolDetails | undefined;1261
}1263
export interface FindToolResultEvent extends ToolResultEventBase {1264
toolName: "find";1265
details: FindToolDetails | undefined;1266
}1268
export interface LsToolResultEvent extends ToolResultEventBase {1269
toolName: "ls";1270
details: LsToolDetails | undefined;1271
}1273
export interface CustomToolResultEvent extends ToolResultEventBase {1274
toolName: string;1275
details: unknown;1276
}1278
/** Fired after a tool executes. Can modify result. */1279
export type ToolResultEvent =1280
| BashToolResultEvent1281
| PowerShellToolResultEvent1282
| ReadToolResultEvent1283
| EditToolResultEvent1284
| WriteToolResultEvent1285
| GrepToolResultEvent1286
| FindToolResultEvent1287
| LsToolResultEvent1288
| CustomToolResultEvent;1290
// Type guards for ToolResultEvent1291
export function isBashToolResult(e: ToolResultEvent): e is BashToolResultEvent {1292
return e.toolName === "bash";1293
}1294
export function isPowerShellToolResult(e: ToolResultEvent): e is PowerShellToolResultEvent {1295
return e.toolName === "powershell";1296
}1297
export function isReadToolResult(e: ToolResultEvent): e is ReadToolResultEvent {1298
return e.toolName === "read";1299
}1300
export function isEditToolResult(e: ToolResultEvent): e is EditToolResultEvent {1301
return e.toolName === "edit";1302
}1303
export function isWriteToolResult(e: ToolResultEvent): e is WriteToolResultEvent {1304
return e.toolName === "write";1305
}1306
export function isGrepToolResult(e: ToolResultEvent): e is GrepToolResultEvent {1307
return e.toolName === "grep";1308
}1309
export function isFindToolResult(e: ToolResultEvent): e is FindToolResultEvent {1310
return e.toolName === "find";1311
}1312
export function isLsToolResult(e: ToolResultEvent): e is LsToolResultEvent {1313
return e.toolName === "ls";1314
}1316
/**1317
* Type guard for narrowing ToolCallEvent by tool name.1318
*1319
* Built-in tools narrow automatically (no type params needed):1320
* ```ts1321
* if (isToolCallEventType("bash", event)) {1322
* event.input.command; // string1323
* }1324
* ```1325
*1326
* Custom tools require explicit type parameters:1327
* ```ts1328
* if (isToolCallEventType<"my_tool", MyToolInput>("my_tool", event)) {1329
* event.input.action; // typed1330
* }1331
* ```1332
*1333
* Note: Direct narrowing via `event.toolName === "bash"` doesn't work because1334
* CustomToolCallEvent.toolName is `string` which overlaps with all literals.1335
*/1336
export function isToolCallEventType(toolName: "bash", event: ToolCallEvent): event is BashToolCallEvent;1337
export function isToolCallEventType(toolName: "powershell", event: ToolCallEvent): event is PowerShellToolCallEvent;1338
export function isToolCallEventType(toolName: "read", event: ToolCallEvent): event is ReadToolCallEvent;1339
export function isToolCallEventType(toolName: "edit", event: ToolCallEvent): event is EditToolCallEvent;1340
export function isToolCallEventType(toolName: "write", event: ToolCallEvent): event is WriteToolCallEvent;1341
export function isToolCallEventType(toolName: "grep", event: ToolCallEvent): event is GrepToolCallEvent;1342
export function isToolCallEventType(toolName: "find", event: ToolCallEvent): event is FindToolCallEvent;1343
export function isToolCallEventType(toolName: "ls", event: ToolCallEvent): event is LsToolCallEvent;1344
export function isToolCallEventType<TName extends string, TInput extends Record<string, unknown>>(1345
toolName: TName,1346
event: ToolCallEvent,1347
): event is ToolCallEvent & { toolName: TName; input: TInput };1348
export function isToolCallEventType(toolName: string, event: ToolCallEvent): boolean {1349
return event.toolName === toolName;1350
}1352
/** Union of all event types */1353
export type ExtensionEvent =1354
| ProjectTrustEvent1355
| ResourcesDiscoverEvent1356
| McpServersChangeEvent1357
| SessionEvent1358
| ContextEvent1359
| ContextWithSystemEvent1360
| CacheWarmingDecisionEvent1361
| BeforeProviderRequestEvent1362
| BeforeProviderHeadersEvent1363
| AfterProviderResponseEvent1364
| ProviderStreamEvent1365
| BeforeAgentStartEvent1366
| AgentStartEvent1367
| AgentEndEvent1368
| AgentBeforeSettleEvent1369
| AgentSettledEvent1370
| UIPromptStartEvent1371
| UIPromptEndEvent1372
| TurnStartEvent1373
| TurnEndEvent1374
| MessageStartEvent1375
| MessageUpdateEvent1376
| MessageEndEvent1377
| ToolExecutionStartEvent1378
| ToolExecutionUpdateEvent1379
| ToolExecutionEndEvent1380
| ModelSelectEvent1381
| ThinkingLevelSelectEvent1382
| UserBashEvent1383
| InputEvent1384
| ToolCallEvent1385
| ToolResultEvent;1387
// ============================================================================1388
// Event Results1389
// ============================================================================1391
export interface ContextEventResult {1392
messages?: AgentMessage[];1393
}1395
export type TurnEndEventResult = BoundaryResult;1396
export type AgentBeforeSettleEventResult = BoundaryResult;1398
export type BeforeProviderRequestEventResult = unknown;1400
export type { CacheWarmingDecisionEvent, CacheWarmingDecisionEventResult } from "../cache-warmer.ts";1402
export interface ToolCallEventResult {1403
/** Block tool execution. To modify arguments, mutate `event.input` in place instead. */1404
block?: boolean;1405
reason?: string;1406
/**1407
* Hint that the agent should stop after the current tool batch when this call is blocked.1408
* Early termination only happens when every finalized tool result in the batch sets this to true.1409
*/1410
terminate?: boolean;1411
}1413
/** Result from user_bash event handler */1414
export type UserBashEventResult =1415
| {1416
/** Custom operations to use for execution */1417
operations: BashOperations;1418
result?: never;1419
}1420
| {1421
operations?: never;1422
/** Full replacement: extension handled execution, use this result */1423
result: BashResult;1424
};1426
/**1427
* Changes a `tool_result` handler makes. Omitted fields stay as they are, except that replacing1428
* `content` without returning `structuredContent` drops the structured content, because it may no1429
* longer match. Return it along with `content` to keep it.1430
*/1431
export interface ToolResultEventResult {1432
content?: (TextContent | ImageContent)[];1433
details?: unknown;1434
structuredContent?: JsonValue;1435
isError?: boolean;1436
usage?: Usage;1437
}1439
export interface MessageEndEventResult {1440
/** Replace the finalized message. The replacement must keep the original message role. */1441
message?: AgentMessage;1442
}1444
export interface BeforeAgentStartEventResult {1445
message?: Pick<CustomMessage, "customType" | "content" | "display" | "details">;1446
/** Replace the complete system prompt for this turn. Later handlers observe this exact override. */1447
systemPrompt?: string;1448
}1450
export interface SessionBeforeSwitchResult {1451
cancel?: boolean;1452
}1454
export interface SessionBeforeForkResult {1455
cancel?: boolean;1456
skipConversationRestore?: boolean;1457
}1459
export interface SessionBeforeCompactResult {1460
cancel?: boolean;1461
compaction?: CompactionResult;1462
}1464
export interface SessionBeforeTreeResult {1465
cancel?: boolean;1466
summary?: {1467
summary: string;1468
details?: unknown;1469
usage?: Usage;1470
};1471
/** Override custom instructions for summarization */1472
customInstructions?: string;1473
/** Override whether customInstructions replaces the default prompt */1474
replaceInstructions?: boolean;1475
/** Override label to attach to the branch summary entry */1476
label?: string;1477
}1479
// ============================================================================1480
// Message and Entry Rendering1481
// ============================================================================1483
export interface MessageRenderOptions {1484
expanded: boolean;1485
/** Horizontal padding configured by the outputPad setting. */1486
outputPad: number;1487
}1489
export interface MarkdownTransformContext {1490
messageType: "user" | "assistant" | "assistant-thinking";1491
isStreaming: boolean;1492
availableWidth: number;1493
}1495
export type MarkdownTransformer = (markdown: string, context: MarkdownTransformContext) => string;1497
export interface EntryRenderOptions {1498
expanded: boolean;1499
}1501
export type MessageRenderer<T = unknown> = (1502
message: CustomMessage<T>,1503
options: MessageRenderOptions,1504
theme: Theme,1505
) => Component | undefined;1507
export type EntryRenderer<T = unknown> = (1508
entry: CustomEntry<T>,1509
options: EntryRenderOptions,1510
theme: Theme,1511
) => Component | undefined;1513
// ============================================================================1514
// Command Registration1515
// ============================================================================1517
export interface RegisteredCommand {1518
name: string;1519
sourceInfo: SourceInfo;1520
description?: string;1521
getArgumentCompletions?: (argumentPrefix: string) => AutocompleteItem[] | null | Promise<AutocompleteItem[] | null>;1522
handler: (args: string, ctx: ExtensionCommandContext) => Promise<void>;1523
}1525
export interface ResolvedCommand extends RegisteredCommand {1526
invocationName: string;1527
}1529
// ============================================================================1530
// Extension API1531
// ============================================================================1533
/** Handler function type for events */1534
// biome-ignore lint/suspicious/noConfusingVoidType: void allows bare return statements1535
export type ExtensionHandler<E, R = undefined> = (event: E, ctx: ExtensionContext) => Promise<R | void> | R | void;1537
/**1538
* ExtensionAPI passed to extension factory functions.1539
*/1540
export interface ExtensionAPI {1541
// =========================================================================1542
// Event Subscription1543
// =========================================================================1545
on(event: "project_trust", handler: ProjectTrustHandler): () => void;1546
on(1547
event: "resources_discover",1548
handler: ExtensionHandler<ResourcesDiscoverEvent, ResourcesDiscoverResult>,1549
): () => void;1550
on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): () => void;1551
on(event: "session_info_changed", handler: ExtensionHandler<SessionInfoChangedEvent>): () => void;1552
on(1553
event: "session_before_switch",1554
handler: ExtensionHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>,1555
): () => void;1556
on(1557
event: "session_before_fork",1558
handler: ExtensionHandler<SessionBeforeForkEvent, SessionBeforeForkResult>,1559
): () => void;1560
on(1561
event: "session_before_compact",1562
handler: ExtensionHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>,1563
): () => void;1564
on(event: "session_compact", handler: ExtensionHandler<SessionCompactEvent>): () => void;1565
on(event: "session_compact_failed", handler: ExtensionHandler<SessionCompactFailedEvent>): () => void;1566
on(event: "session_shutdown", handler: ExtensionHandler<SessionShutdownEvent>): () => void;1567
on(event: "mcp_servers_change", handler: ExtensionHandler<McpServersChangeEvent>): () => void;1568
on(1569
event: "session_before_tree",1570
handler: ExtensionHandler<SessionBeforeTreeEvent, SessionBeforeTreeResult>,1571
): () => void;1572
on(event: "session_tree", handler: ExtensionHandler<SessionTreeEvent>): () => void;1573
on(event: "context", handler: ExtensionHandler<ContextEvent, ContextEventResult>): () => void;1574
on(event: "context_with_system", handler: ExtensionHandler<ContextWithSystemEvent, ContextEventResult>): () => void;1575
on(1576
event: "cache_warming_decision",1577
handler: ExtensionHandler<CacheWarmingDecisionEvent, CacheWarmingDecisionEventResult>,1578
): () => void;1579
on(1580
event: "before_provider_request",1581
handler: ExtensionHandler<BeforeProviderRequestEvent, BeforeProviderRequestEventResult>,1582
): () => void;1583
on(event: "before_provider_headers", handler: ExtensionHandler<BeforeProviderHeadersEvent>): () => void;1584
on(event: "after_provider_response", handler: ExtensionHandler<AfterProviderResponseEvent>): () => void;1585
on(event: "provider_stream_event", handler: ExtensionHandler<ProviderStreamEvent>): () => void;1586
on(1587
event: "before_agent_start",1588
handler: ExtensionHandler<BeforeAgentStartEvent, BeforeAgentStartEventResult>,1589
): () => void;1590
on(event: "agent_start", handler: ExtensionHandler<AgentStartEvent>): () => void;1591
on(event: "agent_end", handler: ExtensionHandler<AgentEndEvent>): () => void;1592
on(1593
event: "agent_before_settle",1594
handler: ExtensionHandler<AgentBeforeSettleEvent, AgentBeforeSettleEventResult>,1595
): () => void;1596
on(event: "agent_settled", handler: ExtensionHandler<AgentSettledEvent>): () => void;1597
on(event: "ui_prompt_start", handler: ExtensionHandler<UIPromptStartEvent>): () => void;1598
on(event: "ui_prompt_end", handler: ExtensionHandler<UIPromptEndEvent>): () => void;1599
on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): () => void;1600
on(event: "turn_end", handler: ExtensionHandler<TurnEndEvent, TurnEndEventResult>): () => void;1601
on(event: "message_start", handler: ExtensionHandler<MessageStartEvent>): () => void;1602
on(event: "message_update", handler: ExtensionHandler<MessageUpdateEvent>): () => void;1603
on(event: "message_end", handler: ExtensionHandler<MessageEndEvent, MessageEndEventResult>): () => void;1604
on(event: "tool_execution_start", handler: ExtensionHandler<ToolExecutionStartEvent>): () => void;1605
on(event: "tool_execution_update", handler: ExtensionHandler<ToolExecutionUpdateEvent>): () => void;1606
on(event: "tool_execution_end", handler: ExtensionHandler<ToolExecutionEndEvent>): () => void;1607
on(event: "model_select", handler: ExtensionHandler<ModelSelectEvent>): () => void;1608
on(event: "thinking_level_select", handler: ExtensionHandler<ThinkingLevelSelectEvent>): () => void;1609
on(event: "tool_call", handler: ExtensionHandler<ToolCallEvent, ToolCallEventResult>): () => void;1610
on(event: "tool_result", handler: ExtensionHandler<ToolResultEvent, ToolResultEventResult>): () => void;1611
on(event: "user_bash", handler: ExtensionHandler<UserBashEvent, UserBashEventResult>): () => void;1612
on(event: "input", handler: ExtensionHandler<InputEvent, InputEventResult>): () => void;1614
// =========================================================================1615
// Tool Registration1616
// =========================================================================1618
/** Register a tool that the LLM can call. */1619
registerTool<TParams extends TSchema = TSchema, TDetails = unknown, TState = any>(1620
tool: ToolDefinition<TParams, TDetails, TState>,1621
): void;1623
// =========================================================================1624
// Command, Shortcut, Flag Registration1625
// =========================================================================1627
/** Register a custom command. */1628
registerCommand(name: string, options: Omit<RegisteredCommand, "name" | "sourceInfo">): void;1630
/** Register a keyboard shortcut. */1631
registerShortcut(1632
shortcut: KeyId,1633
options: {1634
description?: string;1635
handler: (ctx: ExtensionContext) => Promise<void> | void;1636
},1637
): void;1639
/** Register a CLI flag. */1640
registerFlag(1641
name: string,1642
options:1643
| {1644
description?: string;1645
type: "boolean";1646
default?: boolean;1647
}1648
| {1649
description?: string;1650
type: "string";1651
default?: string;1652
},1653
): void;1655
/** Get the value of a registered CLI flag. */1656
getFlag(name: string): boolean | string | undefined;1658
// =========================================================================1659
// Message Rendering1660
// =========================================================================1662
/** Register a custom renderer for CustomMessageEntry. */1663
registerMessageRenderer<T = unknown>(customType: string, renderer: MessageRenderer<T>): void;1665
/** Register a transformer for user and assistant Markdown before Pi renders it in the interactive transcript. */1666
registerMarkdownTransformer(transformer: MarkdownTransformer): void;1668
/** Register a custom renderer for CustomEntry. Custom entries do not participate in LLM context. */1669
registerEntryRenderer<T = unknown>(customType: string, renderer: EntryRenderer<T>): void;1671
// =========================================================================1672
// Actions1673
// =========================================================================1675
/** Send a custom message to the session. */1676
sendMessage<T = unknown>(1677
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,1678
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },1679
): void;1681
/**1682
* Send a user message to the agent. Always triggers a turn.1683
* When the agent is streaming, use deliverAs to specify how to queue the message.1684
* Set expandPromptTemplates to dispatch extension commands and expand skill commands and prompt templates.1685
*/1686
sendUserMessage(1687
content: string | (TextContent | ImageContent)[],1688
options?: { deliverAs?: "steer" | "followUp"; expandPromptTemplates?: boolean },1689
): void;1691
/** Append a custom entry to the session for state persistence (not sent to LLM). */1692
appendEntry<T = unknown>(customType: string, data?: T): void;1694
// =========================================================================1695
// Session Metadata1696
// =========================================================================1698
/** Set the session display name (shown in session selector). */1699
setSessionName(name: string): void;1701
/** Get the current session name, if set. */1702
getSessionName(): string | undefined;1704
/** Set or clear a label on an entry. Labels are user-defined markers for bookmarking/navigation. */1705
setLabel(entryId: string, label: string | undefined): void;1707
/** Execute a shell command. */1708
exec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;1710
/** Get the names of the active tools, which are the tools declared to the model. */1711
getActiveTools(): string[];1713
/** Get all configured tools with parameter schema, prompt guidelines, exposure, and source metadata. */1714
getAllTools(): ToolInfo[];1716
/** Get a copy of the effective settings (global and project settings merged, with overrides). */1717
getSettings(): Settings;1719
/**1720
* Set the active tools by name. Unknown and `hidden` tools are ignored. Tools with `codemode` or1721
* `deferred` exposure stay callable from codemode scripts whether active or not.1722
*/1723
setActiveTools(toolNames: string[]): void;1725
/** Get available slash commands in the current session. */1726
getCommands(): SlashCommandInfo[];1728
// =========================================================================1729
// Model and Thinking Level1730
// =========================================================================1732
/**1733
* Set the model for the current session without changing the configured default for new sessions.1734
* Returns false if authentication is not configured for the model's provider.1735
*/1736
setModel(model: Model<any>): Promise<boolean>;1738
/** Get current thinking level. */1739
getThinkingLevel(): ThinkingLevel;1741
/**1742
* Set the thinking level (clamped to model capabilities) for the current session without changing the configured default1743
* for new sessions.1744
*/1745
setThinkingLevel(level: ThinkingLevel): void;1747
// =========================================================================1748
// Provider Registration1749
// =========================================================================1751
/**1752
* Register or override a model provider.1753
*1754
* If `models` is provided: replaces all existing models for this provider.1755
* If only `baseUrl` is provided: overrides the URL for existing models.1756
* If `oauth` is provided: registers OAuth provider for /login support.1757
* If `streamSimple` is provided: registers a custom API stream handler.1758
*1759
* During initial extension load this call is queued and applied once the1760
* runner has bound its context. After that it takes effect immediately, so1761
* it is safe to call from command handlers or event callbacks without1762
* requiring a `/reload`.1763
*1764
* @example1765
* // Register a new provider with custom models1766
* pi.registerProvider("my-proxy", {1767
* baseUrl: "https://proxy.example.com",1768
* apiKey: "$PROXY_API_KEY",1769
* api: "anthropic-messages",1770
* models: [1771
* {1772
* id: "claude-sonnet-4-20250514",1773
* name: "Claude 4 Sonnet (proxy)",1774
* reasoning: false,1775
* input: ["text", "image"],1776
* cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },1777
* contextWindow: 200000,1778
* maxTokens: 163841779
* }1780
* ]1781
* });1782
*1783
* @example1784
* // Override baseUrl for an existing provider1785
* pi.registerProvider("anthropic", {1786
* baseUrl: "https://proxy.example.com"1787
* });1788
*1789
* @example1790
* // Register provider with OAuth support1791
* pi.registerProvider("corporate-ai", {1792
* baseUrl: "https://ai.corp.com",1793
* api: "openai-responses",1794
* models: [...],1795
* oauth: {1796
* name: "Corporate AI (SSO)",1797
* async login(callbacks) { ... },1798
* async refreshToken(credentials) { ... },1799
* getApiKey(credentials) { return credentials.access; }1800
* }1801
* });1802
*/1803
registerProvider(provider: Provider): void;1804
registerProvider(name: string, config: ProviderConfig): void;1806
/**1807
* Unregister a previously registered provider.1808
*1809
* Removes all models belonging to the named provider and restores any1810
* built-in models that were overridden by it. Has no effect if the provider1811
* is not currently registered.1812
*1813
* Like `registerProvider`, this takes effect immediately when called after1814
* the initial load phase.1815
*1816
* @example1817
* pi.unregisterProvider("my-proxy");1818
*/1819
unregisterProvider(name: string): void;1821
// =========================================================================1822
// MCP Servers1823
// =========================================================================1825
/**1826
* Register an MCP server for this session, with the same config as an `mcpServers` entry in1827
* `mcp.json`. The server connects next to the configured servers: on `session_start` when1828
* registered during extension load, right away when registered later. Registering a name again1829
* replaces the extension's earlier registration.1830
*1831
* The registration is not saved; register again on every load. A server of the same name in1832
* `mcp.json` takes precedence. Throws for invalid configs and for names another extension1833
* registered. When no loaded extension handles MCP servers (for example because another MCP1834
* extension replaced the built-in one), the registration is reported as an extension error.1835
*1836
* @example1837
* pi.registerMcpServer("jira", { url: "https://mcp.example.com/jira" });1838
*/1839
registerMcpServer(name: string, config: McpServerConfig): void;1841
/** Remove an MCP server this extension registered and close its connection. */1842
unregisterMcpServer(name: string): void;1844
/** Every MCP server registered by extensions. For extensions that connect MCP servers. */1845
getMcpServers(): RegisteredMcpServer[];1847
/**1848
* Register a virtual model: a selectable catalog entry that routes each request to a physical1849
* model. The selection (`ctx.model`, `model_change` entries) names the virtual model; assistant1850
* messages record the physical model and thinking level the router picked.1851
*1852
* `provider` may be any provider id, including one with physical models, and may list several1853
* virtual models. Registering the same provider and id again replaces the virtual model. See1854
* docs/virtual-models.md.1855
*/1856
registerVirtualModel<TState = unknown>(model: ExtensionVirtualModel<TState>): void;1858
/** Remove a virtual model registered with `registerVirtualModel()`. */1859
unregisterVirtualModel(provider: string, id: string): void;1861
/** Shared event bus for extension communication. */1862
events: EventBus;1863
}1865
// ============================================================================1866
// Provider Registration Types1867
// ============================================================================1869
/** Virtual model registered via pi.registerVirtualModel(). */1870
export interface ExtensionVirtualModel<TState = unknown> extends Omit<VirtualModelDefinition<TState>, "route"> {1871
/** Like `VirtualModelDefinition.route`, with an extension context. */1872
route(request: ModelRouteRequest<TState>, ctx: ExtensionContext): ModelRoute<TState> | Promise<ModelRoute<TState>>;1873
}1875
/** Configuration for registering a provider via pi.registerProvider(). */1876
export interface ProviderConfig {1877
/** Display name for the provider in UI. */1878
name?: string;1879
/** Base URL for the API endpoint. Required when defining models. */1880
baseUrl?: string;1881
/** API key literal, env interpolation ($ENV_VAR or ${ENV_VAR}), or leading !command. Required when defining models (unless oauth provided). */1882
apiKey?: string;1883
/** API type. Required at provider or model level when defining models. */1884
api?: Api;1885
/**1886
* Optional streamSimple handler for custom APIs.1887
* The context is a normalized transcript: read the prompt and tools from its system messages1888
* (`getCurrentSystemPrompt(context.messages)`, `getCurrentTools(context.messages)`).1889
* Implementations must invoke `options.onPayload` before sending the provider request and use any1890
* returned replacement payload. They must invoke `options.onResponse` after receiving the response1891
* and before consuming its body, matching built-in providers. Implementations may invoke1892
* `options.onProviderStreamEvent(data, model)` with parsed stream events before normalization.1893
* Event data is adapter-owned and must be treated as read-only.1894
*/1895
streamSimple?: (1896
model: Model<Api>,1897
context: TranscriptContext,1898
options?: SimpleStreamOptions,1899
) => AssistantMessageEventStream;1900
/** Image-generation implementations keyed by image API. */1901
images?: Partial<Record<ImageApi, ProviderImages>>;1902
/** Classifier implementations keyed by classifier API. */1903
classifiers?: Partial<Record<ClassifierApi, ProviderClassifier>>;1904
/** Custom headers to include in requests. */1905
headers?: Record<string, string>;1906
/** If true, adds Authorization: Bearer header with the resolved API key. */1907
authHeader?: boolean;1908
/** Models to register. If provided, replaces all existing models for this provider. */1909
models?: ProviderModelConfig[];1910
/**1911
* Refresh this provider's model list. The returned list replaces extension-provided models.1912
* Use context.publish({ persist: entry }) when the catalog should persist across sessions.1913
*/1914
refreshModels?(context: RefreshModelsContext): Promise<ProviderModelConfig[]>;1915
/** OAuth provider for /login support. The `id` is set automatically from the provider name. */1916
oauth?: {1917
/** Display name for the provider in login UI. */1918
name: string;1919
/** Whether access through this auth method is backed by a provider subscription. */1920
isSubscription?: boolean;1921
/** @deprecated Retained for source compatibility; canonical auth flows ignore it. */1922
usesCallbackServer?: boolean;1923
/** Run the login flow, return credentials to persist. */1924
login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;1925
/** Refresh expired credentials, return updated credentials to persist. */1926
refreshToken(credentials: OAuthCredentials, signal: AbortSignal): Promise<OAuthCredentials>;1927
/** Convert credentials to API key string for the provider. */1928
getApiKey(credentials: OAuthCredentials): string;1929
/** Legacy synchronous credential-dependent model projection. */1930
modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];1931
};1932
}1934
interface ProviderModelConfigBase {1935
/** Model ID. */1936
id: string;1937
/** Display name. */1938
name: string;1939
/** API type override for this model. */1940
api?: string;1941
/** API endpoint URL override for this model. */1942
baseUrl?: string;1943
/** Supported input types. */1944
input: ("text" | "image")[];1945
/** Provider input limits and cache-safe image preprocessing metadata. */1946
inputLimits?: AnyModel["inputLimits"];1947
/** Per-million-token cost rates and optional request-wide input pricing tiers. */1948
cost: AnyModel["cost"];1949
/** Custom headers for this model. */1950
headers?: Record<string, string>;1951
}1953
/** Chat model configuration. Omitted `type` is normalized to `"chat"`. */1954
export interface ProviderChatModelConfig extends ProviderModelConfigBase {1955
type?: "chat";1956
api?: Api;1957
/** Whether the model supports extended thinking. */1958
reasoning: boolean;1959
/** Maps pi thinking levels to provider/model-specific values; null marks a level unsupported. */1960
thinkingLevelMap?: Model<Api>["thinkingLevelMap"];1961
/** Best-effort prompt cache lifetime in seconds per retention tier. Unset disables cache warming. */1962
promptCache?: Model<Api>["promptCache"];1963
/** Maximum context window size in tokens. */1964
contextWindow: number;1965
/** Maximum output tokens. */1966
maxTokens: number;1967
samplingParams?: Record<string, unknown>;1968
/** OpenAI compatibility settings. */1969
compat?: Model<Api>["compat"];1970
}1972
/** Image-generation model configuration. */1973
export interface ProviderImageModelConfig extends ProviderModelConfigBase {1974
type: "image";1975
api?: ImageApi;1976
output: ("text" | "image")[];1977
}1979
/** Structured classifier model configuration. */1980
export interface ProviderClassifierModelConfig extends ProviderModelConfigBase {1981
type: "classifier";1982
api?: ClassifierApi;1983
contextWindow: number;1984
}1986
/** Configuration for a model within a provider. */1987
export type ProviderModelConfig = ProviderChatModelConfig | ProviderImageModelConfig | ProviderClassifierModelConfig;1989
/** Extension factory function type. Supports both sync and async initialization. */1990
export type ExtensionFactory = (pi: ExtensionAPI) => void | Promise<void>;1992
export type InlineExtension =1993
| ExtensionFactory1994
| {1995
/**1996
* Display name shown as `<inline:name>` in the startup Extensions list and errors. With1997
* `builtin`, the extension is named `builtin:name` in errors and diagnostics.1998
*/1999
name: string;2000
factory: ExtensionFactory;2001
/** Omit this extension from the startup Extensions list. */2002
hidden?: boolean;2003
/**2004
* Leave this extension out when another extension registers a tool, command, or flag with a2005
* name it registers during loading, instead of reporting a conflict. The CLI's built-in MCP,2006
* codemode, and tool search extensions use it, so for example an MCP extension that registers2007
* `/mcp` replaces the built-in MCP support. The factory still runs, so it should only register2008
* tools, commands, flags, and event handlers.2009
*/2010
replaceable?: boolean;2011
/**2012
* Supply the code of the `builtin:<name>` extension instead of loading as an inline extension.2013
* `builtin:<name>` is an extension resource like a file: it loads by default, `pi config` lists2014
* it, `-builtin:<name>` in the `extensions` setting and `--no-extensions` disable it, and2015
* `-e builtin:<name>` loads it explicitly. It is hidden from the startup Extensions list and2016
* loads after project trust is resolved, so it cannot handle `project_trust`. The CLI's built-in2017
* extensions use it.2018
*/2019
builtin?: boolean;2020
};2022
// ============================================================================2023
// Loaded Extension Types2024
// ============================================================================2026
export interface RegisteredTool {2027
definition: ToolDefinition;2028
sourceInfo: SourceInfo;2029
}2031
export interface ExtensionFlag {2032
name: string;2033
description?: string;2034
type: "boolean" | "string";2035
default?: boolean | string;2036
extensionPath: string;2037
}2039
export interface ExtensionShortcut {2040
shortcut: KeyId;2041
description?: string;2042
handler: (ctx: ExtensionContext) => Promise<void> | void;2043
extensionPath: string;2044
}2046
type HandlerFn = (...args: unknown[]) => Promise<unknown>;2048
export type SendMessageHandler = <T = unknown>(2049
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,2050
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },2051
) => void;2053
export type SendUserMessageHandler = (2054
content: string | (TextContent | ImageContent)[],2055
options?: { deliverAs?: "steer" | "followUp"; expandPromptTemplates?: boolean },2056
) => void;2058
export type AppendEntryHandler = <T = unknown>(customType: string, data?: T) => void;2060
export type SetSessionNameHandler = (name: string) => void;2062
export type GetSessionNameHandler = () => string | undefined;2064
export type GetActiveToolsHandler = () => string[];2066
/** Tool info with name, description, parameter schema, prompt guidelines, and source metadata. */2067
export type ToolInfo = Pick<ToolDefinition, "name" | "description" | "parameters" | "promptGuidelines"> & {2068
exposure: ToolExposure;2069
namespace?: ToolNamespace;2070
annotations?: ToolAnnotations;2071
sourceInfo: SourceInfo;2072
};2074
export type GetAllToolsHandler = () => ToolInfo[];2076
export type GetSettingsHandler = () => Settings;2078
export type GetCommandsHandler = () => SlashCommandInfo[];2080
export type SetActiveToolsHandler = (toolNames: string[]) => void;2082
export type RefreshToolsHandler = () => void;2084
export type SetModelHandler = (model: Model<any>) => Promise<boolean>;2086
export type GetThinkingLevelHandler = () => ThinkingLevel;2088
export type SetThinkingLevelHandler = (level: ThinkingLevel) => void;2090
export type SetLabelHandler = (entryId: string, label: string | undefined) => void;2092
/**2093
* Shared state created by loader, used during registration and runtime.2094
* Contains flag values (defaults set during registration, CLI values set after).2095
*/2096
export interface ExtensionRuntimeState {2097
flagValues: Map<string, boolean | string>;2098
/** Legacy provider-config registrations queued during extension loading, processed when runner binds. */2099
pendingProviderRegistrations: Array<{ name: string; config: ProviderConfig; extensionPath: string }>;2100
/** Native pi-ai provider registrations queued during extension loading, processed when runner binds. */2101
pendingNativeProviderRegistrations: Array<{ provider: Provider; extensionPath: string }>;2102
/** Virtual model registrations queued during extension loading, processed when runner binds. */2103
pendingVirtualModelRegistrations: Array<{ definition: VirtualModelDefinition; extensionPath: string }>;2104
/** Create an extension context. Throws before the runner binds. */2105
createContext: () => ExtensionContext;2106
/** Throws when this extension instance is stale after runtime replacement. */2107
assertActive: () => void;2108
/** Marks this extension instance as stale after runtime replacement or reload. */2109
invalidate: (message?: string) => void;2110
/** Retain an event-bus subscription until this runtime is invalidated. */2111
trackEventBusSubscription: (unsubscribe: () => void) => () => void;2112
/**2113
* Register or unregister a provider.2114
*2115
* Before bindCore(): queues registrations / removes from queue.2116
* After bindCore(): calls ModelRegistry directly for immediate effect.2117
*/2118
registerProvider: (name: string, config: ProviderConfig, extensionPath?: string) => void;2119
registerNativeProvider: (provider: Provider, extensionPath?: string) => void;2120
unregisterProvider: (name: string, extensionPath?: string) => void;2121
/** Servers registered with `pi.registerMcpServer()`. */2122
mcpServers: McpServerRegistry;2123
registerVirtualModel: (definition: VirtualModelDefinition, extensionPath?: string) => void;2124
unregisterVirtualModel: (provider: string, id: string) => void;2125
}2127
/**2128
* Action implementations for pi.* API methods.2129
* Provided to runner.initialize(), copied into the shared runtime.2130
*/2131
export interface ExtensionActions {2132
sendMessage: SendMessageHandler;2133
sendUserMessage: SendUserMessageHandler;2134
appendEntry: AppendEntryHandler;2135
setSessionName: SetSessionNameHandler;2136
getSessionName: GetSessionNameHandler;2137
setLabel: SetLabelHandler;2138
getActiveTools: GetActiveToolsHandler;2139
getAllTools: GetAllToolsHandler;2140
getSettings: GetSettingsHandler;2141
setActiveTools: SetActiveToolsHandler;2142
refreshTools: RefreshToolsHandler;2143
getCommands: GetCommandsHandler;2144
setModel: SetModelHandler;2145
getThinkingLevel: GetThinkingLevelHandler;2146
setThinkingLevel: SetThinkingLevelHandler;2147
}2149
/**2150
* Actions for ExtensionContext (ctx.* in event handlers).2151
* Required by all modes.2152
*/2153
export interface ExtensionContextActions {2154
getModel: () => Model<any> | undefined;2155
getScopedModels: () => readonly ScopedModel[];2156
isIdle: () => boolean;2157
isProjectTrusted: () => boolean;2158
getSignal: () => AbortSignal | undefined;2159
abort: () => void;2160
hasPendingMessages: () => boolean;2161
shutdown: () => void;2162
getContextUsage: () => ContextUsage | undefined;2163
compact: (options?: CompactOptions) => void;2164
getSystemPrompt: () => string;2165
getSystemPromptOptions?: () => BuildSystemPromptOptions;2166
/** Backs `ExtensionToolContext.executeTool()`. Without it, nested calls fail. */2167
executeTool?: (2168
callerId: string,2169
name: string,2170
args: unknown,2171
options: ExecuteToolOptions,2172
) => Promise<AgentToolCallOutcome>;2173
/** Backs `ExtensionToolContext.tools`. */2174
getCallableTools?: () => readonly AgentTool[];2175
}2177
/**2178
* Actions for ExtensionCommandContext (ctx.* in command handlers).2179
* Only needed for interactive mode where extension commands are invokable.2180
*/2181
export interface ExtensionCommandContextActions {2182
waitForIdle: () => Promise<void>;2183
newSession: (options?: {2184
parentSession?: string;2185
setup?: (sessionManager: SessionManager) => Promise<void>;2186
withSession?: (ctx: ReplacedSessionContext) => Promise<void>;2187
}) => Promise<{ cancelled: boolean }>;2188
fork: (2189
entryId: string,2190
options?: { position?: "before" | "at"; withSession?: (ctx: ReplacedSessionContext) => Promise<void> },2191
) => Promise<{ cancelled: boolean }>;2192
navigateTree: (2193
targetId: string,2194
options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string },2195
) => Promise<{ cancelled: boolean }>;2196
switchSession: (2197
sessionPath: string,2198
options?: { withSession?: (ctx: ReplacedSessionContext) => Promise<void> },2199
) => Promise<{ cancelled: boolean }>;2200
reload: () => Promise<void>;2201
}2203
/**2204
* Full runtime = state + actions.2205
* Created by loader with throwing action stubs, completed by runner.initialize().2206
*/2207
export interface ExtensionRuntime extends ExtensionRuntimeState, ExtensionActions {}2209
/** Loaded extension with all registered items. */2210
export interface Extension {2211
path: string;2212
resolvedPath: string;2213
hidden?: boolean;2214
/** See {@link InlineExtension}. */2215
replaceable?: boolean;2216
sourceInfo: SourceInfo;2217
handlers: Map<string, HandlerFn[]>;2218
tools: Map<string, RegisteredTool>;2219
messageRenderers: Map<string, MessageRenderer>;2220
markdownTransformer?: MarkdownTransformer;2221
entryRenderers?: Map<string, EntryRenderer>;2222
commands: Map<string, RegisteredCommand>;2223
flags: Map<string, ExtensionFlag>;2224
shortcuts: Map<KeyId, ExtensionShortcut>;2225
}2227
/** Result of loading extensions. */2228
export interface LoadExtensionsResult {2229
extensions: Extension[];2230
errors: Array<{ path: string; error: string }>;2231
warnings?: Array<{ path: string; warning: string }>;2232
/** Shared runtime - actions are throwing stubs until runner.initialize() */2233
runtime: ExtensionRuntime;2234
}2236
// ============================================================================2237
// Extension Error2238
// ============================================================================2240
export interface ExtensionError {2241
extensionPath: string;2242
event: string;2243
error: string;2244
stack?: string;2245
}