跳转至

阶段 5 | 工具调用系统

目标:理解 Claude Code 的"工具"抽象 —— LLM 怎么"调用工具"、前端怎么渲染、用户怎么授权、结果怎么回到对话流。 时长:1~2 天 前端类比:表单 + 异步 action + 权限 modal —— 把"用户提交 → 异步执行 → 结果回填"完整循环做成可视化系统。


5.1 全景:43 个工具

src/tools/                            共 43 个工具子目录
├── AgentTool/                        启动子 agent
├── AskUserQuestionTool/              多选题询问
├── BashTool/                         跑 shell 命令(16 个文件)
├── BriefTool/                        简短摘要模式
├── ConfigTool/                       改运行时配置
├── EnterPlanModeTool / EnterWorktreeTool
├── ExitPlanModeTool / ExitWorktreeTool
├── FileEditTool/                     编辑文件(7 个文件)
├── FileReadTool / FileWriteTool
├── GlobTool / GrepTool               文件查找 + 内容搜索
├── LSPTool                           LSP 集成
├── ListMcpResourcesTool / McpAuthTool / MCPTool / ReadMcpResourceTool  (MCP)
├── NotebookEditTool                  Jupyter 笔记本
├── PowerShellTool                    Windows 专用
├── RemoteTriggerTool                 远程触发(Ant-only)
├── REPLTool                          REPL 内部工具(Ant-only)
├── ScheduleCronTool/                 定时任务(3 个:CronCreate/Delete/List)
├── SendMessageTool                   swarm 队友消息
├── SkillTool                         加载 skill
├── SleepTool                         PROACTIVE/KAIROS 模式
├── SyntheticOutputTool               合成输出
├── TaskCreateTool / TaskGetTool / TaskListTool / TaskOutputTool / TaskStopTool / TaskUpdateTool
├── TeamCreateTool / TeamDeleteTool   swarm 队伍管理
├── TodoWriteTool                     todo 列表
├── ToolSearchTool                    工具检索
├── WebFetchTool / WebSearchTool      网络
├── shared/                           工具间共享代码
├── testing/                          测试用
└── utils.ts                          通用工具

每个工具都是独立子目录,结构高度一致

<BashTool>/
├── BashTool.tsx           工具实现(核心)
├── UI.tsx                 渲染组件(用户看到的)
├── prompt.ts              工具描述(喂给 LLM 的)
├── utils.ts               工具私有工具函数
├── <special>             (如 bashSecurity.ts、pathValidation.ts...)
└── 常量 / types / 等

5.2 核心抽象:src/Tool.ts

792 行 —— 整个工具系统的基础 导出:14 个类型 + 4 个工具函数 + 1 个工厂函数

5.2.1 关键类型

// src/Tool.ts:362
export type Tool<Name extends string = string, Input = any> = {
  name: Name
  description: string           // 给 LLM 看的工具描述
  inputSchema: AnyObject        // zod schema,校验输入
  // ... 渲染、执行、权限等字段
}

// src/Tool.ts:701
export type Tools = readonly Tool[]

// src/Tool.ts:158
export type ToolUseContext = {
  // 工具执行时的上下文(abortController、state、permissions...)
}

// src/Tool.ts:123
export type ToolPermissionContext = DeepImmutable<{
  // 当前权限规则(allow/deny/ask)
}>

// src/Tool.ts:321
export type ToolResult<T> = {
  content: T
  is_error?: boolean
}

// src/Tool.ts:95
export type ValidationResult = {
  result: true | { errorMessage: string }
}

5.2.2 工厂函数 buildTool

// src/Tool.ts:783
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
  // ToolDef → BuiltTool 转换器
  // 补全默认值、注入 ctx 类型、绑定元数据
}

前端类比:和 React 的 forwardRef / memo 是同种"工厂 + 增强"模式。

5.2.3 工具查询

// src/Tool.ts:348
export function toolMatchesName(tool: Tool, name: string): boolean
export function findToolByName(tools: Tools, name: string): Tool | undefined

前端类比:Redux 的 selectTodoById、React Router 的 matchPath

5.3 工具注册表:src/tools.ts

389 行 —— 43 个工具的"中央集线器"

5.3.1 注册模式

