Claude Code 源码分析Claude Code 源码分析
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
  • 概览

    • Claude Code 源码分析
    • 源码统计
    • CodeGraph 图谱
  • 架构

    • 系统架构
    • UML 图表索引
    • 查询引擎
    • 核心流程
    • 消息系统
    • 状态管理
  • 功能模块

    • 工具系统
    • 斜杠命令
    • 服务层
    • MCP 协议
    • Skills 技能
    • 子代理系统
  • 分层深度

    • 入口层
    • UI / Ink 层
    • utils 基础设施
    • 桥接 / 远程
    • 上下文压缩
  • 原理与安全

    • 底层原理
    • 技术难点
    • 权限与安全
    • 内部机制
    • 遥测与分析
  • 深度专题

    • Hooks 系统
    • 插件系统
    • 记忆系统
    • API 通信层
    • Ink 终端 UI
    • 认证系统
    • 构建与发布
    • 术语表
  • 调用分析

    • 调用链分析
    • 核心文件索引
  • 模块详解

    • utils

      • 模块: utils
      • messages · 消息工厂与规范化
      • session-storage · JSONL 会话持久化
      • permissions · 工具权限决策
      • shell-hooks · 用户 Shell Hook 系统
    • components

      • 模块: components
      • REPL · 主屏编排
      • messages · 消息行渲染
      • PermissionRequest · 权限弹窗
      • PromptInput · 底部输入
    • services

      • 模块: services
      • api-claude · Anthropic API 流式与重试
      • mcp-client · MCP 连接与工具调用
      • compact · 上下文压缩与自动触发
      • analytics · GrowthBook、Datadog 与 1P 事件
    • tools

      • 模块: tools
      • tool-interface · Tool 契约与注册表
      • bash-tool · Shell 执行与权限
      • streaming-executor · 流式工具并发调度
      • agent-tool · 子 Agent 委派
    • commands

      • 模块: commands
      • command-registry · commands.ts 注册与分派
      • model-command · /model 模型选择
      • mcp-commands · /mcp 服务器管理
      • compact-memory-commands · /compact 与 /memory
    • ink

      • 模块: ink
      • Ink 渲染管线 · Screen 与终端输出
      • 终端事件 · resize、paste、stdin
      • Ink Hooks · 输入、搜索、终端状态
      • Ink 组件 · Box、Text、ScrollBox 原语
    • hooks

      • 模块: hooks
      • useCanUseTool · 权限 UI 接缝
      • 输入与快捷键 Hook
      • 合并态 Hook(MCP + 本地)
      • notifs 通知 Hook
    • bridge

      • 模块: bridge
      • repl-bridge · REPL 桥初始化与传输
      • bridge-messaging · 桥消息路由与入站处理
      • remote-bridge-core · env-less 核心与守护主循环
      • bridge-permissions-ui · 权限、API 与 TUI
    • cli

      • 模块: cli
      • Structured IO · NDJSON SDK 协议
      • CLI Transports · Session Ingress 传输层
      • CLI Handlers · 子命令懒加载实现
      • Update & Upload · 自更新与串行上传原语
    • screens

      • 模块: screens
      • REPL 屏 · Screen 类型与顶层路由
      • ResumeConversation · 会话恢复选择器
      • Doctor · 安装诊断全屏
    • entrypoints

      • 模块: entrypoints
      • cli-entrypoint · Bootstrap 与快路径
      • sdk-types · core / control / runtime 类型体系
      • mcp-entrypoint · MCP stdio 服务器
      • sandbox-types · 沙箱配置单一真相源
    • skills

      • 模块: skills
      • skills-loading · 磁盘加载与 bundled 注册表
      • bundled-skills · 内置 skill 与 initBundledSkills
      • mcp-skills · MCP prompt 转 skill
      • skill-tool-integration · SkillTool 与命令注册
    • types

      • 模块: types
      • message-types · Message 联合与 content blocks
      • tool-permission-types · Tool、Permission、Command 类型
      • api-sdk-types · API 与 Hooks 协议类型
      • misc-types · ids、plugin、generated 与其余类型
    • tasks

      • 模块: tasks
      • local-agent-task · 本地 Agent 与主会话后台化
      • remote-agent-task · 远程 CCR 与 In-Process Teammate
      • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
      • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybindings

      • 模块: keybindings
      • keybinding-registry · 注册、Provider 与 useKeybinding
      • default-bindings · 默认键位表与平台差异
      • command-bindings · command:* 动态斜杠命令绑定
      • vim-bindings · Vim 模式与 keybindings 边界
    • memdir

      • 模块: memdir
      • memdir-core · 路径、加载与 MEMORY.md
      • memory-extraction · extractMemories 与 SessionMemory
      • memdir-commands · /memory、/remember 与命令集成
    • state

      • 模块: state
      • app-state-core · store、AppState 类型与 Provider
      • app-state-selectors · selectors 与 onChangeAppState
      • teammate-state · 队友视图与 swarm 状态
      • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query

      • 模块: query
      • query config 与 deps · 配置快照与依赖注入
      • query tokenBudget · +500k 自动续跑
      • query transitions · Continue / Terminal 状态机
      • query stopHooks · Stop 事件与 turn 结束编排
  • 模块详解(扩展)

    • messages · 消息工厂与规范化
    • session-storage · JSONL 会话持久化
    • permissions · 工具权限决策
    • shell-hooks · 用户 Shell Hook 系统
    • REPL · 主屏编排
    • messages · 消息行渲染
    • PermissionRequest · 权限弹窗
    • PromptInput · 底部输入
    • api-claude · Anthropic API 流式与重试
    • mcp-client · MCP 连接与工具调用
    • compact · 上下文压缩与自动触发
    • analytics · GrowthBook、Datadog 与 1P 事件
    • tool-interface · Tool 契约与注册表
    • bash-tool · Shell 执行与权限
    • streaming-executor · 流式工具并发调度
    • agent-tool · 子 Agent 委派
    • command-registry · commands.ts 注册与分派
    • model-command · /model 模型选择
    • mcp-commands · /mcp 服务器管理
    • compact-memory-commands · /compact 与 /memory
    • Ink 渲染管线 · Screen 与终端输出
    • 终端事件 · resize、paste、stdin
    • Ink Hooks · 输入、搜索、终端状态
    • Ink 组件 · Box、Text、ScrollBox 原语
    • useCanUseTool · 权限 UI 接缝
    • 输入与快捷键 Hook
    • 合并态 Hook(MCP + 本地)
    • notifs 通知 Hook
    • repl-bridge · REPL 桥初始化与传输
    • bridge-messaging · 桥消息路由与入站处理
    • remote-bridge-core · env-less 核心与守护主循环
    • bridge-permissions-ui · 权限、API 与 TUI
    • Structured IO · NDJSON SDK 协议
    • CLI Transports · Session Ingress 传输层
    • CLI Handlers · 子命令懒加载实现
    • Update & Upload · 自更新与串行上传原语
    • REPL 屏 · Screen 类型与顶层路由
    • ResumeConversation · 会话恢复选择器
    • Doctor · 安装诊断全屏
    • cli-entrypoint · Bootstrap 与快路径
    • sdk-types · core / control / runtime 类型体系
    • mcp-entrypoint · MCP stdio 服务器
    • sandbox-types · 沙箱配置单一真相源
    • skills-loading · 磁盘加载与 bundled 注册表
    • bundled-skills · 内置 skill 与 initBundledSkills
    • mcp-skills · MCP prompt 转 skill
    • skill-tool-integration · SkillTool 与命令注册
    • message-types · Message 联合与 content blocks
    • tool-permission-types · Tool、Permission、Command 类型
    • api-sdk-types · API 与 Hooks 协议类型
    • misc-types · ids、plugin、generated 与其余类型
    • local-agent-task · 本地 Agent 与主会话后台化
    • remote-agent-task · 远程 CCR 与 In-Process Teammate
    • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
    • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybinding-registry · 注册、Provider 与 useKeybinding
    • default-bindings · 默认键位表与平台差异
    • command-bindings · command:* 动态斜杠命令绑定
    • vim-bindings · Vim 模式与 keybindings 边界
    • memdir-core · 路径、加载与 MEMORY.md
    • memory-extraction · extractMemories 与 SessionMemory
    • memdir-commands · /memory、/remember 与命令集成
    • app-state-core · store、AppState 类型与 Provider
    • app-state-selectors · selectors 与 onChangeAppState
    • teammate-state · 队友视图与 swarm 状态
    • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query config 与 deps · 配置快照与依赖注入
    • query tokenBudget · +500k 自动续跑
    • query transitions · Continue / Terminal 状态机
    • query stopHooks · Stop 事件与 turn 结束编排
  • 工具详解

    • tool-interface · Tool 契约与注册表
    • tool-permission-types · Tool、Permission、Command 类型
    • 工具: Bash
    • 工具: PowerShell
    • 工具: Agent
    • 工具: LSP
    • 工具: FileEdit
    • 工具: FileRead
    • 工具: Skill
    • 工具: WebFetch
    • 工具: MCP
    • 工具: SendMessage
    • 工具: FileWrite
    • 工具: Config
    • 工具: Grep
    • 工具: Brief
    • 工具: ExitPlanMode
    • 工具: ToolSearch
    • 工具: NotebookEdit
    • 工具: TaskOutput
    • 工具: WebSearch
    • 工具: ScheduleCron

