/** * Custom tool types. * * Custom tools are TypeScript modules that define additional tools for the agent. * They can provide custom rendering for tool calls and results in the TUI. */ import type { AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core"; import type { CompactionResult } from "@oh-my-pi/pi-agent-core/compaction"; import type { Model, Static, TSchema } from "@oh-my-pi/pi-ai"; import type { Component } from "@oh-my-pi/pi-tui"; import type { Rule } from "../../capability/rule"; import type { ModelRegistry } from "../../config/model-registry"; import type { Settings } from "../../config/settings"; import type { ExecOptions, ExecResult } from "../../exec/exec"; import type { HookUIContext } from "../../extensibility/hooks/types"; import type { Theme } from "../../modes/theme/theme"; import type { ReadonlySessionManager } from "../../session/session-manager"; import type { TodoItem } from "../../tools/todo-write"; /** Alias for clarity */ export type CustomToolUIContext = HookUIContext; // Re-export for backward compatibility export type { ExecOptions, ExecResult } from "../../exec/exec"; /** Re-export for custom tools to use in execute signature */ export type { AgentToolResult, AgentToolUpdateCallback }; /** Pending action entry consumed by the hidden resolve tool */ export interface CustomToolPendingAction { /** Human-readable preview label shown in resolve flow */ label: string; /** Apply callback invoked when resolve(action="apply") is called */ apply(reason: string): Promise>; /** Optional reject callback invoked when resolve(action="discard") is called */ reject?(reason: string): Promise | undefined>; /** Optional details metadata stored with the pending action */ details?: unknown; /** Optional source tool name shown by resolve renderer (defaults to "custom_tool") */ sourceToolName?: string; } /** API passed to custom tool factory (stable across session changes) */ export interface CustomToolAPI { /** Current working directory */ cwd: string; /** Execute a command */ exec(command: string, args: string[], options?: ExecOptions): Promise; /** UI methods for user interaction (select, confirm, input, notify, custom) */ ui: CustomToolUIContext; /** Whether UI is available (false in print/RPC mode) */ hasUI: boolean; /** File logger for error/warning/debug messages */ logger: typeof import("@oh-my-pi/pi-utils").logger; /** Injected zod-backed typebox shim (legacy/compat — Zod-authored tools are preferred). */ typebox: typeof import("../typebox"); /** Injected zod module for Zod-authored custom tools. */ zod: typeof import("zod/v4"); /** Injected pi-coding-agent exports */ pi: typeof import("../.."); /** Push a preview action that can later be resolved with the hidden resolve tool */ pushPendingAction(action: CustomToolPendingAction): void; } /** * Context passed to tool execute and onSession callbacks. * Provides access to session state and model information. */ export interface CustomToolContext { /** Session manager (read-only) */ sessionManager: ReadonlySessionManager; /** Model registry - use for API key resolution and model retrieval */ modelRegistry: ModelRegistry; /** Current model (may be undefined if no model is selected yet) */ model: Model | undefined; /** Whether the agent is idle (not streaming) */ isIdle(): boolean; /** Whether there are queued messages waiting to be processed */ hasQueuedMessages(): boolean; /** Abort the current agent operation (fire-and-forget, does not wait) */ abort(): void; /** Settings instance for the current session. Prefer over the global singleton. */ settings?: Settings; } /** Session event passed to onSession callback */ export type CustomToolSessionEvent = | { /** Reason for the session event */ reason: "start" | "switch" | "branch" | "tree" | "shutdown"; /** Previous session file path, or undefined for "start" and "shutdown" */ previousSessionFile: string | undefined; } | { reason: "auto_compaction_start"; trigger: "threshold" | "overflow" | "idle"; action: "context-full" | "handoff"; } | { reason: "auto_compaction_end"; action: "context-full" | "handoff"; result: CompactionResult | undefined; aborted: boolean; willRetry: boolean; errorMessage?: string; } | { reason: "auto_retry_start"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string; } | { reason: "auto_retry_end"; success: boolean; attempt: number; finalError?: string; } | { reason: "ttsr_triggered"; rules: Rule[]; } | { reason: "todo_reminder"; todos: TodoItem[]; attempt: number; maxAttempts: number; }; /** Rendering options passed to renderResult */ export interface RenderResultOptions { /** Whether the result view is expanded */ expanded: boolean; /** Whether this is a partial/streaming result */ isPartial: boolean; /** Current spinner frame index for animated elements (0-9, only provided during partial results) */ spinnerFrame?: number; } export type CustomToolResult = AgentToolResult; /** * Custom tool definition. * * Custom tools are standalone - they don't extend AgentTool directly. * When loaded, they are wrapped in an AgentTool for the agent to use. * * The execute callback receives a ToolContext with access to session state, * model registry, and current model. * * @example * ```typescript * const factory: CustomToolFactory = (pi) => ({ * name: "my_tool", * label: "My Tool", * description: "Does something useful", * parameters: Type.Object({ input: Type.String() }), * * async execute(toolCallId, params, onUpdate, ctx, signal) { * // Access session state via ctx.sessionManager * // Access model registry via ctx.modelRegistry * // Current model via ctx.model * return { content: [{ type: "text", text: "Done" }] }; * }, * * onSession(event, ctx) { * if (event.reason === "shutdown") { * // Cleanup * } * // Reconstruct state from ctx.sessionManager.getEntries() * } * }); * ``` */ export interface CustomTool { /** Tool name (used in LLM tool calls) */ name: string; /** Human-readable label for UI */ label: string; /** If true, tool is strictly typed and validated against the parameters schema before execution */ strict?: boolean; /** Description for LLM */ description: string; /** Parameter schema (Zod or TypeBox; TypeBox is auto-lifted to Zod at registration). */ parameters: TParams; /** If true, tool is excluded unless explicitly listed in --tools or agent's tools field */ hidden?: 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. * @param toolCallId - Unique ID for this tool call * @param params - Parsed parameters matching the schema * @param onUpdate - Callback for streaming partial results (for UI, not LLM) * @param ctx - Context with session manager, model registry, and current model * @param signal - Optional abort signal for cancellation */ execute( toolCallId: string, params: Static, onUpdate: AgentToolUpdateCallback | undefined, ctx: CustomToolContext, signal?: AbortSignal, ): Promise>; /** Called on session lifecycle events - use to reconstruct state or cleanup resources */ onSession?: (event: CustomToolSessionEvent, ctx: CustomToolContext) => void | Promise; /** Custom rendering for tool call display - return a Component */ renderCall?: (args: Static, options: RenderResultOptions, theme: Theme) => Component; /** Custom rendering for tool result display - return a Component */ renderResult?: ( result: CustomToolResult, options: RenderResultOptions, theme: Theme, args?: Static, ) => Component; } /** Factory function that creates a custom tool or array of tools */ export type CustomToolFactory = ( pi: CustomToolAPI, ) => CustomTool | CustomTool[] | Promise | CustomTool[]>; /** Loaded custom tool with metadata and wrapped AgentTool */ export interface LoadedCustomTool { /** Original path (as specified) */ path: string; /** Resolved absolute path */ resolvedPath: string; /** The original custom tool instance */ tool: CustomTool; /** Source metadata (provider and level) */ source?: { provider: string; providerName: string; level: "user" | "project" }; } /** Error with source metadata */ export interface ToolLoadError { path: string; error: string; source?: { provider: string; providerName: string; level: "user" | "project" }; } /** Result from loading custom tools */ export interface CustomToolsLoadResult { tools: LoadedCustomTool[]; errors: ToolLoadError[]; /** Update the UI context for all loaded tools. Call when mode initializes. */ setUIContext(uiContext: CustomToolUIContext, hasUI: boolean): void; }