← Claude Code 源码学习
03 · 核心系统 · v0.3

工具系统详解

LLM 能推理、能写代码,但它没有手——不能读文件、不能跑命令、不能搜索代码库。要让它真正干活,必须给它一套"工具箱"。本文沿着一个工具的生命周期,按定义、注册、执行、编排四阶段展开源码实现,并附上分类的横切维度。

SCOPEsrc/Tool.ts · src/tools.ts · src/tools/ · src/services/tools/ · src/constants/tools.ts · src/constants/toolLimits.ts
章节目录
  1. 从第一性原理思考
  2. 定义:工具长什么样
  3. 注册:有哪些工具、怎么加载
  4. 执行:从调用到返回的完整链路
  5. 编排:多工具怎么并发
  6. 分类:工具的四种分类维度
  7. 关键源码速查

零、从第一性原理思考:工具系统该怎么设计?

本节是基于源码的设计推导,不是源码本身的直接描述。每个问题先推理"应该怎样",再用源码印证"实际怎样"。

0.1 起点:LLM 光靠嘴说不够

LLM 能推理、能写代码,但它没有手——不能读文件、不能跑命令、不能搜索代码库。要让它真正干活,必须给它一套"工具箱",每个工具对应一个能力:读文件、写文件、执行 Shell、搜索……

那该怎么设计这个工具箱?沿着一个工具从定义到执行的生命周期,自然会遇到四个问题:

0.2 问题链:定义 → 注册 → 执行 → 编排

1. 一个工具长什么样?(定义)

工具需要一个统一的接口:名字叫什么、接受什么输入、输出什么、能不能并发执行。

源码印证:Tool.ts 定义了通用的 Tool<Input, Output, P> 泛型接口,所有工具通过 buildTool() 工厂函数构建,自动填充默认值。

2. 有哪些工具、怎么加载?(注册)

不是所有工具都应该始终可用。有些是所有人都能用的(读文件),有些只在特定条件下开放(PowerShell 只在 Windows),有些是第三方扩展(MCP 工具)。

源码印证:tools.tsgetAllBaseTools()require() + feature flag 实现条件加载,assembleToolPool() 把内置工具和 MCP 工具合并成统一工具池。

3. 从调用到返回经历了什么?(执行)

工具不是"直接调用就完事"——调用前要验证输入、检查权限、跑前置钩子;调用后要跑后置钩子、序列化结果、处理错误。

源码印证:services/tools/toolExecution.tsrunToolUse() 实现了完整的五步链路:验证 → 权限 → 钩子 → call → 结果。

4. 多个工具怎么同时跑?(编排)

模型一次可能调用多个工具(比如同时读 3 个文件)。哪些能并发、哪些必须串行、执行到一半出错了怎么办?

源码印证:services/tools/toolOrchestration.tsisConcurrencySafe() 分批,安全的并发跑、不安全的串行跑。StreamingToolExecutor.ts 实现了边收边跑的流式执行。

接下来四章(一~四),按这四个阶段逐一展开源码实现。第五章"分类"是贯穿前四章的横切维度,从安全属性、Agent 权限、延迟加载、权限模式四个角度对工具进行分类。

一、定义:一个工具长什么样?

1.1 Tool 接口

所有工具的统一接口定义在 Tool.ts:362,核心方法和属性:

方法/属性类型说明
namestring工具名称(如 'Bash''Read'
call()async执行工具,返回 ToolResult<Output>
description()async动态生成工具描述(注入系统提示)
inputSchemaZod Schema输入参数验证(Zod 类型安全)
isEnabled()boolean工具是否启用
isConcurrencySafe()boolean是否可以和其他工具并发执行
isReadOnly()boolean是否只读(不修改文件系统)
isDestructive()boolean是否破坏性(删除、覆盖、发送等)
checkPermissions()async权限检查
validateInput()async自定义输入验证(Zod 之外的业务逻辑)
maxResultSizeCharsnumber结果最大字符数
shouldDeferboolean是否延迟加载(工具数量多时按需加载)
searchHintstringToolSearch 关键词匹配提示

渲染方法(控制终端 UI 展示):renderToolUseMessage()renderToolResultMessage()renderToolUseProgressMessage()renderToolUseRejectedMessage() 等。

1.2 ToolResult:工具返回什么

// Tool.ts:321
type ToolResult<T> = {
  data: T                    // 实际输出数据
  newMessages?: Message[]    // 工具可选生成新消息(如附加上下文)
  contextModifier?: (ctx: ToolUseContext) => ToolUseContext  // 修改上下文(非并发安全工具用)
  mcpMeta?: { ... }          // MCP 协议元数据
}

关键设计:contextModifier 只有非并发安全的工具才能用——因为并发执行时多个 modifier 同时改上下文会冲突,编排层(第四章)会在批次结束后统一应用。

1.3 buildTool:工厂函数和默认值

所有工具通过 buildTool(def) 构建(Tool.ts:757),它的核心作用是填充安全默认值

方法默认值设计意图
isEnabledtrue默认启用
isConcurrencySafefalse默认不安全——保守策略,必须显式声明安全
isReadOnlyfalse默认当作写操作
isDestructivefalse默认非破坏性
checkPermissions返回 allow交给通用权限系统处理

这是一个 fail-closed 设计:忘了声明 isConcurrencySafe 的工具会被当作不安全的串行执行,不会意外并发出问题。

1.4 ToolUseContext:执行上下文

工具执行时收到的上下文(Tool.ts:158),包含它需要知道的一切:

二、注册:有哪些工具、怎么加载?

2.1 工具注册表:getAllBaseTools()

tools.ts:193 是所有工具的注册源头。工具按加载方式分为三类:

始终可用的工具(18 个)

直接 import,无条件加载。完整清单及只读/可并发属性见 2.5 工具清单与属性总览

条件加载的工具

通过 feature() flag、环境变量或函数判断:

条件工具说明
isTodoV2Enabled()TaskCreate/Get/Update/ListToolTodo V2 系统
isWorktreeModeEnabled()EnterWorktreeTool、ExitWorktreeToolGit worktree 隔离
isToolSearchEnabledOptimistic()ToolSearchTool工具多时按需搜索
isPowerShellToolEnabled()PowerShellToolWindows PowerShell
isEnvTruthy('ENABLE_LSP_TOOL')LSPTool语言服务器协议
USER_TYPE === 'ant'ConfigTool、TungstenTool、REPLToolAnthropic 内部专用

Feature Flag 控制的工具🚧 全部未启用

以下工具因 feature() 返回 false 而不会加载。
Feature Flag工具
WEB_BROWSER_TOOLWebBrowserTool
PROACTIVE / KAIROSSleepTool
AGENT_TRIGGERSCronCreate/Delete/ListTool
AGENT_TRIGGERS_REMOTERemoteTriggerTool
MONITOR_TOOLMonitorTool
KAIROSSendUserFileTool
KAIROS / KAIROS_PUSH_NOTIFICATIONPushNotificationTool
KAIROS_GITHUB_WEBHOOKSSubscribePRTool
WORKFLOW_SCRIPTSWorkflowTool
UDS_INBOXListPeersTool
HISTORY_SNIPSnipTool
CONTEXT_COLLAPSECtxInspectTool
TERMINAL_PANELTerminalCaptureTool
OVERFLOW_TEST_TOOLOverflowTestTool

2.2 条件加载的实现:动态 require()

工具注册使用 require() 而非 import,这是有意的设计(tools.ts 文件头部):

// feature flag 控制的工具用 require(),flag 为 false 时整个模块不会被加载
const SleepTool = feature('PROACTIVE') || feature('KAIROS')
  ? require('./tools/SleepTool/SleepTool.js').default
  : null

好处:flag 关闭时不加载模块代码,实现死代码消除。如果用 import,即使 flag 为 false 模块也会被打包进去。

2.3 工具池合并:assembleToolPool()

内置工具和 MCP 工具最终通过 assembleToolPool()tools.ts:345)合并为统一的工具池:

getTools(permissionContext)          → 内置工具(过滤后)
         +
filterToolsByDenyRules(mcpTools)     → MCP 工具(过滤后)
         │
         ▼
    分别排序,然后连接(内置在前,MCP 在后)
    uniqBy 去重(内置优先级高于同名 MCP 工具)
         │
         ▼
    最终工具池(供系统提示注入和工具调用使用)

排序是为了 prompt cache 稳定性——工具列表顺序不变,系统提示的工具描述不变,API 请求前缀字节一致,缓存命中率更高。内置和 MCP 分开排序再连接,保证内置工具的连续前缀不被 MCP 工具打断。

2.4 工具过滤:getTools()

getTools()tools.ts:271)在 getAllBaseTools() 基础上做三层过滤:

getAllBaseTools()
    │
    ├─ 1. filterToolsByDenyRules() — 按拒绝规则(deny rules)过滤
    │     支持 MCP 前缀规则(如 mcp__server 会过滤该服务器的所有工具)
    │
    ├─ 2. REPL 模式过滤 — 隐藏原始工具(通过 REPL 虚拟机间接访问)
    │
    └─ 3. isEnabled() 过滤 — 每个工具的自检(动态禁用)

还有一个特殊模式:CLAUDE_CODE_SIMPLE=true 时只保留 [BashTool, FileReadTool, FileEditTool],极简模式。

2.5 工具清单与属性总览

以下按功能域整理 getAllBaseTools() 中始终加载的工具:

功能域工具作用只读可并发
Shell 执行Bash执行 Shell 命令条件¹条件¹
文件操作FileRead读取文件/图片/PDF/笔记本
FileEdit精确字符串替换编辑文件条件
FileWrite创建或覆盖写入文件条件
NotebookEdit编辑 Jupyter Notebook 单元格条件
搜索Glob按 glob 模式匹配文件路径
Grep基于 ripgrep 的正则内容搜索
网络WebFetch抓取 URL 页面内容
WebSearch搜索引擎查询
AgentAgent派出子 Agent 执行任务条件
SendMessageAgent 间邮箱通信条件
任务管理TaskCreate/Update创建/更新任务
TaskGet/List查询/列出任务
TaskOutput读取后台任务输出
TaskStop停止正在运行的后台任务
TodoWriteTodo V1 待办管理条件
计划模式EnterPlanMode进入计划模式(只思考不执行)
ExitPlanModeV2退出计划模式恢复执行条件
交互AskUserQuestion向用户提问(多选)条件
Skill调用 Skill(/commit 等)条件
Brief发送带附件的消息条件
MCPListMcpResources列出 MCP 服务器资源
ReadMcpResource按 URI 读取 MCP 资源
工具发现ToolSearch搜索延迟加载的工具

只读:工具是否不修改文件系统。✅ = 纯读取操作,不需要用户确认权限;❌ = 有写入/修改行为,默认权限模式下需用户批准。

可并发:模型一次调用多个工具时,该工具能否与其他工具同时执行。✅ = 可以并行(如同时读 3 个文件);❌ = 必须串行排队。编排层按此属性将工具调用分批:连续的可并发工具合为一批同时跑,遇到不可并发的就断开新建一批。

条件:取决于输入参数。如 BashTool 会分析命令内容——ls 判定为只读可并发,rm 则非只读须串行。

¹ BashTool 通过 checkReadOnlyConstraints() 动态分析命令内容判定安全性。

条件加载和 Feature Flag 控制的工具详见 2.1 工具注册表:getAllBaseTools()

三、执行:从调用到返回的完整链路