本章总览

Tool.ts(约 790 行)定义 Claude Code 全部工具共享的 TypeScript 契约:Tool 泛型接口、ToolUseContext 运行时上下文、buildTool 默认值工厂,以及 toolMatchesName / findToolByName 查找辅助。tools.ts 是内置工具的「注册表」:getAllBaseTools 列出所有可能工具,getTools 按权限与环境过滤,assembleToolPool 与 MCP 合并。本章要求你能从 API 看到的 tool name 反查到具体 Tool 导出,并理解 call 签名中各参数的职责。

学完本章你应该能

  • 列举 Tool 接口的核心方法及其在生命周期中的调用顺序
  • 解释 buildTool 的 fail-closed 默认值策略
  • 说明 getTools 与 assembleToolPool 的差异与使用场景
  • 理解 ToolUseContext 中 abortController、setToolJSX、messages 的用途
  • 能在 feature gate / env 条件下预测某工具是否出现在 getAllBaseTools

核心概念(先读懂这些)

Tool 不是 MCP Tool 的简单包装

内置 Tool 与 MCP Tool 最终都满足同一 Tool 接口,但构造路径不同:内置通过 buildTool({ name, call, ... }) 导出常量;MCP 在 client 连接后动态合成。接口上的 isMcp、mcpInfo、inputJSONSchema 等字段主要服务 MCP 路径。读 call() 时要分清 context.options.tools 是当前 turn 的快照还是 refreshTools 刷新后的池。

