| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792 |
- import type {
- ToolResultBlockParam,
- ToolUseBlockParam,
- } from '@anthropic-ai/sdk/resources/index.mjs'
- import type {
- ElicitRequestURLParams,
- ElicitResult,
- } from '@modelcontextprotocol/sdk/types.js'
- import type { UUID } from 'crypto'
- import type { z } from 'zod/v4'
- import type { Command } from './commands.js'
- import type { CanUseToolFn } from './hooks/useCanUseTool.js'
- import type { ThinkingConfig } from './utils/thinking.js'
- export type ToolInputJSONSchema = {
- [x: string]: unknown
- type: 'object'
- properties?: {
- [x: string]: unknown
- }
- }
- import type { Notification } from './context/notifications.js'
- import type {
- MCPServerConnection,
- ServerResource,
- } from './services/mcp/types.js'
- import type {
- AgentDefinition,
- AgentDefinitionsResult,
- } from './tools/AgentTool/loadAgentsDir.js'
- import type {
- AssistantMessage,
- AttachmentMessage,
- Message,
- ProgressMessage,
- SystemLocalCommandMessage,
- SystemMessage,
- UserMessage,
- } from './types/message.js'
- // Import permission types from centralized location to break import cycles
- // Import PermissionResult from centralized location to break import cycles
- import type {
- AdditionalWorkingDirectory,
- PermissionMode,
- PermissionResult,
- } from './types/permissions.js'
- // Import tool progress types from centralized location to break import cycles
- import type {
- AgentToolProgress,
- BashProgress,
- MCPProgress,
- REPLToolProgress,
- SkillToolProgress,
- TaskOutputProgress,
- ToolProgressData,
- WebSearchProgress,
- } from './types/tools.js'
- import type { FileStateCache } from './utils/fileStateCache.js'
- import type { DenialTrackingState } from './utils/permissions/denialTracking.js'
- import type { SystemPrompt } from './utils/systemPromptType.js'
- import type { ContentReplacementState } from './utils/toolResultStorage.js'
- // Re-export progress types for backwards compatibility
- export type {
- AgentToolProgress,
- BashProgress,
- MCPProgress,
- REPLToolProgress,
- SkillToolProgress,
- TaskOutputProgress,
- WebSearchProgress,
- }
- import type { SpinnerMode } from './components/Spinner.js'
- import type { QuerySource } from './constants/querySource.js'
- import type { SDKStatus } from './entrypoints/agentSdkTypes.js'
- import type { AppState } from './state/AppState.js'
- import type {
- HookProgress,
- PromptRequest,
- PromptResponse,
- } from './types/hooks.js'
- import type { AgentId } from './types/ids.js'
- import type { DeepImmutable } from './types/utils.js'
- import type { AttributionState } from './utils/commitAttribution.js'
- import type { FileHistoryState } from './utils/fileHistory.js'
- import type { Theme, ThemeName } from './utils/theme.js'
- export type QueryChainTracking = {
- chainId: string
- depth: number
- }
- export type ValidationResult =
- | { result: true }
- | {
- result: false
- message: string
- errorCode: number
- }
- export type SetToolJSXFn = (
- args: {
- jsx: React.ReactNode | null
- shouldHidePromptInput: boolean
- shouldContinueAnimation?: true
- showSpinner?: boolean
- isLocalJSXCommand?: boolean
- isImmediate?: boolean
- /** Set to true to clear a local JSX command (e.g., from its onDone callback) */
- clearLocalJSX?: boolean
- } | null,
- ) => void
- // Import tool permission types from centralized location to break import cycles
- import type { ToolPermissionRulesBySource } from './types/permissions.js'
- // Re-export for backwards compatibility
- export type { ToolPermissionRulesBySource }
- // Apply DeepImmutable to the imported type
- export type ToolPermissionContext = DeepImmutable<{
- mode: PermissionMode
- additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
- alwaysAllowRules: ToolPermissionRulesBySource
- alwaysDenyRules: ToolPermissionRulesBySource
- alwaysAskRules: ToolPermissionRulesBySource
- isBypassPermissionsModeAvailable: boolean
- isAutoModeAvailable?: boolean
- strippedDangerousRules?: ToolPermissionRulesBySource
- /** When true, permission prompts are auto-denied (e.g., background agents that can't show UI) */
- shouldAvoidPermissionPrompts?: boolean
- /** When true, automated checks (classifier, hooks) are awaited before showing the permission dialog (coordinator workers) */
- awaitAutomatedChecksBeforeDialog?: boolean
- /** Stores the permission mode before model-initiated plan mode entry, so it can be restored on exit */
- prePlanMode?: PermissionMode
- }>
- export const getEmptyToolPermissionContext: () => ToolPermissionContext =
- () => ({
- mode: 'default',
- additionalWorkingDirectories: new Map(),
- alwaysAllowRules: {},
- alwaysDenyRules: {},
- alwaysAskRules: {},
- isBypassPermissionsModeAvailable: false,
- })
- export type CompactProgressEvent =
- | {
- type: 'hooks_start'
- hookType: 'pre_compact' | 'post_compact' | 'session_start'
- }
- | { type: 'compact_start' }
- | { type: 'compact_end' }
- export type ToolUseContext = {
- options: {
- commands: Command[]
- debug: boolean
- mainLoopModel: string
- tools: Tools
- verbose: boolean
- thinkingConfig: ThinkingConfig
- mcpClients: MCPServerConnection[]
- mcpResources: Record<string, ServerResource[]>
- isNonInteractiveSession: boolean
- agentDefinitions: AgentDefinitionsResult
- maxBudgetUsd?: number
- /** Custom system prompt that replaces the default system prompt */
- customSystemPrompt?: string
- /** Additional system prompt appended after the main system prompt */
- appendSystemPrompt?: string
- /** Override querySource for analytics tracking */
- querySource?: QuerySource
- /** Optional callback to get the latest tools (e.g., after MCP servers connect mid-query) */
- refreshTools?: () => Tools
- }
- abortController: AbortController
- readFileState: FileStateCache
- getAppState(): AppState
- setAppState(f: (prev: AppState) => AppState): void
- /**
- * Always-shared setAppState for session-scoped infrastructure (background
- * tasks, session hooks). Unlike setAppState, which is no-op for async agents
- * (see createSubagentContext), this always reaches the root store so agents
- * at any nesting depth can register/clean up infrastructure that outlives
- * a single turn. Only set by createSubagentContext; main-thread contexts
- * fall back to setAppState.
- */
- setAppStateForTasks?: (f: (prev: AppState) => AppState) => void
- /**
- * Optional handler for URL elicitations triggered by tool call errors (-32042).
- * In print/SDK mode, this delegates to structuredIO.handleElicitation.
- * In REPL mode, this is undefined and the queue-based UI path is used.
- */
- handleElicitation?: (
- serverName: string,
- params: ElicitRequestURLParams,
- signal: AbortSignal,
- ) => Promise<ElicitResult>
- setToolJSX?: SetToolJSXFn
- addNotification?: (notif: Notification) => void
- /** Append a UI-only system message to the REPL message list. Stripped at the
- * normalizeMessagesForAPI boundary — the Exclude<> makes that type-enforced. */
- appendSystemMessage?: (
- msg: Exclude<SystemMessage, SystemLocalCommandMessage>,
- ) => void
- /** Send an OS-level notification (iTerm2, Kitty, Ghostty, bell, etc.) */
- sendOSNotification?: (opts: {
- message: string
- notificationType: string
- }) => void
- nestedMemoryAttachmentTriggers?: Set<string>
- /**
- * CLAUDE.md paths already injected as nested_memory attachments this
- * session. Dedup for memoryFilesToAttachments — readFileState is an LRU
- * that evicts entries in busy sessions, so its .has() check alone can
- * re-inject the same CLAUDE.md dozens of times.
- */
- loadedNestedMemoryPaths?: Set<string>
- dynamicSkillDirTriggers?: Set<string>
- /** Skill names surfaced via skill_discovery this session. Telemetry only (feeds was_discovered). */
- discoveredSkillNames?: Set<string>
- userModified?: boolean
- setInProgressToolUseIDs: (f: (prev: Set<string>) => Set<string>) => void
- /** Only wired in interactive (REPL) contexts; SDK/QueryEngine don't set this. */
- setHasInterruptibleToolInProgress?: (v: boolean) => void
- setResponseLength: (f: (prev: number) => number) => void
- /** Ant-only: push a new API metrics entry for OTPS tracking.
- * Called by subagent streaming when a new API request starts. */
- pushApiMetricsEntry?: (ttftMs: number) => void
- setStreamMode?: (mode: SpinnerMode) => void
- onCompactProgress?: (event: CompactProgressEvent) => void
- setSDKStatus?: (status: SDKStatus) => void
- openMessageSelector?: () => void
- updateFileHistoryState: (
- updater: (prev: FileHistoryState) => FileHistoryState,
- ) => void
- updateAttributionState: (
- updater: (prev: AttributionState) => AttributionState,
- ) => void
- setConversationId?: (id: UUID) => void
- agentId?: AgentId // Only set for subagents; use getSessionId() for session ID. Hooks use this to distinguish subagent calls.
- agentType?: string // Subagent type name. For the main thread's --agent type, hooks fall back to getMainThreadAgentType().
- /** When true, canUseTool must always be called even when hooks auto-approve.
- * Used by speculation for overlay file path rewriting. */
- requireCanUseTool?: boolean
- messages: Message[]
- fileReadingLimits?: {
- maxTokens?: number
- maxSizeBytes?: number
- }
- globLimits?: {
- maxResults?: number
- }
- toolDecisions?: Map<
- string,
- {
- source: string
- decision: 'accept' | 'reject'
- timestamp: number
- }
- >
- queryTracking?: QueryChainTracking
- /** Callback factory for requesting interactive prompts from the user.
- * Returns a prompt callback bound to the given source name.
- * Only available in interactive (REPL) contexts. */
- requestPrompt?: (
- sourceName: string,
- toolInputSummary?: string | null,
- ) => (request: PromptRequest) => Promise<PromptResponse>
- toolUseId?: string
- criticalSystemReminder_EXPERIMENTAL?: string
- /** When true, preserve toolUseResult on messages even for subagents.
- * Used by in-process teammates whose transcripts are viewable by the user. */
- preserveToolUseResults?: boolean
- /** Local denial tracking state for async subagents whose setAppState is a
- * no-op. Without this, the denial counter never accumulates and the
- * fallback-to-prompting threshold is never reached. Mutable — the
- * permissions code updates it in place. */
- localDenialTracking?: DenialTrackingState
- /**
- * Per-conversation-thread content replacement state for the tool result
- * budget. When present, query.ts applies the aggregate tool result budget.
- * Main thread: REPL provisions once (never resets — stale UUID keys
- * are inert). Subagents: createSubagentContext clones the parent's state
- * by default (cache-sharing forks need identical decisions), or
- * resumeAgentBackground threads one reconstructed from sidechain records.
- */
- contentReplacementState?: ContentReplacementState
- /**
- * Parent's rendered system prompt bytes, frozen at turn start.
- * Used by fork subagents to share the parent's prompt cache — re-calling
- * getSystemPrompt() at fork-spawn time can diverge (GrowthBook cold→warm)
- * and bust the cache. See forkSubagent.ts.
- */
- renderedSystemPrompt?: SystemPrompt
- }
- // Re-export ToolProgressData from centralized location
- export type { ToolProgressData }
- export type Progress = ToolProgressData | HookProgress
- export type ToolProgress<P extends ToolProgressData> = {
- toolUseID: string
- data: P
- }
- export function filterToolProgressMessages(
- progressMessagesForMessage: ProgressMessage[],
- ): ProgressMessage<ToolProgressData>[] {
- return progressMessagesForMessage.filter(
- (msg): msg is ProgressMessage<ToolProgressData> =>
- msg.data?.type !== 'hook_progress',
- )
- }
- export type ToolResult<T> = {
- data: T
- newMessages?: (
- | UserMessage
- | AssistantMessage
- | AttachmentMessage
- | SystemMessage
- )[]
- // contextModifier is only honored for tools that aren't concurrency safe.
- contextModifier?: (context: ToolUseContext) => ToolUseContext
- /** MCP protocol metadata (structuredContent, _meta) to pass through to SDK consumers */
- mcpMeta?: {
- _meta?: Record<string, unknown>
- structuredContent?: Record<string, unknown>
- }
- }
- export type ToolCallProgress<P extends ToolProgressData = ToolProgressData> = (
- progress: ToolProgress<P>,
- ) => void
- // Type for any schema that outputs an object with string keys
- export type AnyObject = z.ZodType<{ [key: string]: unknown }>
- /**
- * Checks if a tool matches the given name (primary name or alias).
- */
- export function toolMatchesName(
- tool: { name: string; aliases?: string[] },
- name: string,
- ): boolean {
- return tool.name === name || (tool.aliases?.includes(name) ?? false)
- }
- /**
- * Finds a tool by name or alias from a list of tools.
- */
- export function findToolByName(tools: Tools, name: string): Tool | undefined {
- return tools.find(t => toolMatchesName(t, name))
- }
- export type Tool<
- Input extends AnyObject = AnyObject,
- Output = unknown,
- P extends ToolProgressData = ToolProgressData,
- > = {
- /**
- * Optional aliases for backwards compatibility when a tool is renamed.
- * The tool can be looked up by any of these names in addition to its primary name.
- */
- aliases?: string[]
- /**
- * One-line capability phrase used by ToolSearch for keyword matching.
- * Helps the model find this tool via keyword search when it's deferred.
- * 3–10 words, no trailing period.
- * Prefer terms not already in the tool name (e.g. 'jupyter' for NotebookEdit).
- */
- searchHint?: string
- call(
- args: z.infer<Input>,
- context: ToolUseContext,
- canUseTool: CanUseToolFn,
- parentMessage: AssistantMessage,
- onProgress?: ToolCallProgress<P>,
- ): Promise<ToolResult<Output>>
- description(
- input: z.infer<Input>,
- options: {
- isNonInteractiveSession: boolean
- toolPermissionContext: ToolPermissionContext
- tools: Tools
- },
- ): Promise<string>
- readonly inputSchema: Input
- // Type for MCP tools that can specify their input schema directly in JSON Schema format
- // rather than converting from Zod schema
- readonly inputJSONSchema?: ToolInputJSONSchema
- // Optional because TungstenTool doesn't define this. TODO: Make it required.
- // When we do that, we can also go through and make this a bit more type-safe.
- outputSchema?: z.ZodType<unknown>
- inputsEquivalent?(a: z.infer<Input>, b: z.infer<Input>): boolean
- isConcurrencySafe(input: z.infer<Input>): boolean
- isEnabled(): boolean
- isReadOnly(input: z.infer<Input>): boolean
- /** Defaults to false. Only set when the tool performs irreversible operations (delete, overwrite, send). */
- isDestructive?(input: z.infer<Input>): boolean
- /**
- * What should happen when the user submits a new message while this tool
- * is running.
- *
- * - `'cancel'` — stop the tool and discard its result
- * - `'block'` — keep running; the new message waits
- *
- * Defaults to `'block'` when not implemented.
- */
- interruptBehavior?(): 'cancel' | 'block'
- /**
- * Returns information about whether this tool use is a search or read operation
- * that should be collapsed into a condensed display in the UI. Examples include
- * file searching (Grep, Glob), file reading (Read), and bash commands like find,
- * grep, wc, etc.
- *
- * Returns an object indicating whether the operation is a search or read operation:
- * - `isSearch: true` for search operations (grep, find, glob patterns)
- * - `isRead: true` for read operations (cat, head, tail, file read)
- * - `isList: true` for directory-listing operations (ls, tree, du)
- * - All can be false if the operation shouldn't be collapsed
- */
- isSearchOrReadCommand?(input: z.infer<Input>): {
- isSearch: boolean
- isRead: boolean
- isList?: boolean
- }
- isOpenWorld?(input: z.infer<Input>): boolean
- requiresUserInteraction?(): boolean
- isMcp?: boolean
- isLsp?: boolean
- /**
- * When true, this tool is deferred (sent with defer_loading: true) and requires
- * ToolSearch to be used before it can be called.
- */
- readonly shouldDefer?: boolean
- /**
- * When true, this tool is never deferred — its full schema appears in the
- * initial prompt even when ToolSearch is enabled. For MCP tools, set via
- * `_meta['anthropic/alwaysLoad']`. Use for tools the model must see on
- * turn 1 without a ToolSearch round-trip.
- */
- readonly alwaysLoad?: boolean
- /**
- * For MCP tools: the server and tool names as received from the MCP server (unnormalized).
- * Present on all MCP tools regardless of whether `name` is prefixed (mcp__server__tool)
- * or unprefixed (CLAUDE_AGENT_SDK_MCP_NO_PREFIX mode).
- */
- mcpInfo?: { serverName: string; toolName: string }
- readonly name: string
- /**
- * Maximum size in characters for tool result before it gets persisted to disk.
- * When exceeded, the result is saved to a file and Claude receives a preview
- * with the file path instead of the full content.
- *
- * Set to Infinity for tools whose output must never be persisted (e.g. Read,
- * where persisting creates a circular Read→file→Read loop and the tool
- * already self-bounds via its own limits).
- */
- maxResultSizeChars: number
- /**
- * When true, enables strict mode for this tool, which causes the API to
- * more strictly adhere to tool instructions and parameter schemas.
- * Only applied when the tengu_tool_pear is enabled.
- */
- readonly strict?: boolean
- /**
- * Called on copies of tool_use input before observers see it (SDK stream,
- * transcript, canUseTool, PreToolUse/PostToolUse hooks). Mutate in place
- * to add legacy/derived fields. Must be idempotent. The original API-bound
- * input is never mutated (preserves prompt cache). Not re-applied when a
- * hook/permission returns a fresh updatedInput — those own their shape.
- */
- backfillObservableInput?(input: Record<string, unknown>): void
- /**
- * Determines if this tool is allowed to run with this input in the current context.
- * It informs the model of why the tool use failed, and does not directly display any UI.
- * @param input
- * @param context
- */
- validateInput?(
- input: z.infer<Input>,
- context: ToolUseContext,
- ): Promise<ValidationResult>
- /**
- * Determines if the user is asked for permission. Only called after validateInput() passes.
- * General permission logic is in permissions.ts. This method contains tool-specific logic.
- * @param input
- * @param context
- */
- checkPermissions(
- input: z.infer<Input>,
- context: ToolUseContext,
- ): Promise<PermissionResult>
- // Optional method for tools that operate on a file path
- getPath?(input: z.infer<Input>): string
- /**
- * Prepare a matcher for hook `if` conditions (permission-rule patterns like
- * "git *" from "Bash(git *)"). Called once per hook-input pair; any
- * expensive parsing happens here. Returns a closure that is called per
- * hook pattern. If not implemented, only tool-name-level matching works.
- */
- preparePermissionMatcher?(
- input: z.infer<Input>,
- ): Promise<(pattern: string) => boolean>
- prompt(options: {
- getToolPermissionContext: () => Promise<ToolPermissionContext>
- tools: Tools
- agents: AgentDefinition[]
- allowedAgentTypes?: string[]
- }): Promise<string>
- userFacingName(input: Partial<z.infer<Input>> | undefined): string
- userFacingNameBackgroundColor?(
- input: Partial<z.infer<Input>> | undefined,
- ): keyof Theme | undefined
- /**
- * Transparent wrappers (e.g. REPL) delegate all rendering to their progress
- * handler, which emits native-looking blocks for each inner tool call.
- * The wrapper itself shows nothing.
- */
- isTransparentWrapper?(): boolean
- /**
- * Returns a short string summary of this tool use for display in compact views.
- * @param input The tool input
- * @returns A short string summary, or null to not display
- */
- getToolUseSummary?(input: Partial<z.infer<Input>> | undefined): string | null
- /**
- * Returns a human-readable present-tense activity description for spinner display.
- * Example: "Reading src/foo.ts", "Running bun test", "Searching for pattern"
- * @param input The tool input
- * @returns Activity description string, or null to fall back to tool name
- */
- getActivityDescription?(
- input: Partial<z.infer<Input>> | undefined,
- ): string | null
- /**
- * Returns a compact representation of this tool use for the auto-mode
- * security classifier. Examples: `ls -la` for Bash, `/tmp/x: new content`
- * for Edit. Return '' to skip this tool in the classifier transcript
- * (e.g. tools with no security relevance). May return an object to avoid
- * double-encoding when the caller JSON-wraps the value.
- */
- toAutoClassifierInput(input: z.infer<Input>): unknown
- mapToolResultToToolResultBlockParam(
- content: Output,
- toolUseID: string,
- ): ToolResultBlockParam
- /**
- * Optional. When omitted, the tool result renders nothing (same as returning
- * null). Omit for tools whose results are surfaced elsewhere (e.g., TodoWrite
- * updates the todo panel, not the transcript).
- */
- renderToolResultMessage?(
- content: Output,
- progressMessagesForMessage: ProgressMessage<P>[],
- options: {
- style?: 'condensed'
- theme: ThemeName
- tools: Tools
- verbose: boolean
- isTranscriptMode?: boolean
- isBriefOnly?: boolean
- /** Original tool_use input, when available. Useful for compact result
- * summaries that reference what was requested (e.g. "Sent to #foo"). */
- input?: unknown
- },
- ): React.ReactNode
- /**
- * Flattened text of what renderToolResultMessage shows IN TRANSCRIPT
- * MODE (verbose=true, isTranscriptMode=true). For transcript search
- * indexing: the index counts occurrences in this string, the highlight
- * overlay scans the actual screen buffer. For count ≡ highlight, this
- * must return the text that ends up visible — not the model-facing
- * serialization from mapToolResultToToolResultBlockParam (which adds
- * system-reminders, persisted-output wrappers).
- *
- * Chrome can be skipped (under-count is fine). "Found 3 files in 12ms"
- * isn't worth indexing. Phantoms are not fine — text that's claimed
- * here but doesn't render is a count≠highlight bug.
- *
- * Optional: omitted → field-name heuristic in transcriptSearch.ts.
- * Drift caught by test/utils/transcriptSearch.renderFidelity.test.tsx
- * which renders sample outputs and flags text that's indexed-but-not-
- * rendered (phantom) or rendered-but-not-indexed (under-count warning).
- */
- extractSearchText?(out: Output): string
- /**
- * Render the tool use message. Note that `input` is partial because we render
- * the message as soon as possible, possibly before tool parameters have fully
- * streamed in.
- */
- renderToolUseMessage(
- input: Partial<z.infer<Input>>,
- options: { theme: ThemeName; verbose: boolean; commands?: Command[] },
- ): React.ReactNode
- /**
- * Returns true when the non-verbose rendering of this output is truncated
- * (i.e., clicking to expand would reveal more content). Gates
- * click-to-expand in fullscreen — only messages where verbose actually
- * shows more get a hover/click affordance. Unset means never truncated.
- */
- isResultTruncated?(output: Output): boolean
- /**
- * Renders an optional tag to display after the tool use message.
- * Used for additional metadata like timeout, model, resume ID, etc.
- * Returns null to not display anything.
- */
- renderToolUseTag?(input: Partial<z.infer<Input>>): React.ReactNode
- /**
- * Optional. When omitted, no progress UI is shown while the tool runs.
- */
- renderToolUseProgressMessage?(
- progressMessagesForMessage: ProgressMessage<P>[],
- options: {
- tools: Tools
- verbose: boolean
- terminalSize?: { columns: number; rows: number }
- inProgressToolCallCount?: number
- isTranscriptMode?: boolean
- },
- ): React.ReactNode
- renderToolUseQueuedMessage?(): React.ReactNode
- /**
- * Optional. When omitted, falls back to <FallbackToolUseRejectedMessage />.
- * Only define this for tools that need custom rejection UI (e.g., file edits
- * that show the rejected diff).
- */
- renderToolUseRejectedMessage?(
- input: z.infer<Input>,
- options: {
- columns: number
- messages: Message[]
- style?: 'condensed'
- theme: ThemeName
- tools: Tools
- verbose: boolean
- progressMessagesForMessage: ProgressMessage<P>[]
- isTranscriptMode?: boolean
- },
- ): React.ReactNode
- /**
- * Optional. When omitted, falls back to <FallbackToolUseErrorMessage />.
- * Only define this for tools that need custom error UI (e.g., search tools
- * that show "File not found" instead of the raw error).
- */
- renderToolUseErrorMessage?(
- result: ToolResultBlockParam['content'],
- options: {
- progressMessagesForMessage: ProgressMessage<P>[]
- tools: Tools
- verbose: boolean
- isTranscriptMode?: boolean
- },
- ): React.ReactNode
- /**
- * Renders multiple parallel instances of this tool as a group.
- * @returns React node to render, or null to fall back to individual rendering
- */
- /**
- * Renders multiple tool uses as a group (non-verbose mode only).
- * In verbose mode, individual tool uses render at their original positions.
- * @returns React node to render, or null to fall back to individual rendering
- */
- renderGroupedToolUse?(
- toolUses: Array<{
- param: ToolUseBlockParam
- isResolved: boolean
- isError: boolean
- isInProgress: boolean
- progressMessages: ProgressMessage<P>[]
- result?: {
- param: ToolResultBlockParam
- output: unknown
- }
- }>,
- options: {
- shouldAnimate: boolean
- tools: Tools
- },
- ): React.ReactNode | null
- }
- /**
- * A collection of tools. Use this type instead of `Tool[]` to make it easier
- * to track where tool sets are assembled, passed, and filtered across the codebase.
- */
- export type Tools = readonly Tool[]
- /**
- * Methods that `buildTool` supplies a default for. A `ToolDef` may omit these;
- * the resulting `Tool` always has them.
- */
- type DefaultableToolKeys =
- | 'isEnabled'
- | 'isConcurrencySafe'
- | 'isReadOnly'
- | 'isDestructive'
- | 'checkPermissions'
- | 'toAutoClassifierInput'
- | 'userFacingName'
- /**
- * Tool definition accepted by `buildTool`. Same shape as `Tool` but with the
- * defaultable methods optional — `buildTool` fills them in so callers always
- * see a complete `Tool`.
- */
- export type ToolDef<
- Input extends AnyObject = AnyObject,
- Output = unknown,
- P extends ToolProgressData = ToolProgressData,
- > = Omit<Tool<Input, Output, P>, DefaultableToolKeys> &
- Partial<Pick<Tool<Input, Output, P>, DefaultableToolKeys>>
- /**
- * Type-level spread mirroring `{ ...TOOL_DEFAULTS, ...def }`. For each
- * defaultable key: if D provides it (required), D's type wins; if D omits
- * it or has it optional (inherited from Partial<> in the constraint), the
- * default fills in. All other keys come from D verbatim — preserving arity,
- * optional presence, and literal types exactly as `satisfies Tool` did.
- */
- type BuiltTool<D> = Omit<D, DefaultableToolKeys> & {
- [K in DefaultableToolKeys]-?: K extends keyof D
- ? undefined extends D[K]
- ? ToolDefaults[K]
- : D[K]
- : ToolDefaults[K]
- }
- /**
- * Build a complete `Tool` from a partial definition, filling in safe defaults
- * for the commonly-stubbed methods. All tool exports should go through this so
- * that defaults live in one place and callers never need `?.() ?? default`.
- *
- * Defaults (fail-closed where it matters):
- * - `isEnabled` → `true`
- * - `isConcurrencySafe` → `false` (assume not safe)
- * - `isReadOnly` → `false` (assume writes)
- * - `isDestructive` → `false`
- * - `checkPermissions` → `{ behavior: 'allow', updatedInput }` (defer to general permission system)
- * - `toAutoClassifierInput` → `''` (skip classifier — security-relevant tools must override)
- * - `userFacingName` → `name`
- */
- const TOOL_DEFAULTS = {
- isEnabled: () => true,
- isConcurrencySafe: (_input?: unknown) => false,
- isReadOnly: (_input?: unknown) => false,
- isDestructive: (_input?: unknown) => false,
- checkPermissions: (
- input: { [key: string]: unknown },
- _ctx?: ToolUseContext,
- ): Promise<PermissionResult> =>
- Promise.resolve({ behavior: 'allow', updatedInput: input }),
- toAutoClassifierInput: (_input?: unknown) => '',
- userFacingName: (_input?: unknown) => '',
- }
- // The defaults type is the ACTUAL shape of TOOL_DEFAULTS (optional params so
- // both 0-arg and full-arg call sites type-check — stubs varied in arity and
- // tests relied on that), not the interface's strict signatures.
- type ToolDefaults = typeof TOOL_DEFAULTS
- // D infers the concrete object-literal type from the call site. The
- // constraint provides contextual typing for method parameters; `any` in
- // constraint position is structural and never leaks into the return type.
- // BuiltTool<D> mirrors runtime `{...TOOL_DEFAULTS, ...def}` at the type level.
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- type AnyToolDef = ToolDef<any, any, any>
- export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
- // The runtime spread is straightforward; the `as` bridges the gap between
- // the structural-any constraint and the precise BuiltTool<D> return. The
- // type semantics are proven by the 0-error typecheck across all 60+ tools.
- return {
- ...TOOL_DEFAULTS,
- userFacingName: () => def.name,
- ...def,
- } as BuiltTool<D>
- }
|