3.1 五步执行链路

一个工具从模型调用到结果返回,在 toolExecution.ts 中经历五步(runToolUse()):

模型输出 tool_use 块(名称 + 输入参数)
         │
    ┌─── 1. 输入验证 ─────────────────────────────────┐
    │  inputSchema.safeParse(input)  — Zod 类型校验      │
    │  validateInput(input)          — 自定义业务校验      │
    │  失败 → 返回 InputValidationError                   │
    └─────────────────────────────────────────────────────┘
         │
    ┌─── 2. 权限检查 ─────────────────────────────────┐
    │  PreToolUse 钩子 → 规则系统 → 用户交互             │
    │  拒绝 → 返回权限拒绝消息                            │
    │  (权限系统详解见后续独立文档)                       │
    └─────────────────────────────────────────────────────┘
         │
    ┌─── 3. 工具调用 ─────────────────────────────────┐
    │  tool.call(input, context, canUseTool, ...)       │
    │  支持进度回调(onToolProgress)                      │
    └─────────────────────────────────────────────────────┘
         │
    ┌─── 4. 后置钩子 ─────────────────────────────────┐
    │  PostToolUse 钩子                                  │
    │  可修改输出(仅 MCP 工具)、阻止继续执行              │
    └─────────────────────────────────────────────────────┘
         │
    ┌─── 5. 结果序列化 ──────────────────────────────┐
    │  mapToolResultToToolResultBlockParam()             │
    │  截断超长结果(DEFAULT_MAX_RESULT_SIZE_CHARS=50000)│
    └─────────────────────────────────────────────────────┘

3.2 钩子系统:前置和后置

钩子(Hooks)是用户可配置的 Shell 命令,在工具执行前后自动运行(toolHooks.ts)。

PreToolUse 钩子能做 5 件事

能力说明
修改输入返回 updatedInput,替换原始输入
做权限决策返回 allow / deny / ask,影响权限流程
阻止执行设置 preventContinuation,工具不执行
提供上下文返回 additionalContexts,附加到对话中
取消自身钩子执行超时或被中止

PostToolUse 钩子能做 3 件事