ToolUseContext 是会话级依赖注入

call() 第二个参数 ToolUseContext 携带 abortController、getAppState/setAppState、readFileState、messages 等。子 Agent 通过 createSubagentContext 克隆并收窄权限;setAppState 对 async agent 可能是 no-op,setAppStateForTasks 始终到达根 store。render* 方法不接收 context,但 call 内可通过 setToolJSX 驱动 REPL overlay。

buildTool 集中默认值

60+ 工具通过 buildTool 定义,避免每个文件重复 isEnabled/isConcurrencySafe/checkPermissions 等 stub。默认 isConcurrencySafe=false(保守)、checkPermissions=allow(交给 permissions.ts)。安全相关工具必须 override toAutoClassifierInput,否则分类器跳过该工具。

建议学习步骤

  1. 阅读 Tool 接口定义(call、checkPermissions、validateInput)
  2. 阅读 buildTool 与 TOOL_DEFAULTS
  3. 阅读 getAllBaseTools 的条件展开
  4. 阅读 getTools 过滤链
  5. 阅读 assembleToolPool 排序与去重
  6. 在源码树打开 Tool.ts 对照行号

常见误区

注意

不要把 ToolProgress 与 HookProgress 混为一谈(filterToolProgressMessages 负责分离)

注意

getMergedTools 不去重,assembleToolPool 才去重;token 计数场景注意选对函数

注意

REPL 模式下 REPL_ONLY_TOOLS 隐藏原始 Bash/Read,但 REPL VM 内仍可调用

在架构中的位置

工具生命周期:

tools.ts getTools / assembleToolPool
  → query 把 Tools 放入 toolUseContext.options.tools
  → API 请求携带 tool schemas(description + inputSchema)
  → 模型返回 tool_use blocks
  → StreamingToolExecutor.addTool → runToolUse → tool.call()
  → mapToolResultToToolResultBlockParam → user tool_result message

