source dump of claude code
at main 792 lines 30 kB view raw
1import type { 2 ToolResultBlockParam, 3 ToolUseBlockParam, 4} from '@anthropic-ai/sdk/resources/index.mjs' 5import type { 6 ElicitRequestURLParams, 7 ElicitResult, 8} from '@modelcontextprotocol/sdk/types.js' 9import type { UUID } from 'crypto' 10import type { z } from 'zod/v4' 11import type { Command } from './commands.js' 12import type { CanUseToolFn } from './hooks/useCanUseTool.js' 13import type { ThinkingConfig } from './utils/thinking.js' 14 15export type ToolInputJSONSchema = { 16 [x: string]: unknown 17 type: 'object' 18 properties?: { 19 [x: string]: unknown 20 } 21} 22 23import type { Notification } from './context/notifications.js' 24import type { 25 MCPServerConnection, 26 ServerResource, 27} from './services/mcp/types.js' 28import type { 29 AgentDefinition, 30 AgentDefinitionsResult, 31} from './tools/AgentTool/loadAgentsDir.js' 32import type { 33 AssistantMessage, 34 AttachmentMessage, 35 Message, 36 ProgressMessage, 37 SystemLocalCommandMessage, 38 SystemMessage, 39 UserMessage, 40} from './types/message.js' 41// Import permission types from centralized location to break import cycles 42// Import PermissionResult from centralized location to break import cycles 43import type { 44 AdditionalWorkingDirectory, 45 PermissionMode, 46 PermissionResult, 47} from './types/permissions.js' 48// Import tool progress types from centralized location to break import cycles 49import type { 50 AgentToolProgress, 51 BashProgress, 52 MCPProgress, 53 REPLToolProgress, 54 SkillToolProgress, 55 TaskOutputProgress, 56 ToolProgressData, 57 WebSearchProgress, 58} from './types/tools.js' 59import type { FileStateCache } from './utils/fileStateCache.js' 60import type { DenialTrackingState } from './utils/permissions/denialTracking.js' 61import type { SystemPrompt } from './utils/systemPromptType.js' 62import type { ContentReplacementState } from './utils/toolResultStorage.js' 63 64// Re-export progress types for backwards compatibility 65export type { 66 AgentToolProgress, 67 BashProgress, 68 MCPProgress, 69 REPLToolProgress, 70 SkillToolProgress, 71 TaskOutputProgress, 72 WebSearchProgress, 73} 74 75import type { SpinnerMode } from './components/Spinner.js' 76import type { QuerySource } from './constants/querySource.js' 77import type { SDKStatus } from './entrypoints/agentSdkTypes.js' 78import type { AppState } from './state/AppState.js' 79import type { 80 HookProgress, 81 PromptRequest, 82 PromptResponse, 83} from './types/hooks.js' 84import type { AgentId } from './types/ids.js' 85import type { DeepImmutable } from './types/utils.js' 86import type { AttributionState } from './utils/commitAttribution.js' 87import type { FileHistoryState } from './utils/fileHistory.js' 88import type { Theme, ThemeName } from './utils/theme.js' 89 90export type QueryChainTracking = { 91 chainId: string 92 depth: number 93} 94 95export type ValidationResult = 96 | { result: true } 97 | { 98 result: false 99 message: string 100 errorCode: number 101 } 102 103export type SetToolJSXFn = ( 104 args: { 105 jsx: React.ReactNode | null 106 shouldHidePromptInput: boolean 107 shouldContinueAnimation?: true 108 showSpinner?: boolean 109 isLocalJSXCommand?: boolean 110 isImmediate?: boolean 111 /** Set to true to clear a local JSX command (e.g., from its onDone callback) */ 112 clearLocalJSX?: boolean 113 } | null, 114) => void 115 116// Import tool permission types from centralized location to break import cycles 117import type { ToolPermissionRulesBySource } from './types/permissions.js' 118 119// Re-export for backwards compatibility 120export type { ToolPermissionRulesBySource } 121 122// Apply DeepImmutable to the imported type 123export type ToolPermissionContext = DeepImmutable<{ 124 mode: PermissionMode 125 additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory> 126 alwaysAllowRules: ToolPermissionRulesBySource 127 alwaysDenyRules: ToolPermissionRulesBySource 128 alwaysAskRules: ToolPermissionRulesBySource 129 isBypassPermissionsModeAvailable: boolean 130 isAutoModeAvailable?: boolean 131 strippedDangerousRules?: ToolPermissionRulesBySource 132 /** When true, permission prompts are auto-denied (e.g., background agents that can't show UI) */ 133 shouldAvoidPermissionPrompts?: boolean 134 /** When true, automated checks (classifier, hooks) are awaited before showing the permission dialog (coordinator workers) */ 135 awaitAutomatedChecksBeforeDialog?: boolean 136 /** Stores the permission mode before model-initiated plan mode entry, so it can be restored on exit */ 137 prePlanMode?: PermissionMode 138}> 139 140export const getEmptyToolPermissionContext: () => ToolPermissionContext = 141 () => ({ 142 mode: 'default', 143 additionalWorkingDirectories: new Map(), 144 alwaysAllowRules: {}, 145 alwaysDenyRules: {}, 146 alwaysAskRules: {}, 147 isBypassPermissionsModeAvailable: false, 148 }) 149 150export type CompactProgressEvent = 151 | { 152 type: 'hooks_start' 153 hookType: 'pre_compact' | 'post_compact' | 'session_start' 154 } 155 | { type: 'compact_start' } 156 | { type: 'compact_end' } 157 158export type ToolUseContext = { 159 options: { 160 commands: Command[] 161 debug: boolean 162 mainLoopModel: string 163 tools: Tools 164 verbose: boolean 165 thinkingConfig: ThinkingConfig 166 mcpClients: MCPServerConnection[] 167 mcpResources: Record<string, ServerResource[]> 168 isNonInteractiveSession: boolean 169 agentDefinitions: AgentDefinitionsResult 170 maxBudgetUsd?: number 171 /** Custom system prompt that replaces the default system prompt */ 172 customSystemPrompt?: string 173 /** Additional system prompt appended after the main system prompt */ 174 appendSystemPrompt?: string 175 /** Override querySource for analytics tracking */ 176 querySource?: QuerySource 177 /** Optional callback to get the latest tools (e.g., after MCP servers connect mid-query) */ 178 refreshTools?: () => Tools 179 } 180 abortController: AbortController 181 readFileState: FileStateCache 182 getAppState(): AppState 183 setAppState(f: (prev: AppState) => AppState): void 184 /** 185 * Always-shared setAppState for session-scoped infrastructure (background 186 * tasks, session hooks). Unlike setAppState, which is no-op for async agents 187 * (see createSubagentContext), this always reaches the root store so agents 188 * at any nesting depth can register/clean up infrastructure that outlives 189 * a single turn. Only set by createSubagentContext; main-thread contexts 190 * fall back to setAppState. 191 */ 192 setAppStateForTasks?: (f: (prev: AppState) => AppState) => void 193 /** 194 * Optional handler for URL elicitations triggered by tool call errors (-32042). 195 * In print/SDK mode, this delegates to structuredIO.handleElicitation. 196 * In REPL mode, this is undefined and the queue-based UI path is used. 197 */ 198 handleElicitation?: ( 199 serverName: string, 200 params: ElicitRequestURLParams, 201 signal: AbortSignal, 202 ) => Promise<ElicitResult> 203 setToolJSX?: SetToolJSXFn 204 addNotification?: (notif: Notification) => void 205 /** Append a UI-only system message to the REPL message list. Stripped at the 206 * normalizeMessagesForAPI boundary — the Exclude<> makes that type-enforced. */ 207 appendSystemMessage?: ( 208 msg: Exclude<SystemMessage, SystemLocalCommandMessage>, 209 ) => void 210 /** Send an OS-level notification (iTerm2, Kitty, Ghostty, bell, etc.) */ 211 sendOSNotification?: (opts: { 212 message: string 213 notificationType: string 214 }) => void 215 nestedMemoryAttachmentTriggers?: Set<string> 216 /** 217 * CLAUDE.md paths already injected as nested_memory attachments this 218 * session. Dedup for memoryFilesToAttachments — readFileState is an LRU 219 * that evicts entries in busy sessions, so its .has() check alone can 220 * re-inject the same CLAUDE.md dozens of times. 221 */ 222 loadedNestedMemoryPaths?: Set<string> 223 dynamicSkillDirTriggers?: Set<string> 224 /** Skill names surfaced via skill_discovery this session. Telemetry only (feeds was_discovered). */ 225 discoveredSkillNames?: Set<string> 226 userModified?: boolean 227 setInProgressToolUseIDs: (f: (prev: Set<string>) => Set<string>) => void 228 /** Only wired in interactive (REPL) contexts; SDK/QueryEngine don't set this. */ 229 setHasInterruptibleToolInProgress?: (v: boolean) => void 230 setResponseLength: (f: (prev: number) => number) => void 231 /** Ant-only: push a new API metrics entry for OTPS tracking. 232 * Called by subagent streaming when a new API request starts. */ 233 pushApiMetricsEntry?: (ttftMs: number) => void 234 setStreamMode?: (mode: SpinnerMode) => void 235 onCompactProgress?: (event: CompactProgressEvent) => void 236 setSDKStatus?: (status: SDKStatus) => void 237 openMessageSelector?: () => void 238 updateFileHistoryState: ( 239 updater: (prev: FileHistoryState) => FileHistoryState, 240 ) => void 241 updateAttributionState: ( 242 updater: (prev: AttributionState) => AttributionState, 243 ) => void 244 setConversationId?: (id: UUID) => void 245 agentId?: AgentId // Only set for subagents; use getSessionId() for session ID. Hooks use this to distinguish subagent calls. 246 agentType?: string // Subagent type name. For the main thread's --agent type, hooks fall back to getMainThreadAgentType(). 247 /** When true, canUseTool must always be called even when hooks auto-approve. 248 * Used by speculation for overlay file path rewriting. */ 249 requireCanUseTool?: boolean 250 messages: Message[] 251 fileReadingLimits?: { 252 maxTokens?: number 253 maxSizeBytes?: number 254 } 255 globLimits?: { 256 maxResults?: number 257 } 258 toolDecisions?: Map< 259 string, 260 { 261 source: string 262 decision: 'accept' | 'reject' 263 timestamp: number 264 } 265 > 266 queryTracking?: QueryChainTracking 267 /** Callback factory for requesting interactive prompts from the user. 268 * Returns a prompt callback bound to the given source name. 269 * Only available in interactive (REPL) contexts. */ 270 requestPrompt?: ( 271 sourceName: string, 272 toolInputSummary?: string | null, 273 ) => (request: PromptRequest) => Promise<PromptResponse> 274 toolUseId?: string 275 criticalSystemReminder_EXPERIMENTAL?: string 276 /** When true, preserve toolUseResult on messages even for subagents. 277 * Used by in-process teammates whose transcripts are viewable by the user. */ 278 preserveToolUseResults?: boolean 279 /** Local denial tracking state for async subagents whose setAppState is a 280 * no-op. Without this, the denial counter never accumulates and the 281 * fallback-to-prompting threshold is never reached. Mutable — the 282 * permissions code updates it in place. */ 283 localDenialTracking?: DenialTrackingState 284 /** 285 * Per-conversation-thread content replacement state for the tool result 286 * budget. When present, query.ts applies the aggregate tool result budget. 287 * Main thread: REPL provisions once (never resets — stale UUID keys 288 * are inert). Subagents: createSubagentContext clones the parent's state 289 * by default (cache-sharing forks need identical decisions), or 290 * resumeAgentBackground threads one reconstructed from sidechain records. 291 */ 292 contentReplacementState?: ContentReplacementState 293 /** 294 * Parent's rendered system prompt bytes, frozen at turn start. 295 * Used by fork subagents to share the parent's prompt cache — re-calling 296 * getSystemPrompt() at fork-spawn time can diverge (GrowthBook cold→warm) 297 * and bust the cache. See forkSubagent.ts. 298 */ 299 renderedSystemPrompt?: SystemPrompt 300} 301 302// Re-export ToolProgressData from centralized location 303export type { ToolProgressData } 304 305export type Progress = ToolProgressData | HookProgress 306 307export type ToolProgress<P extends ToolProgressData> = { 308 toolUseID: string 309 data: P 310} 311 312export function filterToolProgressMessages( 313 progressMessagesForMessage: ProgressMessage[], 314): ProgressMessage<ToolProgressData>[] { 315 return progressMessagesForMessage.filter( 316 (msg): msg is ProgressMessage<ToolProgressData> => 317 msg.data?.type !== 'hook_progress', 318 ) 319} 320 321export type ToolResult<T> = { 322 data: T 323 newMessages?: ( 324 | UserMessage 325 | AssistantMessage 326 | AttachmentMessage 327 | SystemMessage 328 )[] 329 // contextModifier is only honored for tools that aren't concurrency safe. 330 contextModifier?: (context: ToolUseContext) => ToolUseContext 331 /** MCP protocol metadata (structuredContent, _meta) to pass through to SDK consumers */ 332 mcpMeta?: { 333 _meta?: Record<string, unknown> 334 structuredContent?: Record<string, unknown> 335 } 336} 337 338export type ToolCallProgress<P extends ToolProgressData = ToolProgressData> = ( 339 progress: ToolProgress<P>, 340) => void 341 342// Type for any schema that outputs an object with string keys 343export type AnyObject = z.ZodType<{ [key: string]: unknown }> 344 345/** 346 * Checks if a tool matches the given name (primary name or alias). 347 */ 348export function toolMatchesName( 349 tool: { name: string; aliases?: string[] }, 350 name: string, 351): boolean { 352 return tool.name === name || (tool.aliases?.includes(name) ?? false) 353} 354 355/** 356 * Finds a tool by name or alias from a list of tools. 357 */ 358export function findToolByName(tools: Tools, name: string): Tool | undefined { 359 return tools.find(t => toolMatchesName(t, name)) 360} 361 362export type Tool< 363 Input extends AnyObject = AnyObject, 364 Output = unknown, 365 P extends ToolProgressData = ToolProgressData, 366> = { 367 /** 368 * Optional aliases for backwards compatibility when a tool is renamed. 369 * The tool can be looked up by any of these names in addition to its primary name. 370 */ 371 aliases?: string[] 372 /** 373 * One-line capability phrase used by ToolSearch for keyword matching. 374 * Helps the model find this tool via keyword search when it's deferred. 375 * 3–10 words, no trailing period. 376 * Prefer terms not already in the tool name (e.g. 'jupyter' for NotebookEdit). 377 */ 378 searchHint?: string 379 call( 380 args: z.infer<Input>, 381 context: ToolUseContext, 382 canUseTool: CanUseToolFn, 383 parentMessage: AssistantMessage, 384 onProgress?: ToolCallProgress<P>, 385 ): Promise<ToolResult<Output>> 386 description( 387 input: z.infer<Input>, 388 options: { 389 isNonInteractiveSession: boolean 390 toolPermissionContext: ToolPermissionContext 391 tools: Tools 392 }, 393 ): Promise<string> 394 readonly inputSchema: Input 395 // Type for MCP tools that can specify their input schema directly in JSON Schema format 396 // rather than converting from Zod schema 397 readonly inputJSONSchema?: ToolInputJSONSchema 398 // Optional because TungstenTool doesn't define this. TODO: Make it required. 399 // When we do that, we can also go through and make this a bit more type-safe. 400 outputSchema?: z.ZodType<unknown> 401 inputsEquivalent?(a: z.infer<Input>, b: z.infer<Input>): boolean 402 isConcurrencySafe(input: z.infer<Input>): boolean 403 isEnabled(): boolean 404 isReadOnly(input: z.infer<Input>): boolean 405 /** Defaults to false. Only set when the tool performs irreversible operations (delete, overwrite, send). */ 406 isDestructive?(input: z.infer<Input>): boolean 407 /** 408 * What should happen when the user submits a new message while this tool 409 * is running. 410 * 411 * - `'cancel'` — stop the tool and discard its result 412 * - `'block'` — keep running; the new message waits 413 * 414 * Defaults to `'block'` when not implemented. 415 */ 416 interruptBehavior?(): 'cancel' | 'block' 417 /** 418 * Returns information about whether this tool use is a search or read operation 419 * that should be collapsed into a condensed display in the UI. Examples include 420 * file searching (Grep, Glob), file reading (Read), and bash commands like find, 421 * grep, wc, etc. 422 * 423 * Returns an object indicating whether the operation is a search or read operation: 424 * - `isSearch: true` for search operations (grep, find, glob patterns) 425 * - `isRead: true` for read operations (cat, head, tail, file read) 426 * - `isList: true` for directory-listing operations (ls, tree, du) 427 * - All can be false if the operation shouldn't be collapsed 428 */ 429 isSearchOrReadCommand?(input: z.infer<Input>): { 430 isSearch: boolean 431 isRead: boolean 432 isList?: boolean 433 } 434 isOpenWorld?(input: z.infer<Input>): boolean 435 requiresUserInteraction?(): boolean 436 isMcp?: boolean 437 isLsp?: boolean 438 /** 439 * When true, this tool is deferred (sent with defer_loading: true) and requires 440 * ToolSearch to be used before it can be called. 441 */ 442 readonly shouldDefer?: boolean 443 /** 444 * When true, this tool is never deferred — its full schema appears in the 445 * initial prompt even when ToolSearch is enabled. For MCP tools, set via 446 * `_meta['anthropic/alwaysLoad']`. Use for tools the model must see on 447 * turn 1 without a ToolSearch round-trip. 448 */ 449 readonly alwaysLoad?: boolean 450 /** 451 * For MCP tools: the server and tool names as received from the MCP server (unnormalized). 452 * Present on all MCP tools regardless of whether `name` is prefixed (mcp__server__tool) 453 * or unprefixed (CLAUDE_AGENT_SDK_MCP_NO_PREFIX mode). 454 */ 455 mcpInfo?: { serverName: string; toolName: string } 456 readonly name: string 457 /** 458 * Maximum size in characters for tool result before it gets persisted to disk. 459 * When exceeded, the result is saved to a file and Claude receives a preview 460 * with the file path instead of the full content. 461 * 462 * Set to Infinity for tools whose output must never be persisted (e.g. Read, 463 * where persisting creates a circular Read→file→Read loop and the tool 464 * already self-bounds via its own limits). 465 */ 466 maxResultSizeChars: number 467 /** 468 * When true, enables strict mode for this tool, which causes the API to 469 * more strictly adhere to tool instructions and parameter schemas. 470 * Only applied when the tengu_tool_pear is enabled. 471 */ 472 readonly strict?: boolean 473 474 /** 475 * Called on copies of tool_use input before observers see it (SDK stream, 476 * transcript, canUseTool, PreToolUse/PostToolUse hooks). Mutate in place 477 * to add legacy/derived fields. Must be idempotent. The original API-bound 478 * input is never mutated (preserves prompt cache). Not re-applied when a 479 * hook/permission returns a fresh updatedInput — those own their shape. 480 */ 481 backfillObservableInput?(input: Record<string, unknown>): void 482 483 /** 484 * Determines if this tool is allowed to run with this input in the current context. 485 * It informs the model of why the tool use failed, and does not directly display any UI. 486 * @param input 487 * @param context 488 */ 489 validateInput?( 490 input: z.infer<Input>, 491 context: ToolUseContext, 492 ): Promise<ValidationResult> 493 494 /** 495 * Determines if the user is asked for permission. Only called after validateInput() passes. 496 * General permission logic is in permissions.ts. This method contains tool-specific logic. 497 * @param input 498 * @param context 499 */ 500 checkPermissions( 501 input: z.infer<Input>, 502 context: ToolUseContext, 503 ): Promise<PermissionResult> 504 505 // Optional method for tools that operate on a file path 506 getPath?(input: z.infer<Input>): string 507 508 /** 509 * Prepare a matcher for hook `if` conditions (permission-rule patterns like 510 * "git *" from "Bash(git *)"). Called once per hook-input pair; any 511 * expensive parsing happens here. Returns a closure that is called per 512 * hook pattern. If not implemented, only tool-name-level matching works. 513 */ 514 preparePermissionMatcher?( 515 input: z.infer<Input>, 516 ): Promise<(pattern: string) => boolean> 517 518 prompt(options: { 519 getToolPermissionContext: () => Promise<ToolPermissionContext> 520 tools: Tools 521 agents: AgentDefinition[] 522 allowedAgentTypes?: string[] 523 }): Promise<string> 524 userFacingName(input: Partial<z.infer<Input>> | undefined): string 525 userFacingNameBackgroundColor?( 526 input: Partial<z.infer<Input>> | undefined, 527 ): keyof Theme | undefined 528 /** 529 * Transparent wrappers (e.g. REPL) delegate all rendering to their progress 530 * handler, which emits native-looking blocks for each inner tool call. 531 * The wrapper itself shows nothing. 532 */ 533 isTransparentWrapper?(): boolean 534 /** 535 * Returns a short string summary of this tool use for display in compact views. 536 * @param input The tool input 537 * @returns A short string summary, or null to not display 538 */ 539 getToolUseSummary?(input: Partial<z.infer<Input>> | undefined): string | null 540 /** 541 * Returns a human-readable present-tense activity description for spinner display. 542 * Example: "Reading src/foo.ts", "Running bun test", "Searching for pattern" 543 * @param input The tool input 544 * @returns Activity description string, or null to fall back to tool name 545 */ 546 getActivityDescription?( 547 input: Partial<z.infer<Input>> | undefined, 548 ): string | null 549 /** 550 * Returns a compact representation of this tool use for the auto-mode 551 * security classifier. Examples: `ls -la` for Bash, `/tmp/x: new content` 552 * for Edit. Return '' to skip this tool in the classifier transcript 553 * (e.g. tools with no security relevance). May return an object to avoid 554 * double-encoding when the caller JSON-wraps the value. 555 */ 556 toAutoClassifierInput(input: z.infer<Input>): unknown 557 mapToolResultToToolResultBlockParam( 558 content: Output, 559 toolUseID: string, 560 ): ToolResultBlockParam 561 /** 562 * Optional. When omitted, the tool result renders nothing (same as returning 563 * null). Omit for tools whose results are surfaced elsewhere (e.g., TodoWrite 564 * updates the todo panel, not the transcript). 565 */ 566 renderToolResultMessage?( 567 content: Output, 568 progressMessagesForMessage: ProgressMessage<P>[], 569 options: { 570 style?: 'condensed' 571 theme: ThemeName 572 tools: Tools 573 verbose: boolean 574 isTranscriptMode?: boolean 575 isBriefOnly?: boolean 576 /** Original tool_use input, when available. Useful for compact result 577 * summaries that reference what was requested (e.g. "Sent to #foo"). */ 578 input?: unknown 579 }, 580 ): React.ReactNode 581 /** 582 * Flattened text of what renderToolResultMessage shows IN TRANSCRIPT 583 * MODE (verbose=true, isTranscriptMode=true). For transcript search 584 * indexing: the index counts occurrences in this string, the highlight 585 * overlay scans the actual screen buffer. For count ≡ highlight, this 586 * must return the text that ends up visible — not the model-facing 587 * serialization from mapToolResultToToolResultBlockParam (which adds 588 * system-reminders, persisted-output wrappers). 589 * 590 * Chrome can be skipped (under-count is fine). "Found 3 files in 12ms" 591 * isn't worth indexing. Phantoms are not fine — text that's claimed 592 * here but doesn't render is a count≠highlight bug. 593 * 594 * Optional: omitted → field-name heuristic in transcriptSearch.ts. 595 * Drift caught by test/utils/transcriptSearch.renderFidelity.test.tsx 596 * which renders sample outputs and flags text that's indexed-but-not- 597 * rendered (phantom) or rendered-but-not-indexed (under-count warning). 598 */ 599 extractSearchText?(out: Output): string 600 /** 601 * Render the tool use message. Note that `input` is partial because we render 602 * the message as soon as possible, possibly before tool parameters have fully 603 * streamed in. 604 */ 605 renderToolUseMessage( 606 input: Partial<z.infer<Input>>, 607 options: { theme: ThemeName; verbose: boolean; commands?: Command[] }, 608 ): React.ReactNode 609 /** 610 * Returns true when the non-verbose rendering of this output is truncated 611 * (i.e., clicking to expand would reveal more content). Gates 612 * click-to-expand in fullscreen — only messages where verbose actually 613 * shows more get a hover/click affordance. Unset means never truncated. 614 */ 615 isResultTruncated?(output: Output): boolean 616 /** 617 * Renders an optional tag to display after the tool use message. 618 * Used for additional metadata like timeout, model, resume ID, etc. 619 * Returns null to not display anything. 620 */ 621 renderToolUseTag?(input: Partial<z.infer<Input>>): React.ReactNode 622 /** 623 * Optional. When omitted, no progress UI is shown while the tool runs. 624 */ 625 renderToolUseProgressMessage?( 626 progressMessagesForMessage: ProgressMessage<P>[], 627 options: { 628 tools: Tools 629 verbose: boolean 630 terminalSize?: { columns: number; rows: number } 631 inProgressToolCallCount?: number 632 isTranscriptMode?: boolean 633 }, 634 ): React.ReactNode 635 renderToolUseQueuedMessage?(): React.ReactNode 636 /** 637 * Optional. When omitted, falls back to <FallbackToolUseRejectedMessage />. 638 * Only define this for tools that need custom rejection UI (e.g., file edits 639 * that show the rejected diff). 640 */ 641 renderToolUseRejectedMessage?( 642 input: z.infer<Input>, 643 options: { 644 columns: number 645 messages: Message[] 646 style?: 'condensed' 647 theme: ThemeName 648 tools: Tools 649 verbose: boolean 650 progressMessagesForMessage: ProgressMessage<P>[] 651 isTranscriptMode?: boolean 652 }, 653 ): React.ReactNode 654 /** 655 * Optional. When omitted, falls back to <FallbackToolUseErrorMessage />. 656 * Only define this for tools that need custom error UI (e.g., search tools 657 * that show "File not found" instead of the raw error). 658 */ 659 renderToolUseErrorMessage?( 660 result: ToolResultBlockParam['content'], 661 options: { 662 progressMessagesForMessage: ProgressMessage<P>[] 663 tools: Tools 664 verbose: boolean 665 isTranscriptMode?: boolean 666 }, 667 ): React.ReactNode 668 669 /** 670 * Renders multiple parallel instances of this tool as a group. 671 * @returns React node to render, or null to fall back to individual rendering 672 */ 673 /** 674 * Renders multiple tool uses as a group (non-verbose mode only). 675 * In verbose mode, individual tool uses render at their original positions. 676 * @returns React node to render, or null to fall back to individual rendering 677 */ 678 renderGroupedToolUse?( 679 toolUses: Array<{ 680 param: ToolUseBlockParam 681 isResolved: boolean 682 isError: boolean 683 isInProgress: boolean 684 progressMessages: ProgressMessage<P>[] 685 result?: { 686 param: ToolResultBlockParam 687 output: unknown 688 } 689 }>, 690 options: { 691 shouldAnimate: boolean 692 tools: Tools 693 }, 694 ): React.ReactNode | null 695} 696 697/** 698 * A collection of tools. Use this type instead of `Tool[]` to make it easier 699 * to track where tool sets are assembled, passed, and filtered across the codebase. 700 */ 701export type Tools = readonly Tool[] 702 703/** 704 * Methods that `buildTool` supplies a default for. A `ToolDef` may omit these; 705 * the resulting `Tool` always has them. 706 */ 707type DefaultableToolKeys = 708 | 'isEnabled' 709 | 'isConcurrencySafe' 710 | 'isReadOnly' 711 | 'isDestructive' 712 | 'checkPermissions' 713 | 'toAutoClassifierInput' 714 | 'userFacingName' 715 716/** 717 * Tool definition accepted by `buildTool`. Same shape as `Tool` but with the 718 * defaultable methods optional — `buildTool` fills them in so callers always 719 * see a complete `Tool`. 720 */ 721export type ToolDef< 722 Input extends AnyObject = AnyObject, 723 Output = unknown, 724 P extends ToolProgressData = ToolProgressData, 725> = Omit<Tool<Input, Output, P>, DefaultableToolKeys> & 726 Partial<Pick<Tool<Input, Output, P>, DefaultableToolKeys>> 727 728/** 729 * Type-level spread mirroring `{ ...TOOL_DEFAULTS, ...def }`. For each 730 * defaultable key: if D provides it (required), D's type wins; if D omits 731 * it or has it optional (inherited from Partial<> in the constraint), the 732 * default fills in. All other keys come from D verbatim — preserving arity, 733 * optional presence, and literal types exactly as `satisfies Tool` did. 734 */ 735type BuiltTool<D> = Omit<D, DefaultableToolKeys> & { 736 [K in DefaultableToolKeys]-?: K extends keyof D 737 ? undefined extends D[K] 738 ? ToolDefaults[K] 739 : D[K] 740 : ToolDefaults[K] 741} 742 743/** 744 * Build a complete `Tool` from a partial definition, filling in safe defaults 745 * for the commonly-stubbed methods. All tool exports should go through this so 746 * that defaults live in one place and callers never need `?.() ?? default`. 747 * 748 * Defaults (fail-closed where it matters): 749 * - `isEnabled` → `true` 750 * - `isConcurrencySafe` → `false` (assume not safe) 751 * - `isReadOnly` → `false` (assume writes) 752 * - `isDestructive` → `false` 753 * - `checkPermissions` → `{ behavior: 'allow', updatedInput }` (defer to general permission system) 754 * - `toAutoClassifierInput` → `''` (skip classifier — security-relevant tools must override) 755 * - `userFacingName` → `name` 756 */ 757const TOOL_DEFAULTS = { 758 isEnabled: () => true, 759 isConcurrencySafe: (_input?: unknown) => false, 760 isReadOnly: (_input?: unknown) => false, 761 isDestructive: (_input?: unknown) => false, 762 checkPermissions: ( 763 input: { [key: string]: unknown }, 764 _ctx?: ToolUseContext, 765 ): Promise<PermissionResult> => 766 Promise.resolve({ behavior: 'allow', updatedInput: input }), 767 toAutoClassifierInput: (_input?: unknown) => '', 768 userFacingName: (_input?: unknown) => '', 769} 770 771// The defaults type is the ACTUAL shape of TOOL_DEFAULTS (optional params so 772// both 0-arg and full-arg call sites type-check — stubs varied in arity and 773// tests relied on that), not the interface's strict signatures. 774type ToolDefaults = typeof TOOL_DEFAULTS 775 776// D infers the concrete object-literal type from the call site. The 777// constraint provides contextual typing for method parameters; `any` in 778// constraint position is structural and never leaks into the return type. 779// BuiltTool<D> mirrors runtime `{...TOOL_DEFAULTS, ...def}` at the type level. 780// eslint-disable-next-line @typescript-eslint/no-explicit-any 781type AnyToolDef = ToolDef<any, any, any> 782 783export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> { 784 // The runtime spread is straightforward; the `as` bridges the gap between 785 // the structural-any constraint and the precise BuiltTool<D> return. The 786 // type semantics are proven by the 0-error typecheck across all 60+ tools. 787 return { 788 ...TOOL_DEFAULTS, 789 userFacingName: () => def.name, 790 ...def, 791 } as BuiltTool<D> 792}