能力说明
修改输出仅 MCP 工具允许(updatedMCPToolOutput
阻止继续设置 preventContinuation,模型不再生成后续消息
提供上下文返回 additionalContexts

钩子与权限的优先级(resolveHookPermissionDecision()

钩子说 allow + 规则说 deny → 规则胜(规则不可被钩子绕过)
钩子说 deny              → 立即拒绝(规则不再检查)
钩子说 ask               → 弹出用户交互对话
无钩子决策               → 走正常权限流程

3.3 错误分类

classifyToolError()toolExecution.ts:150)按优先级分类错误,用于遥测:

优先级错误类型分类方式
1TelemetrySafeError取错误的 telemetryMessage(已验证安全)
2文件系统错误errno 代码(ENOENTEACCES 等)
3命名错误error.name(如 ShellError
4兜底'Error''UnknownError'

3.4 结果大小限制

工具结果有三层截断保护(constants/toolLimits.ts):

限制说明
DEFAULT_MAX_RESULT_SIZE_CHARS50,000单个工具结果最大字符数
MAX_TOOL_RESULT_TOKENS100,000单个结果的 token 限制
MAX_TOOL_RESULTS_PER_MESSAGE_CHARS200,000一条消息中所有工具结果的聚合限制

四、编排:多工具怎么并发?

4.1 分批策略:partitionToolCalls()

模型一次可能调用多个工具。toolOrchestration.ts:91partitionToolCalls() 把工具调用序列分成批次,每批要么全并发、要么全串行:

工具1(Read—安全)   ─┐
工具2(Grep—安全)   ─┤→ 批 A:并发执行
工具3(Read—安全)   ─┘
工具4(Bash—不安全)  ──→ 批 B:串行执行
工具5(Read—安全)    ──→ 批 C:并发执行(不能和批 A 合并,因为中间断了)

规则很简单:连续的并发安全工具合并为一批,遇到不安全的就断开新建一批

判断依据是每个工具的 isConcurrencySafe(input) 方法——注意它接收 input 参数,所以同一个工具可以根据不同输入返回不同的安全性判断(比如 Bash 读取命令安全、写入命令不安全)。

4.2 执行模式:并发 vs 串行

runTools()toolOrchestration.ts:19)按批次依次执行:

并发批次

串行批次

4.3 流式执行:StreamingToolExecutor

StreamingToolExecutor.ts 是更激进的优化——不等模型输出完所有 tool_use 块,边收到边开始执行

模型输出流:[tool_use_1] [tool_use_2 ...还在生成...] [tool_use_3]
               │              │
               ▼              ▼
          立即开始执行      收到后立即开始

核心判断逻辑(canExecuteTool()):

// 只要所有正在执行的工具都是并发安全的,新工具就能立即开始
executingTools.length === 0 ||
(isConcurrencySafe && executingTools.every(t => t.isConcurrencySafe))

错误级联机制:如果 Bash 工具执行出错,会通过 siblingAbortController 中止同批次其他工具——避免命令链失败后继续浪费资源。

结果有序输出:虽然执行是乱序的,但输出严格按工具调用顺序。每个工具有状态机 queued → executing → completed → yieldedgetCompletedResults() 按序遍历,遇到未完成的就停下等。

五、分类:工具的四种分类维度

源码中没有 toolCategoryToolGroup 字段——不存在一个显式的"分类枚举"。但存在四种隐式分类维度

5.1 维度一:安全属性分类(Tool.ts 接口属性)

每个工具通过 4 个布尔属性声明自己的安全特征:

                    isReadOnly?
                   ╱           ╲
                 是              否
              (纯读取)        (有写入)
               │                │
        isConcurrencySafe?   isDestructive?
           ╱      ╲            ╱        ╲
          是       否          是         否
       (可并发) (须串行)   (不可逆:     (可逆:
                           删除/覆盖)    普通写入)

这四个属性均采用 fail-closed 默认值(默认值和设计意图详见 1.3 buildTool:工厂函数和默认值)——忘了声明 isConcurrencySafe 的工具会被当作不安全的串行执行,宁可慢,不能并发出错。

5.2 维度二:Agent 使用权限分类(constants/tools.ts)

这是最接近"等级分类"的设计——按工具在不同 Agent 角色中的可用性分组:

┌─────────────────────────────────────────────────────────┐
│  所有工具池(getAllBaseTools)                            │
│                                                         │
│  ┌──────────────────────────────────────────────────┐   │
│  │  主 Claude 可用:全部工具                          │   │
│  │                                                  │   │
│  │  ┌─────────────────────────────────────────────┐ │   │
│  │  │  子 Agent 可用(排除 DISALLOWED)             │ │   │
│  │  │  排除:AgentTool*、AskUserQuestion、         │ │   │
│  │  │        TaskOutput、TaskStop、                │ │   │
│  │  │        EnterPlanMode、ExitPlanMode           │ │   │
│  │  │                                              │ │   │
│  │  │  ┌──────────────────────────────────────┐    │ │   │
│  │  │  │  异步 Agent 允许(ALLOWED 白名单)      │    │ │   │
│  │  │  │  仅 15 个:Read/Write/Edit/Glob/      │    │ │   │
│  │  │  │  Grep/Bash/WebSearch/WebFetch/       │    │ │   │
│  │  │  │  Notebook/Skill/Todo/ToolSearch/     │    │ │   │
│  │  │  │  Worktree                            │    │ │   │
│  │  │  └──────────────────────────────────────┘    │ │   │
│  │  │                                              │ │   │
│  │  │  ┌──────────────────────────────────────┐    │ │   │
│  │  │  │  协调器模式 🚧 未启用                  │    │ │   │
│  │  │  │ (COORDINATOR 白名单)                 │    │ │   │
│  │  │  │  仅 4 个:Agent/TaskStop/             │    │ │   │
│  │  │  │  SendMessage/SyntheticOutput         │    │ │   │
│  │  │  └──────────────────────────────────────┘    │ │   │
│  │  └─────────────────────────────────────────────┘ │   │
│  └──────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

* AgentTool 仅 ant 用户的子 Agent 可嵌套使用

设计逻辑:越深层的角色,可用工具越少——防止递归、保护主线程状态、避免资源冲突。

对应源码常量(constants/tools.ts):

常量成员数用途
ALL_AGENT_DISALLOWED_TOOLS7-8所有子 Agent 禁用的工具(黑名单)
CUSTOM_AGENT_DISALLOWED_TOOLS同上自定义 Agent 禁用(当前与上相同)
ASYNC_AGENT_ALLOWED_TOOLS15异步 Agent 允许的工具(白名单)
IN_PROCESS_TEAMMATE_ALLOWED_TOOLS5-8进程内队友额外允许的工具(Task 系列 + SendMessage)
COORDINATOR_MODE_ALLOWED_TOOLS4协调器模式仅允许的工具🚧 未启用
REPL_ONLY_TOOLS8REPL 模式下被隐藏的原始工具(通过 REPL 虚拟机间接访问)

5.3 维度三:延迟加载分类(shouldDefer + alwaysLoad)

当工具总数超过阈值时,ToolSearch 机制启用,工具分为两类:

分类判断逻辑对模型的影响
立即加载shouldDefer=falsealwaysLoad=true完整 schema 出现在初始提示中,模型直接可调用
延迟加载shouldDefer=true 且非特殊工具仅名称出现在提示中,需先用 ToolSearch 获取 schema 才能调用

立即加载的核心工具:Bash、FileRead、FileEdit、FileWrite、Glob、Grep、Agent、ToolSearch 本身。

延迟加载的工具(约 24 个):WebFetch、WebSearch、TodoWrite、TaskCreate/Get/Update/List、SendMessage、EnterWorktree、EnterPlanMode、AskUserQuestion、NotebookEdit、ListMcpResources、ReadMcpResource 等。

判断优先级(ToolSearchTool/prompt.ts:62isDeferredTool()):

alwaysLoad=true → 永不延迟
MCP 工具      → 总是延迟
特殊工具       → 永不延迟(ToolSearch、Agent、Brief 等)
shouldDefer   → 按标记延迟

5.4 维度四:权限模式分类(types/permissions.ts)

这不是工具本身的分类,而是运行环境的分类——不同模式下工具的权限检查行为不同:

模式说明
default默认模式,写操作需要用户确认
plan计划模式,只允许只读工具,写工具被阻止
acceptEdits自动接受文件编辑,其他写操作仍需确认
bypassPermissions跳过所有权限检查(--dangerously-skip-permissions
dontAsk不询问用户,被拒绝的操作直接跳过
auto🚧 未启用(需要 TRANSCRIPT_CLASSIFIER)

六、关键源码速查

阶段文件核心职责
定义Tool.ts工具接口、buildTool 工厂函数、ToolUseContext
定义constants/toolLimits.ts结果大小限制常数
注册tools.ts工具注册表、条件加载、工具池合并
注册constants/tools.ts各场景工具白名单/黑名单
执行services/tools/toolExecution.ts单工具五步执行链路
执行services/tools/toolHooks.ts前置/后置钩子执行
编排services/tools/toolOrchestration.ts多工具分批、并发/串行编排
编排services/tools/StreamingToolExecutor.ts流式边收边执行
分类types/permissions.ts权限模式定义(default/plan/acceptEdits 等)
分类tools/ToolSearchTool/prompt.ts延迟加载判断逻辑(isDeferredTool()