Tool.ts 位于依赖图中心但被刻意保持「无具体工具 import」:类型与工厂 only。tools.ts 是唯一知道「有哪些内置工具」的模块,且与 GrowthBook dynamic config 注释要求保持同步。

Tool 泛型接口:call 与权限钩子

Tool<Input, Output, P> 的核心方法:

方法阶段说明
validateInput?call 前返回 ValidationResult,失败则模型看到 errorCode
checkPermissionscanUseTool 内工具特有权限(Bash 命令模式等)
call执行返回 ToolResult { data, newMessages?, contextModifier? }
mapToolResultToToolResultBlockParam结果序列化转为 API tool_result block
renderTool*UIInk 组件,与模型 payload 可不同

call 签名:

call(args, context, canUseTool, parentMessage, onProgress?)

canUseTool 由 useCanUseTool / permissions 提供;多数工具在 call 内不再调用,但 FileEdit 等可能在执行前二次确认。onProgress 推送 ProgressMessage,StreamingToolExecutor 立即 yield。

并发语义: isConcurrencySafe(input) 决定 StreamingToolExecutor 能否与其他 safe 工具并行。默认 false。

源码引用: src/Tool.ts · 第 362–420 行(共 793 行)

 362| export 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,

源码引用: src/Tool.ts · 第 489–504 行(共 793 行)

 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| 

ToolUseContext 关键字段

ToolUseContext(约 158 行起)是会话级依赖容器:

options: commands、mainLoopModel、tools、agentDefinitions、mcpClients、refreshTools 等。tools 字段是当前 turn 可用工具快照。

状态回调:

  • setInProgressToolUseIDs — REPL 显示 spinner / 禁止重复提交
  • setHasInterruptibleToolInProgress — ESC 中断语义(interruptBehavior=cancel 的工具)
  • setToolJSX — 工具驱动的全屏 overlay(如 AskUserQuestion)
  • appendSystemMessage — UI-only system 行,API 边界 strip

子 Agent 专用:

  • agentId / agentType — hooks 区分主线程与子 agent
  • setAppStateForTasks — 后台任务注册不受 async no-op 影响
  • contentReplacementState — tool result 预算替换
  • renderedSystemPrompt — fork 子 agent 共享父 prompt cache

读 call 实现时,先确认 toolUseContext.agentId 是否存在,许多工具(Bash cwd、权限)行为因此不同。

源码引用: src/Tool.ts · 第 158–210 行(共 793 行)

 158| export 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.) */

源码引用: src/Tool.ts · 第 245–300 行(共 793 行)

 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| }

buildTool 与 TOOL_DEFAULTS

buildTool(def) 展开 { ...TOOL_DEFAULTS, userFacingName: () =&gt; def.name, ...def }。

默认值(fail-closed):

  • isEnabled → true
  • isConcurrencySafe → false
  • isReadOnly → false
  • isDestructive → false
  • checkPermissions → allow(defer 到 permissions.ts)
  • toAutoClassifierInput → ''(跳过 classifier)

ToolDef 类型允许省略 DefaultableToolKeys;BuiltTool<D> 类型级保证最终 Tool 完整。

工程意义: 新增工具时只需实现 call、inputSchema、prompt、render* 等差异化方法;不必复制 7 个 stub。若忘记 override isConcurrencySafe,工具会自动串行执行——对写操作通常是正确默认。

源码引用: src/Tool.ts · 第 743–792 行(共 793 行)

 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|  */
 757| const 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.
 774| type 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
 781| type AnyToolDef = ToolDef<any, any, any>
 782| 
 783| export 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| }

toolMatchesName 与 findToolByName

工具可声明 aliases(如 AgentTool 的 Task 旧名)。toolMatchesName 同时匹配 name 与 aliases;findToolByName 在 Tools 数组上查找。

StreamingToolExecutor.addTool 与 runToolUse 均通过 findToolByName 解析 block.name。权限规则、hooks、analytics 也应使用同一 helper,避免 Task vs Agent 分裂。

searchHint 字段供 ToolSearch 关键词匹配:3–10 词,不含 tool name 已有词汇。shouldDefer / alwaysLoad 控制 ToolSearch 实验下的 schema 可见性。