// src/tools.ts 头部
import { AgentTool } from './tools/AgentTool/AgentTool.js'
import { SkillTool } from './tools/SkillTool/SkillTool.js'
import { BashTool } from './tools/BashTool/BashTool.js'
import { FileEditTool } from './tools/FileEditTool/FileEditTool.js'
// ... 43 个静态 import

// DCE 控制
const REPLTool = process.env.USER_TYPE === 'ant'
  ? require('./tools/REPLTool/REPLTool.js').REPLTool
  : null
const SleepTool = feature('PROACTIVE') || feature('KAIROS')
  ? require('./tools/SleepTool/SleepTool.js').SleepTool
  : null
const cronTools = feature('AGENT_TRIGGERS')
  ? [CronCreateTool, CronDeleteTool, CronListTool]
  : []

5.3.2 预设(preset)

// src/tools.ts:161
export const TOOL_PRESETS = ['default'] as const
export type ToolPreset = (typeof TOOL_PRESETS)[number]

export function parseToolPreset(preset: string): ToolPreset | null { ... }
export function getToolsForDefaultPreset(): string[] { ... }
export function getAllBaseTools(): Tools { ... }

关键洞察用户可以选择预设'default'),未来可能加 'minimal''web-only' 等。这是 Web 项目的"feature flag"思想在工具粒度的应用

5.3.3 权限过滤

// src/tools.ts:262
export function filterToolsByDenyRules<T extends Tools>(tools: T, rules: ...): T

// src/tools.ts:271
export const getTools = (permissionContext: ToolPermissionContext): Tools => {
  // 1. 拿所有基础工具
  // 2. 跑权限 deny rules 过滤
  // 3. 按用户/项目配置加/减
  // 4. 返回 readonly Tools
}

前端类比:和 React Router 的"基于权限的路由表"是同种模式。

5.4 工具全生命周期

LLM 返回 tool_use block
query.ts 解析 tool_use,findToolByName(tools, name)
ToolUseContext 构造(abortController / getAppState / ...)
Permission check: shouldRunInSandbox? / needsUserApproval?
[需要用户批准] → PermissionRequest UI → 用户点 Yes/No
[拒绝] → 构造 denial ToolResult,注入回 messages
[批准] → 继续
tool.call(input, ctx) → 异步执行
执行中可能发出 ToolProgress("BashTool 正在跑 5 秒")
执行完毕 → ToolResult
[如果需要] UI 渲染 tool_result(如 FileEditToolResultMessage)
toolResult 注入回 messages,进入下一轮 LLM 调用

5.5 深读:FileEditTool/

完整路径src/tools/FileEditTool/ 7 个文件constants.ts, FileEditTool.ts, prompt.ts, types.ts, UI.tsx, utils.ts

5.5.1 目录结构 = 完整工具定义

constants.ts      工具相关的常量(默认行数限制、文件后缀白名单...)
FileEditTool.ts   工具实现(execute / validate / 渲染)
prompt.ts         喂给 LLM 的工具描述("Use this tool to edit files...")
types.ts          工具私有类型
UI.tsx            渲染组件(用户看到的样子 + 用户交互)
utils.ts          工具私有工具函数

💡 这就是"工具"的完整定义:实现 + 描述 + UI + 校验 + 常量。
前端类比:一个 React 表单组件 = index.tsx (UI) + validation.ts + types.ts + constants.ts

5.5.2 FileEditTool.ts 头部揭示的依赖

观察 FileEditTool.ts 头部 import:

导入 作用
analytics/growthbook.js 特性开关(编辑行为是否要 LSP 联动)
services/diagnosticTracking.ts LSP 诊断上报
services/lsp/manager.js LSP 服务器管理
skills/loadSkillsDir.js 编辑后激活 conditional skills
Tool.jsbuildTool + ToolUseContext 工具基类
utils/diff.jscountLinesChanged 计算 diff 行数
utils/file.js 的读/写文件原语 实际 IO
utils/fileHistory.js 文件版本历史(撤销/回看)
utils/gitDiff.jsfetchSingleFileGitDiff 取 git 历史 diff
utils/permissions/filesystem.js 文件写权限检查
utils/permissions/PermissionResult.js 权限决策类型

关键洞察一个"编辑文件"的工具,背后串联了 20+ 业务模块 —— analytics、LSP、skills、diff、权限、文件历史。这是工具系统的"杠杆点" —— 加一个工具就能复用全部基础设施。

