Tool.ts 29 KB

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