源码引用: src/Tool.ts · 第 345–360 行(共 793 行)

 345| /**
 346|  * Checks if a tool matches the given name (primary name or alias).
 347|  */
 348| export 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|  */
 358| export function findToolByName(tools: Tools, name: string): Tool | undefined {
 359|   return tools.find(t => toolMatchesName(t, name))
 360| }

源码引用: src/Tool.ts · 第 436–449 行(共 793 行)

 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

getAllBaseTools:条件注册

getAllBaseTools(约 193 行)返回当前构建环境下可能存在的全部内置工具数组。注释强调必须与 Statsig claude_code_global_system_caching 保持同步。

典型条件分支:

  • hasEmbeddedSearchTools() — ant 内置 bfs/ugrep 时省略 Glob/Grep
  • feature('KAIROS') / feature('AGENT_TRIGGERS') — 定时与远程触发工具
  • isTodoV2Enabled() — TaskCreate/Get/Update/List
  • isReplModeEnabled() + REPLTool — ant 内置 REPL
  • isToolSearchEnabledOptimistic() — ToolSearchTool
  • process.env.USER_TYPE === 'ant' — ConfigTool、TungstenTool 等

lazy require(TeamCreateTool 等)打破 tools.ts ↔ 具体工具的循环依赖。读此函数是理解「为什么某环境缺少某工具」的最佳入口。

源码引用: src/tools.ts · 第 193–251 行(共 390 行)

 193| export function getAllBaseTools(): Tools {
 194|   return [
 195|     AgentTool,
 196|     TaskOutputTool,
 197|     BashTool,
 198|     // Ant-native builds have bfs/ugrep embedded in the bun binary (same ARGV0
 199|     // trick as ripgrep). When available, find/grep in Claude's shell are aliased
 200|     // to these fast tools, so the dedicated Glob/Grep tools are unnecessary.
 201|     ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
 202|     ExitPlanModeV2Tool,
 203|     FileReadTool,
 204|     FileEditTool,
 205|     FileWriteTool,
 206|     NotebookEditTool,
 207|     WebFetchTool,
 208|     TodoWriteTool,
 209|     WebSearchTool,
 210|     TaskStopTool,
 211|     AskUserQuestionTool,
 212|     SkillTool,
 213|     EnterPlanModeTool,
 214|     ...(process.env.USER_TYPE === 'ant' ? [ConfigTool] : []),
 215|     ...(process.env.USER_TYPE === 'ant' ? [TungstenTool] : []),
 216|     ...(SuggestBackgroundPRTool ? [SuggestBackgroundPRTool] : []),
 217|     ...(WebBrowserTool ? [WebBrowserTool] : []),
 218|     ...(isTodoV2Enabled()
 219|       ? [TaskCreateTool, TaskGetTool, TaskUpdateTool, TaskListTool]
 220|       : []),
 221|     ...(OverflowTestTool ? [OverflowTestTool] : []),
 222|     ...(CtxInspectTool ? [CtxInspectTool] : []),
 223|     ...(TerminalCaptureTool ? [TerminalCaptureTool] : []),
 224|     ...(isEnvTruthy(process.env.ENABLE_LSP_TOOL) ? [LSPTool] : []),
 225|     ...(isWorktreeModeEnabled() ? [EnterWorktreeTool, ExitWorktreeTool] : []),
 226|     getSendMessageTool(),
 227|     ...(ListPeersTool ? [ListPeersTool] : []),
 228|     ...(isAgentSwarmsEnabled()
 229|       ? [getTeamCreateTool(), getTeamDeleteTool()]
 230|       : []),
 231|     ...(VerifyPlanExecutionTool ? [VerifyPlanExecutionTool] : []),
 232|     ...(process.env.USER_TYPE === 'ant' && REPLTool ? [REPLTool] : []),
 233|     ...(WorkflowTool ? [WorkflowTool] : []),
 234|     ...(SleepTool ? [SleepTool] : []),
 235|     ...cronTools,
 236|     ...(RemoteTriggerTool ? [RemoteTriggerTool] : []),
 237|     ...(MonitorTool ? [MonitorTool] : []),
 238|     BriefTool,
 239|     ...(SendUserFileTool ? [SendUserFileTool] : []),
 240|     ...(PushNotificationTool ? [PushNotificationTool] : []),
 241|     ...(SubscribePRTool ? [SubscribePRTool] : []),
 242|     ...(getPowerShellTool() ? [getPowerShellTool()] : []),
 243|     ...(SnipTool ? [SnipTool] : []),
 244|     ...(process.env.NODE_ENV === 'test' ? [TestingPermissionTool] : []),
 245|     ListMcpResourcesTool,
 246|     ReadMcpResourceTool,
 247|     // Include ToolSearchTool when tool search might be enabled (optimistic check)
 248|     // The actual decision to defer tools happens at request time in claude.ts
 249|     ...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []),
 250|   ]
 251| }