5.5.3 UI.tsx —— 用户看到的工具调用

// 推测结构(基于其他 UI 组件模式)
function FileEditToolUI({ input, result, onApprove, onDeny }) {
  return (
    <Box flexDirection="column">
      <FileEditToolDiff input={input} dim={!result} />
      <KeyboardShortcutHint keys="y" label="Approve" />
      <KeyboardShortcutHint keys="n" label="Deny" />
    </Box>
  );
}

前端类比:和 shadcn 的"确认对话框"+"diff 视图"是同种 UX 模式。

5.6 深读:BashTool/

完整路径src/tools/BashTool/ 16 个文件 —— 因为 bash 涉及 安全敏感,所以拆得最细

5.6.1 安全子模块

文件 职责
BashTool.tsx 主实现
UI.tsx 渲染(含命令高亮、警告)
prompt.ts LLM 描述(含安全约束)
bashCommandHelpers.ts 命令解析、tokenize
bashPermissions.ts bash 权限规则(glob 模式)
bashSecurity.ts 安全检查(危险命令检测)
commandSemantics.ts 命令语义分析(cd / export / 副作用)
commentLabel.ts 命令注释提取
destructiveCommandWarning.ts rm -rf / 等破坏性命令警告
modeValidation.ts BashMode 校验
pathValidation.ts 路径校验(防越权)
readOnlyValidation.ts 只读模式校验
sedEditParser.ts sed 命令解析
sedValidation.ts sed 安全性校验
shouldUseSandbox.ts 是否走沙箱

关键洞察 1BashTool 的复杂度是 Claude Code 安全模型的体现
关键洞察 2"安全检查"和"工具实现"分离 —— bashSecurity.ts 是纯函数,好测试、好审计
关键洞察 3沙箱策略shouldUseSandbox.ts)作为独立模块 —— 未来切换到 macOS Seatbelt / Linux bubblewrap 都不影响 BashTool 主实现。

💡 学习价值BashTool 的目录结构 = 一个安全敏感型工具的"完整设计模式"。其他工具(FileEditTool、FileWriteTool)会借鉴这个模式。

5.7 工具权限系统

核心路径src/utils/permissions/

5.7.1 三种模式

模式 行为 适用
default 每次危险操作前询问 默认
acceptEdits 文件编辑类自动通过 编辑文件频繁时
bypassPermissions 全部自动通过 受信任环境
plan 工具不执行,只生成 plan Plan Mode

5.7.2 权限决策流

Tool 想执行
utils/permissions/PermissionResult.ts: matchingRuleForInput
匹配到规则:
  - allow → 直接执行
  - deny → 拒绝,注入 denial ToolResult
  - ask → 弹 PermissionRequest UI
[用户决策] → 更新 DenialTrackingState(避免重复问)
执行

关键文件: - src/utils/permissions/PermissionMode.ts —— 模式定义 - src/utils/permissions/PermissionResult.ts —— 决策类型 - src/utils/permissions/denialTracking.ts —— 拒绝追踪(避免重复问) - src/utils/permissions/shellRuleMatching.ts —— 通配符匹配

5.8 工具进度反馈

类型src/Tool.ts:307ToolProgress<P> 场景:BashTool 跑 30 秒、FileEditTool 改大文件、AgentTool 启动子 agent 跑几分钟

// src/Tool.ts 推测
export type ToolProgress<P extends ToolProgressData> = {
  toolUseID: string
  data: P              // 进度数据
}

// src/Tool.ts 推测
export type ToolProgressData =
  | BashProgress       // "Running for 5s, 3 lines of stdout"
  | MCPProgress        // "MCP server X responding"
  | REPLToolProgress
  | TaskOutputProgress
  | AgentToolProgress
  | WebSearchProgress
  | SkillToolProgress

前端类比:和 WebSocket 的"服务端推送中间状态"是同种模式。

5.9 子 Agent 工具:AgentTool/

特殊工具:它不是"做一件事",而是启动一个完整的子 agent

// src/tools/AgentTool/ 推测导出
export const AgentTool = buildTool({
  name: 'Agent',
  description: 'Launch a new agent...',
  inputSchema: z.object({
    prompt: z.string(),
    subagent_type: z.string(),
  }),
  async *call(input, context) {
    // 启动子进程跑 agent
    yield { type: 'progress', data: { ... } }
    // 收集输出
    yield { type: 'result', data: { ... } }
  }
})

