跳转至

阶段 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.js 的 buildTool + ToolUseContext 工具基类
utils/diff.js 的 countLinesChanged 计算 diff 行数
utils/file.js 的读/写文件原语 实际 IO
utils/fileHistory.js 文件版本历史(撤销/回看)
utils/gitDiff.js 的 fetchSingleFileGitDiff 取 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 是否走沙箱

关键洞察 1:BashTool 的复杂度是 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:307 的 ToolProgress<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 流式响应 → 工具调用 → 结果回传 → 下一轮"做成死循环的。