getTools:运行时过滤

getTools(permissionContext) 在 getAllBaseTools 基础上应用运行时过滤:

  1. CLAUDE_CODE_SIMPLE — 仅 Bash、Read、Edit(coordinator 模式额外 Agent/TaskStop)
  2. specialTools 排除 — ListMcpResources、ReadMcpResource、SyntheticOutput
  3. filterToolsByDenyRules — 整工具 deny(含 MCP server 前缀规则)
  4. REPL 模式 — REPL 启用时隐藏 REPL_ONLY_TOOLS 原始 primitive
  5. isEnabled() — 各工具自定义开关

与 getAllBaseTools 的区别:后者用于 preset 列表与 system cache 键;前者用于实际 API 请求与 Executor。getToolsForDefaultPreset 进一步 map isEnabled 得到默认 preset 工具名列表。

源码引用: src/tools.ts · 第 271–327 行(共 390 行)

 271| export const getTools = (permissionContext: ToolPermissionContext): Tools => {
 272|   // Simple mode: only Bash, Read, and Edit tools
 273|   if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
 274|     // --bare + REPL mode: REPL wraps Bash/Read/Edit/etc inside the VM, so
 275|     // return REPL instead of the raw primitives. Matches the non-bare path
 276|     // below which also hides REPL_ONLY_TOOLS when REPL is enabled.
 277|     if (isReplModeEnabled() && REPLTool) {
 278|       const replSimple: Tool[] = [REPLTool]
 279|       if (
 280|         feature('COORDINATOR_MODE') &&
 281|         coordinatorModeModule?.isCoordinatorMode()
 282|       ) {
 283|         replSimple.push(TaskStopTool, getSendMessageTool())
 284|       }
 285|       return filterToolsByDenyRules(replSimple, permissionContext)
 286|     }
 287|     const simpleTools: Tool[] = [BashTool, FileReadTool, FileEditTool]
 288|     // When coordinator mode is also active, include AgentTool and TaskStopTool
 289|     // so the coordinator gets Task+TaskStop (via useMergedTools filtering) and
 290|     // workers get Bash/Read/Edit (via filterToolsForAgent filtering).
 291|     if (
 292|       feature('COORDINATOR_MODE') &&
 293|       coordinatorModeModule?.isCoordinatorMode()
 294|     ) {
 295|       simpleTools.push(AgentTool, TaskStopTool, getSendMessageTool())
 296|     }
 297|     return filterToolsByDenyRules(simpleTools, permissionContext)
 298|   }
 299| 
 300|   // Get all base tools and filter out special tools that get added conditionally
 301|   const specialTools = new Set([
 302|     ListMcpResourcesTool.name,
 303|     ReadMcpResourceTool.name,
 304|     SYNTHETIC_OUTPUT_TOOL_NAME,
 305|   ])
 306| 
 307|   const tools = getAllBaseTools().filter(tool => !specialTools.has(tool.name))
 308| 
 309|   // Filter out tools that are denied by the deny rules
 310|   let allowedTools = filterToolsByDenyRules(tools, permissionContext)
 311| 
 312|   // When REPL mode is enabled, hide primitive tools from direct use.
 313|   // They're still accessible inside REPL via the VM context.
 314|   if (isReplModeEnabled()) {
 315|     const replEnabled = allowedTools.some(tool =>
 316|       toolMatchesName(tool, REPL_TOOL_NAME),
 317|     )
 318|     if (replEnabled) {
 319|       allowedTools = allowedTools.filter(
 320|         tool => !REPL_ONLY_TOOLS.has(tool.name),
 321|       )
 322|     }
 323|   }
 324| 
 325|   const isEnabled = allowedTools.map(_ => _.isEnabled())
 326|   return allowedTools.filter((_, i) => isEnabled[i])
 327| }