关键洞察Tool 是异步生成器(async *call——yield 多次发送进度,最后 yield result。这是个比 Promise 更强大的模式: - 多次 yield(进度 + 中间结果 + 最终结果) - 调用方可以在任意 yield 点 cancel - 自然支持流式输出

前端类比:和 React Suspense 的 "throw promise" 是同种"lazy evaluation"思路。

5.10 工具与 LLM 通信

完整路径src/services/api/claude.ts + src/services/api/messages.ts

5.10.1 工具描述喂给 LLM

prompt.ts 里的内容会被拼到 system prompt 里:

You have access to the following tools:
- Bash: Run shell commands...
- FileEdit: Edit files by providing old_string + new_string...
- Agent: Launch a subagent...
...

Claude API 用 tools 字段(zod schema)让 LLM 输出 tool_use block。

5.10.2 工具结果回传

// 工具结果转成 Anthropic SDK 格式
const toolResultBlock: ToolResultBlockParam = {
  type: 'tool_result',
  tool_use_id: toolUse.id,
  content: result.content,    // string or array
  is_error: result.is_error,
}

5.11 关键洞察

5.11.1 工具即"业务领域"

43 个工具 = 43 个业务能力。每个工具目录是业务能力的封装:实现 + 描述 + UI + 校验 + 权限。
前端类比:微前端(micro-frontend)每个子应用是个完整业务域。Claude Code 工具就是 TUI 的"微前端"。

5.11.2 安全敏感工具"拆得最细"

BashTool 16 个文件是因为它涉及安全模型bashSecurity.ts / pathValidation.ts / destructiveCommandWarning.ts 各自独立。
这是"高安全代码"的组织范式 —— 把安全相关代码提到顶层模块,方便审计和测试。

5.11.3 工具的"异步生成器"是精髓

async *call() + yield 多次让 Claude Code 能: - 显示进度 - 中途取消 - 流式输出结果 - 区分中间状态 vs 最终结果

比 Promise 强大。React 的 React Server Components 也是同样思想(streaming + 多次 yield)。

5.11.4 prompt.ts 是 LLM 工具描述的事实标准

每个工具独立写自己的 prompt,意味着: - LLM 拿到的工具描述经过专门优化("Use this tool when X, not Y") - 改一个工具的描述不会影响其他 - A/B 测试工具描述 容易("换 prompt,看 LLM 选不选")

5.12 阅读清单

  1. src/Tool.ts(792 行)—— 重点读 buildTool、Tool、ToolUseContext、findToolByName
  2. src/tools.ts(389 行)—— 重点读 getTools、filterToolsByDenyRules、TOOL_PRESETS
  3. src/tools/FileEditTool/FileEditTool.ts(看头部 import + buildTool 入口)—— 工具定义模板
  4. 🔍 src/tools/BashTool/bashSecurity.ts —— 安全检查实现
  5. 🔍 src/tools/BashTool/UI.tsx —— bash 命令的渲染(含危险警告)
  6. 📌 src/tools/AgentTool/AgentTool.tsx(Ant-only?)—— 异步生成器模式
  7. 📌 src/utils/permissions/PermissionResult.ts + shellRuleMatching.ts —— 权限规则
  8. 📌 src/utils/permissions/denialTracking.ts —— 拒绝追踪

5.13 练习任务

  1. 手写 buildTool 的 TypeScript 类型 —— 支持 name / description / inputSchema(zod) / call(input, ctx) / UI / validate 字段
  2. 设计一个新工具 GitCommitTool:调用方式 git commit -m "...",UI 渲染 commit message,要求用户输入 commit message 字符串。画出目录结构
  3. 分析 BashTool 的安全检查链 —— 从 BashTool.tsx 的 call() 入口跟到 bashSecurity.ts 哪些函数被调,画出流程图
  4. 思考:如果让你把"工具结果"做 Web 版(前端调 LLM API 拿 tool_use,本地执行 tool,结果回传),你会用什么抽象?为什么 Claude Code 的 async *call + yield 模式比 Promise 好?

5.14 下一步

进入 阶段 6:Agent 循环 + 流式 API —— query.ts、QueryEngine.ts 是怎么把"LLM 流式响应 → 工具调用 → 结果回传 → 下一轮"做成死循环的。