Skip to content

内置工具详解

概述

本章深入分析几个代表性工具的实现,展示工具系统的设计模式。

BashTool — Shell 命令执行

Source: src/tools/BashTool/BashTool.tsx (46KB)

BashTool 是最复杂的工具之一,支持执行任意 Shell 命令。

输入 Schema

typescript
{
  command: string              // Shell 命令
  cwd?: string                 // 工作目录
  timeout_ms?: number          // 超时(毫秒)
  run_in_background?: boolean  // 后台运行
  dangerouslyDisableSandbox?: boolean  // 禁用沙盒
}

输出 Schema

typescript
{
  exit_code: number
  stdout: string
  stderr: string
  interrupted: boolean
  backgroundTaskId?: string
  persistedOutputPath?: string  // 大输出持久化路径
}

安全检查链

并发安全判断

typescript
isConcurrencySafe(input) {
  // 只有确认为只读的命令才允许并行
  // 例如:grep, cat, ls, git log
  return checkReadOnlyConstraints(input.command).isReadOnly;
}

大输出处理

typescript
if (stdout.length > maxResultSizeChars) {
  const path = await saveToToolResultsDir(stdout);
  return {
    data: { exit_code: exitCode, persistedOutputPath: path, ... }
  };
}

超过 100,000 字符的输出会被保存到临时文件,避免 token 浪费。

FileEditTool — 文件编辑

Source: src/tools/FileEditTool/FileEditTool.ts (20KB)

输入 Schema

typescript
{
  file_path: string       // 文件路径
  old_string: string      // 要替换的文本
  new_string: string      // 替换后的文本
  replace_all?: boolean   // 替换所有出现
}

关键实现

typescript
async call(input, context) {
  // 1. 读取文件内容
  const content = await readFile(input.file_path);
  
  // 2. 查找 old_string
  const index = content.indexOf(input.old_string);
  if (index === -1) {
    throw new Error('old_string not found in file');
  }
  
  // 3. 确保唯一性(除非 replace_all)
  if (!input.replace_all) {
    const secondIndex = content.indexOf(input.old_string, index + 1);
    if (secondIndex !== -1) {
      throw new Error('old_string is not unique, provide more context');
    }
  }
  
  // 4. 执行替换
  const newContent = input.replace_all
    ? content.replaceAll(input.old_string, input.new_string)
    : content.replace(input.old_string, input.new_string);
  
  // 5. 写入文件
  await writeFile(input.file_path, newContent);
  
  return { data: { modified: true, file_size: newContent.length } };
}

路径扩展

typescript
backfillObservableInput(input) {
  // Hook 看到工具调用前,先展开路径
  input.file_path = expandPath(input.file_path);
}

GrepTool — 正则搜索

Source: src/tools/GrepTool/GrepTool.ts (20KB)

输入 Schema

typescript
{
  pattern: string                    // 正则表达式
  path?: string                      // 搜索目录
  glob?: string                      // 文件过滤
  output_mode?: 'content' | 'files_with_matches' | 'count'
  '-B'?: number                      // 上文行数
  '-A'?: number                      // 下文行数
  '-i'?: boolean                     // 大小写不敏感
  '-n'?: boolean                     // 显示行号
  type?: string                      // 文件类型 (js, py, ...)
  head_limit?: number                // 最大结果数
  offset?: number                    // 分页偏移
  multiline?: boolean                // 多行匹配
}

底层实现

GrepTool 使用 ripgrep (rg) 作为搜索引擎:

typescript
async call(input, context) {
  const options = buildRipgrepOptions(input);
  const result = await ripGrep(input.pattern, options);
  
  // 应用 head_limit 分页
  const paged = applyHeadLimit(result, input.head_limit, input.offset);
  
  return { data: paged };
}

默认 head_limit 为 250 行。设置 head_limit: 0 可获取无限结果。

AgentTool — 子代理

Source: src/tools/AgentTool/AgentTool.tsx (69KB)

输入 Schema

typescript
{
  description: string        // 3-5 词任务描述
  prompt: string             // 详细指令
  subagent_type?: string     // 代理类型
  model?: 'sonnet' | 'opus' | 'haiku'
  run_in_background?: boolean
  name?: string              // 可寻址名称
  isolation?: 'worktree'     // Git 隔离
}

执行流程

子代理有自己的工具池和权限上下文,但共享父会话的 MCP 连接。

工具并发分组

Source: src/utils/toolOrchestration.ts

当 Claude 在一次回复中调用多个工具时,系统按并发安全性分组执行:

typescript
function partitionToolCalls(toolUseBlocks): Batch[] {
  // 连续的并发安全工具合为一批
  // 非安全工具单独一批
  
  // 例如:[Grep, Grep, Edit, Grep]
  // → Batch1: [Grep, Grep] (并行)
  // → Batch2: [Edit] (串行)
  // → Batch3: [Grep] (单个)
}

下一步