源码引用: src/tools.ts · 第 262–269 行(共 390 行)

 262| export function filterToolsByDenyRules<
 263|   T extends {
 264|     name: string
 265|     mcpInfo?: { serverName: string; toolName: string }
 266|   },
 267| >(tools: readonly T[], permissionContext: ToolPermissionContext): T[] {
 268|   return tools.filter(tool => !getDenyRuleForTool(permissionContext, tool))
 269| }

assembleToolPool 与 getMergedTools

assembleToolPool 是 REPL(useMergedTools)与 runAgent(coordinator worker)的统一工具池组装:

  1. builtInTools = getTools(permissionContext)
  2. allowedMcpTools = filterToolsByDenyRules(mcpTools, ...)
  3. 分区排序:built-in 按 name 排序后 concat MCP 排序结果
  4. uniqBy name,built-in 优先

为何分区排序: server 的 claude_code_system_cache_policy 在最后一个 matched built-in 后设 cache breakpoint;flat sort 会把 MCP 插入 built-in 中间,破坏 cache 键。

getMergedTools 简单 concat 不去重,用于 token 计数等需要「看见全部 MCP」的场景。

源码引用: src/tools.ts · 第 345–389 行(共 390 行)

 345| export function assembleToolPool(
 346|   permissionContext: ToolPermissionContext,
 347|   mcpTools: Tools,
 348| ): Tools {
 349|   const builtInTools = getTools(permissionContext)
 350| 
 351|   // Filter out MCP tools that are in the deny list
 352|   const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)
 353| 
 354|   // Sort each partition for prompt-cache stability, keeping built-ins as a
 355|   // contiguous prefix. The server's claude_code_system_cache_policy places a
 356|   // global cache breakpoint after the last prefix-matched built-in tool; a flat
 357|   // sort would interleave MCP tools into built-ins and invalidate all downstream
 358|   // cache keys whenever an MCP tool sorts between existing built-ins. uniqBy
 359|   // preserves insertion order, so built-ins win on name conflict.
 360|   // Avoid Array.toSorted (Node 20+) — we support Node 18. builtInTools is
 361|   // readonly so copy-then-sort; allowedMcpTools is a fresh .filter() result.
 362|   const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)
 363|   return uniqBy(
 364|     [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
 365|     'name',
 366|   )
 367| }
 368| 
 369| /**
 370|  * Get all tools including both built-in tools and MCP tools.
 371|  *
 372|  * This is the preferred function when you need the complete tools list for:
 373|  * - Tool search threshold calculations (isToolSearchEnabled)
 374|  * - Token counting that includes MCP tools
 375|  * - Any context where MCP tools should be considered
 376|  *
 377|  * Use getTools() only when you specifically need just built-in tools.
 378|  *
 379|  * @param permissionContext - Permission context for filtering built-in tools
 380|  * @param mcpTools - MCP tools from appState.mcp.tools
 381|  * @returns Combined array of built-in and MCP tools
 382|  */
 383| export function getMergedTools(
 384|   permissionContext: ToolPermissionContext,
 385|   mcpTools: Tools,
 386| ): Tools {
 387|   const builtInTools = getTools(permissionContext)
 388|   return [...builtInTools, ...mcpTools]
 389| }

源码目录与关联文件

强关联:services/tools/toolExecution.ts(runToolUse 编排)、hooks/useCanUseTool.tsx(CanUseToolFn)、constants/tools.ts(Agent 禁用工具列表)。点击 Tool.ts 跳回本章源码块。

动手练习

  1. 在 getAllBaseTools 中搜索 feature(,列出当前构建会启用的实验工具
  2. 设置 CLAUDE_CODE_SIMPLE=1,对比 getTools 返回值长度
  3. 阅读 buildTool 源码,写出新增 Tool 时必须实现的最低方法集
  4. 追踪 assembleToolPool 的调用方(grep),确认 REPL 与 Agent 使用同一入口
  5. 对照 Tool 接口上的 backfillObservableInput 注释,理解 prompt cache 与 observable input 的关系

本章小结与延伸

Tool.ts + tools.ts = 插件契约 + 注册表。具体实现见 bash-tool、agent-tool;执行调度见 streaming-executor。 继续学习:

  • bash-tool
  • streaming-executor
Prev
模块: tools
Next
bash-tool · Shell 执行与权限