Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第 8 章:内置工具深度解析

“工具既是手的延伸,也是心智的延伸。” —— Marshall McLuhan

如果说 Tool 接口定义了工具的骨骼,那么具体的工具实现就是肌肉与血液。Claude Code 内置了超过 40 个工具,涵盖了 shell 执行、文件操作、代码搜索、网络访问和任务管理等核心能力。本章将选取最具代表性的工具进行深度剖析,揭示它们在安全性、性能和用户体验方面的精巧设计。

8.1 BashTool —— 最复杂的单一工具

8.1.1 复杂度概览

BashTool 是 Claude Code 中最复杂的单一工具。其实现涉及 18 个文件,从 BashTool.tsx 主文件到 bashPermissions.tsbashSecurity.tscommandSemantics.tssedEditParser.tsshouldUseSandbox.ts 等辅助模块。这种复杂度源于 shell 命令执行在安全领域的特殊地位——它是所有工具中攻击面最大的。

src/tools/BashTool/
├── BashTool.tsx              # 主实现(schema 定义、call 方法、UI 渲染)
├── bashPermissions.ts        # 权限检查:命令解析、规则匹配、分类器
├── bashSecurity.ts           # 安全检查:命令安全性评估
├── commandSemantics.ts       # 命令语义解释(退出码含义)
├── sedEditParser.ts          # sed 命令特殊处理(作为文件编辑)
├── readOnlyValidation.ts     # 只读约束检查
├── modeValidation.ts         # 模式验证
├── pathValidation.ts         # 路径安全检查
├── sedValidation.ts          # sed 命令验证
├── shouldUseSandbox.ts       # 沙箱判断逻辑
├── bashCommandHelpers.ts     # 复合命令辅助函数
├── destructiveCommandWarning.ts # 破坏性命令警告
├── prompt.ts                 # 系统提示生成
├── toolName.ts               # 工具名称常量
├── commentLabel.ts           # 注释标签
├── utils.ts                  # 通用工具函数
├── UI.tsx                    # React UI 组件
└── BashToolResultMessage.tsx # 结果展示组件

8.1.2 输入 Schema

BashTool 的输入 schema 经过精心设计,包含多个字段:

// src/tools/BashTool/BashTool.tsx
const fullInputSchema = lazySchema(() => z.strictObject({
  command: z.string().describe('The command to execute'),
  timeout: semanticNumber(z.number().optional()).describe(
    `Optional timeout in milliseconds (max ${getMaxTimeoutMs()})`
  ),
  description: z.string().optional().describe(
    'Clear, concise description of what this command does in active voice...'
  ),
  run_in_background: semanticBoolean(z.boolean().optional()).describe(
    'Set to true to run this command in the background.'
  ),
  dangerouslyDisableSandbox: semanticBoolean(z.boolean().optional()).describe(
    'Set this to true to dangerously override sandbox mode...'
  ),
  _simulatedSedEdit: z.object({
    filePath: z.string(),
    newContent: z.string()
  }).optional().describe('Internal: pre-computed sed edit result from preview')
}))

几个值得关注的设计点:

semanticNumber / semanticBoolean 包装器。 这些函数处理模型生成的“语义化“值——模型有时会把布尔值作为字符串 "true" 发送,或者把数字作为字符串 "42" 发送。这些包装器能容错地解析这些情况。

_simulatedSedEdit 的隐藏与防注入。 该字段是内部使用的,在发送给模型的 schema 中被显式移除:

const inputSchema = lazySchema(() =>
  fullInputSchema().omit({ _simulatedSedEdit: true, /* ... */ })
)

同时,toolExecution.ts 中还有一道防线,如果模型试图在输入中注入这个字段,会被强制剥离:

// src/services/tools/toolExecution.ts
if (tool.name === BASH_TOOL_NAME && processedInput &&
    typeof processedInput === 'object' && '_simulatedSedEdit' in processedInput) {
  const { _simulatedSedEdit: _, ...rest } = processedInput
  processedInput = rest
}

这是一个经典的纵深防御——即使 schema 验证被绕过,运行时也会拦截。

8.1.3 并发安全性与只读判断

BashTool 的 isConcurrencySafe 直接委托给 isReadOnly

isConcurrencySafe(input) {
  return this.isReadOnly?.(input) ?? false
},
isReadOnly(input) {
  const compoundCommandHasCd = commandHasAnyCd(input.command)
  const result = checkReadOnlyConstraints(input, compoundCommandHasCd)
  return result.behavior === 'allow'
},

这意味着只有确认为只读的命令才会被标记为并发安全。checkReadOnlyConstraints 会解析命令的 AST,检查是否包含任何写操作。

8.1.4 命令语义分类

BashTool 能够识别命令类型来优化 UI 呈现:

// 搜索命令 -- 在 UI 中折叠显示
const BASH_SEARCH_COMMANDS = new Set([
  'find', 'grep', 'rg', 'ag', 'ack', 'locate', 'which', 'whereis'
])

// 读取命令 -- 在 UI 中折叠显示
const BASH_READ_COMMANDS = new Set([
  'cat', 'head', 'tail', 'less', 'more',
  'wc', 'stat', 'file', 'strings',
  'jq', 'awk', 'cut', 'sort', 'uniq', 'tr'
])

// 目录列表命令
const BASH_LIST_COMMANDS = new Set(['ls', 'tree', 'du'])

// 语义中立命令 -- 不改变管道的读/搜索性质
const BASH_SEMANTIC_NEUTRAL_COMMANDS = new Set([
  'echo', 'printf', 'true', 'false', ':'
])

// 静默命令 -- 成功时无输出,显示 "Done" 而非 "(No output)"
const BASH_SILENT_COMMANDS = new Set([
  'mv', 'cp', 'rm', 'mkdir', 'rmdir', 'chmod', 'chown',
  'chgrp', 'touch', 'ln', 'cd', 'export', 'unset', 'wait'
])

isSearchOrReadBashCommand 的分析逻辑对复合命令有特殊处理——对于管道(|)和链式命令(&&;),所有部分都必须是搜索/读取命令,整个命令才被视为搜索/读取操作。语义中立命令被跳过,不影响判定:

export function isSearchOrReadBashCommand(command: string) {
  const partsWithOperators = splitCommandWithOperators(command)
  let hasSearch = false, hasRead = false, hasList = false
  for (const part of partsWithOperators) {
    // 跳过操作符和重定向目标
    if (['||', '&&', '|', ';'].includes(part)) continue
    const baseCommand = part.trim().split(/\s+/)[0]
    // 语义中立命令不影响判断
    if (BASH_SEMANTIC_NEUTRAL_COMMANDS.has(baseCommand)) continue
    // 任何非搜索/读取命令都会使整个命令不可折叠
    if (!BASH_SEARCH_COMMANDS.has(baseCommand) &&
        !BASH_READ_COMMANDS.has(baseCommand) &&
        !BASH_LIST_COMMANDS.has(baseCommand)) {
      return { isSearch: false, isRead: false, isList: false }
    }
    // ... 累积标记
  }
}

8.1.5 权限检查的多层防线

BashTool 的权限检查通过 bashToolHasPermission 函数实现,是整个系统中最复杂的权限检查流程。它包括:

  1. 模式检查checkPermissionMode)– 检查当前是否在 plan 模式等特殊模式下
  2. 路径约束检查checkPathConstraints)– 确保命令不会操作受限路径
  3. 沙箱判断shouldUseSandbox)– 决定是否在沙箱中执行
  4. 规则匹配 —— 与 allow/deny/ask 规则列表匹配
  5. 安全分类器classifyBashCommand)– 异步 ML 分类器判断命令安全性
  6. sed 特殊处理checkSedConstraints)– sed in-place 编辑的特殊权限路径

权限检查还有一个性能优化——推测性分类器预启动:

// src/services/tools/toolExecution.ts
if (tool.name === BASH_TOOL_NAME && parsedInput.data && 'command' in parsedInput.data) {
  startSpeculativeClassifierCheck(
    parsedInput.data.command,
    appState.toolPermissionContext,
    toolUseContext.abortController.signal,
    toolUseContext.options.isNonInteractiveSession,
  )
}

这段代码在输入验证通过后立即启动分类器检查,而不等到权限检查阶段。这样分类器可以与 pre-tool hooks 并行运行,减少用户等待时间。

8.1.6 安全校验流程

flowchart TD
    A[模型调用 BashTool] --> B[输入验证 Zod safeParse]
    B -->|失败| Z[返回错误]
    B -->|成功| C[剥离 _simulatedSedEdit]
    C --> D[推测性启动分类器]
    C --> E[Pre-tool hooks]
    D --> F{并行等待}
    E --> F
    F --> G[checkPermissionMode]
    G --> H[checkPathConstraints]
    H --> I[规则匹配 allow/deny/ask]
    I -->|allow 规则匹配| J[允许执行]
    I -->|deny 规则匹配| Z
    I -->|ask 或无匹配| K[分类器结果]
    K -->|allow| J
    K -->|deny| Z
    K -->|ask| L[弹出权限对话框]
    L -->|用户允许| J
    L -->|用户拒绝| Z
    J --> M{shouldUseSandbox?}
    M -->|是| N[沙箱执行]
    M -->|否| O[直接执行]
    N --> P[exec 命令]
    O --> P
    P --> Q[Post-tool hooks]
    Q --> R[返回结果]

8.2 文件操作三剑客 —— FileReadTool / FileEditTool / FileWriteTool

8.2.1 FileReadTool —— 智能文件读取

FileReadTool 不是一个简单的 cat 包装。它支持读取普通文本文件、图片(作为视觉内容返回)、PDF(按页提取)、Jupyter Notebook(解析所有 cell)等多种格式。

// src/tools/FileReadTool/FileReadTool.ts(关键属性)
export const FileReadTool = buildTool({
  name: FILE_READ_TOOL_NAME,
  maxResultSizeChars: Infinity,  // 注意:Infinity!
  isConcurrencySafe() { return true },
  isReadOnly() { return true },
  // ...
})

maxResultSizeChars: Infinity 是一个关键设计——FileReadTool 的输出永远不会被持久化到磁盘。源码注释解释道:

“Set to Infinity for tools whose output must never be persisted (e.g. Read, where persisting creates a circular Read -> file -> Read loop and the tool already self-bounds via its own limits).”

如果将读取结果保存到临时文件,模型可能会再次用 Read 读取那个临时文件,形成无限循环。

FileReadTool 还有一个安全特性——阻止读取危险的设备文件:

const BLOCKED_DEVICE_PATHS = new Set([
  '/dev/zero',    // 无限输出,永远不会 EOF
  '/dev/random', '/dev/urandom',  // 同上
  '/dev/stdin', '/dev/fd/0',      // 阻塞等待输入
  // ...
])

8.2.2 FileEditTool —— 精确字符串替换

FileEditTool 的核心理念是“精确替换“而非“全文重写“——通过 old_stringnew_string 实现对文件的最小化修改:

// src/tools/FileEditTool/types.ts
const inputSchema = lazySchema(() => z.strictObject({
  file_path: z.string().describe('The absolute path to the file to modify'),
  old_string: z.string().describe('The text to replace'),
  new_string: z.string().describe('The text to replace it with'),
  replace_all: z.boolean().default(false).describe(
    'Replace all occurrences of old_string'
  ),
}))

FileEditTool 的几个关键设计:

唯一性校验。replace_allfalse 时,old_string 必须在文件中唯一出现。如果有多个匹配,工具会报错要求提供更多上下文来消除歧义。

并发修改检测。 利用 readFileState(FileStateCache)跟踪文件的最后读取时间戳。如果文件在上次读取后被外部修改,编辑会被拒绝:

// 常量定义
export const FILE_UNEXPECTEDLY_MODIFIED_ERROR =
  'File was unexpectedly modified since last read'

文件大小上限。 设置了 1 GiB 的硬限制,防止处理超大文件导致 OOM:

const MAX_EDIT_FILE_SIZE = 1024 * 1024 * 1024 // 1 GiB (stat bytes)

权限检查。 通过 checkWritePermissionForTool 验证文件路径是否在允许写入的范围内:

async checkPermissions(input, context) {
  const appState = context.getAppState()
  return checkWritePermissionForTool(
    FileEditTool, input, appState.toolPermissionContext,
  )
},

路径回填。 backfillObservableInput 方法将相对路径或 ~ 开头的路径展开为绝对路径,确保 hook 的 allowlist 不会被绕过:

backfillObservableInput(input) {
  if (typeof input.file_path === 'string') {
    input.file_path = expandPath(input.file_path)
  }
},

8.2.3 FileWriteTool —— 全文件创建/覆写

FileWriteTool 用于创建新文件或完全覆写已有文件,其 schema 更为简洁:

const inputSchema = lazySchema(() => z.strictObject({
  file_path: z.string().describe(
    'The absolute path to the file to write (must be absolute, not relative)'
  ),
  content: z.string().describe('The content to write to the file'),
}))

FileWriteTool 的输出 schema 包含丰富的变更信息,方便 UI 展示差异:

const outputSchema = lazySchema(() => z.object({
  type: z.enum(['create', 'update']),  // 创建还是更新
  filePath: z.string(),
  content: z.string(),
  structuredPatch: z.array(hunkSchema()),  // diff patch
  originalFile: z.string().optional(),      // 原文件内容(如有)
}))

8.2.4 三剑客的设计对比

graph LR
    subgraph 文件操作工具
        R[FileReadTool]
        E[FileEditTool]
        W[FileWriteTool]
    end

    R -->|"maxResultSizeChars: Infinity<br/>isConcurrencySafe: true<br/>isReadOnly: true"| RR[读取文件]
    E -->|"maxResultSizeChars: 100K<br/>isConcurrencySafe: false<br/>isReadOnly: false<br/>strict: true"| EE[精确替换]
    W -->|"maxResultSizeChars: 100K<br/>isConcurrencySafe: false<br/>isReadOnly: false"| WW[完全覆写]

    RR -->|支持格式| F1[文本 / 图片 / PDF / Notebook]
    EE -->|保护机制| F2[唯一性校验 / 并发检测 / 大小限制]
    WW -->|输出信息| F3[类型 / diff patch / 原文件]
特性FileReadToolFileEditToolFileWriteTool
并发安全
只读
maxResultSizeCharsInfinity100,000100,000
strict 模式
格式支持文本/图片/PDF/Notebook纯文本纯文本
权限检查读取权限写入权限写入权限
shouldDefer

8.3 GlobTool/GrepTool —— Ripgrep 集成

8.3.1 GlobTool —— 快速文件名搜索

GlobTool 通过内部的 glob 工具函数执行文件名模式匹配。它的设计注重简洁:

// src/tools/GlobTool/GlobTool.ts
export const GlobTool = buildTool({
  name: GLOB_TOOL_NAME,
  searchHint: 'find files by name pattern or wildcard',
  maxResultSizeChars: 100_000,
  isConcurrencySafe() { return true },
  isReadOnly() { return true },
  isSearchOrReadCommand() { return { isSearch: true, isRead: false } },
  // ...
})

GlobTool 和 GrepTool 都是并发安全且只读的——它们可以与其他搜索工具并行执行。

输入 schema 只有两个字段:

const inputSchema = lazySchema(() => z.strictObject({
  pattern: z.string().describe('The glob pattern to match files against'),
  path: z.string().optional().describe('The directory to search in.'),
}))

值得注意的是,当内部构建嵌入了搜索工具时,GlobTool 会被完全移除:

// src/tools.ts
...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),

8.3.2 GrepTool —— Ripgrep 的深度集成

GrepTool 是 Claude Code 搜索能力的核心。它基于 Ripgrep(rg)实现,提供了远超 grep 的搜索体验。

丰富的输入参数。 GrepTool 的 schema 几乎是 ripgrep 常用参数的一对一映射:

const inputSchema = lazySchema(() => z.strictObject({
  pattern: z.string(),      // 正则表达式模式
  path: z.string().optional(),
  glob: z.string().optional(),  // 文件过滤 glob
  output_mode: z.enum(['content', 'files_with_matches', 'count']).optional(),
  '-B': semanticNumber(z.number().optional()),  // 前文行数
  '-A': semanticNumber(z.number().optional()),  // 后文行数
  '-C': semanticNumber(z.number().optional()),  // 上下文行数
  context: semanticNumber(z.number().optional()),
  '-n': semanticBoolean(z.boolean().optional()),  // 行号
  '-i': semanticBoolean(z.boolean().optional()),  // 忽略大小写
  type: z.string().optional(),     // 文件类型过滤
  head_limit: semanticNumber(z.number().optional()),  // 结果行数限制
  offset: semanticNumber(z.number().optional()),       // 偏移量
  multiline: semanticBoolean(z.boolean().optional()),  // 多行模式
}))

默认结果限制。 为防止上下文膨胀,GrepTool 默认只返回前 250 条结果:

const DEFAULT_HEAD_LIMIT = 250

模型可以通过传 head_limit: 0 来获取无限结果,但这在源码注释中被标记为“谨慎使用“。

VCS 目录自动排除。 搜索时自动排除版本控制目录:

const VCS_DIRECTORIES_TO_EXCLUDE = ['.git', '.svn', '.hg', '.bzr', '.jj', '.sl']

分页支持。 通过 offsethead_limit 的组合,GrepTool 支持结果分页:

function applyHeadLimit<T>(items: T[], limit: number | undefined, offset: number = 0) {
  if (limit === 0) return { items: items.slice(offset), appliedLimit: undefined }
  const effectiveLimit = limit ?? DEFAULT_HEAD_LIMIT
  const sliced = items.slice(offset, offset + effectiveLimit)
  const wasTruncated = items.length - offset > effectiveLimit
  return { items: sliced, appliedLimit: wasTruncated ? effectiveLimit : undefined }
}

GrepTool 与 BashTool grep 的关系。 有趣的是,模型可以通过 BashTool 执行 greprg 命令,也可以使用 GrepTool。系统提示中明确引导模型使用 GrepTool 而非 BashTool 中的 grep。GrepTool 的优势在于:结构化的输出(文件名列表、匹配计数等)、内置的结果限制和分页、以及正确的权限检查。

8.3.3 搜索工具的设计模式

sequenceDiagram
    participant Model as 模型
    participant Grep as GrepTool
    participant RG as Ripgrep (rg)
    participant FS as 文件系统

    Model->>Grep: { pattern: "TODO", path: "src/", type: "ts" }
    Grep->>Grep: validateInput(路径存在性检查)
    Grep->>Grep: checkPermissions(读取权限)
    Grep->>RG: 构建 rg 命令参数
    Note right of Grep: --type ts --glob<br/>排除 .git 等目录
    RG->>FS: 遍历文件系统
    FS-->>RG: 匹配结果
    RG-->>Grep: 原始输出
    Grep->>Grep: applyHeadLimit(results, 250, 0)
    Grep-->>Model: { numFiles: 12, filenames: [...], content: "..." }

8.4 WebFetchTool / WebSearchTool —— 网络工具

8.4.1 WebFetchTool —— URL 内容抓取

WebFetchTool 从指定 URL 抓取内容,将 HTML 转为 Markdown,然后用一个小模型对内容进行总结:

export const WebFetchTool = buildTool({
  name: WEB_FETCH_TOOL_NAME,
  searchHint: 'fetch and extract content from a URL',
  maxResultSizeChars: 100_000,
  shouldDefer: true,           // 延迟加载
  isConcurrencySafe() { return true },
  isReadOnly() { return true },
  // ...
})

WebFetchTool 被标记为 shouldDefer: true——它不是核心的编码工具,大多数对话不需要它,所以延迟加载以节省 prompt 空间。

输入 schema。 只有两个必填字段:

const inputSchema = lazySchema(() => z.strictObject({
  url: z.string().url().describe('The URL to fetch content from'),
  prompt: z.string().describe('The prompt to run on the fetched content'),
}))

prompt 字段是一个巧妙设计——模型不是简单地抓取网页然后塞入上下文,而是告诉 WebFetchTool “要从页面提取什么信息”。内部使用一个较小的模型处理大量网页内容并提取相关部分,避免了将整个页面内容放入主对话的上下文窗口。

权限模型。 WebFetchTool 的权限基于域名而非完整 URL:

function webFetchToolInputToPermissionRuleContent(input: { [k: string]: unknown }): string {
  try {
    const { url } = parsedInput.data
    const hostname = new URL(url).hostname
    return `domain:${hostname}`
  } catch {
    return `input:${input.toString()}`
  }
}

这意味着用户可以授权 “允许访问 github.com” 而不需要逐一授权每个 URL。

预批准主机。 某些安全的主机(如文档网站)被预先批准,无需用户确认:

// 通过 isPreapprovedHost 和 isPreapprovedUrl 函数检查

8.4.2 WebSearchTool —— 基于 API 的网络搜索

WebSearchTool 的实现方式与其他工具截然不同——它不是在本地执行搜索,而是利用 Anthropic API 的内置 web_search 工具:

function makeToolSchema(input: Input): BetaWebSearchTool20250305 {
  return {
    type: 'web_search_20250305',
    name: 'web_search',
    allowed_domains: input.allowed_domains,
    blocked_domains: input.blocked_domains,
    max_uses: 8,  // 硬编码最多 8 次搜索
  }
}

WebSearchTool 实际上是一个 “工具中的工具”——它通过 queryModelWithStreaming 发起一次新的 API 调用,在该调用中配置了 web_search 工具。API 侧的模型执行搜索,然后 Claude Code 将搜索结果格式化返回给主对话。

const outputSchema = lazySchema(() => z.object({
  query: z.string(),
  results: z.array(z.union([searchResultSchema(), z.string()])),
  durationSeconds: z.number(),
}))

结果是 SearchResult | string 的混合数组——搜索结果和模型生成的文本评论交替出现。

8.4.3 网络工具对比

特性WebFetchToolWebSearchTool
功能抓取单个 URL 内容网络搜索
实现方式直接 HTTP 请求嵌套 API 调用
内容处理HTML -> Markdown -> 小模型总结API 侧 web_search 工具
权限粒度域名级别通用(域名过滤在参数中)
shouldDefer
max_usesN/A8 次搜索/调用

8.5 TaskTools —— 任务管理系统

8.5.1 设计背景

Task 工具族(TaskCreate、TaskGet、TaskUpdate、TaskList)是 Claude Code 的任务管理系统 v2(TodoV2),用于替代旧的 TodoWriteTool。它们在 isTodoV2Enabled() 为 true 时才可用。

8.5.2 TaskCreateTool

// src/tools/TaskCreateTool/TaskCreateTool.ts
export const TaskCreateTool = buildTool({
  name: TASK_CREATE_TOOL_NAME,
  searchHint: 'create a task in the task list',
  maxResultSizeChars: 100_000,
  shouldDefer: true,
  isEnabled() { return isTodoV2Enabled() },
  isConcurrencySafe() { return true },
  // ...
  async call({ subject, description, activeForm, metadata }, context) {
    const taskListId = await getTaskListId(context)
    const task = await createTask(taskListId, {
      subject,
      description,
      activeForm,
      metadata,
      agentName: getAgentName(context),
      teamName: getTeamName(context),
    })
    // 执行 task-created hooks
    await executeTaskCreatedHooks(/* ... */)
    return { data: { task: { id: task.id, subject: task.subject } } }
  }
})

TaskCreateTool 的输入 schema:

const inputSchema = lazySchema(() => z.strictObject({
  subject: z.string().describe('A brief title for the task'),
  description: z.string().describe('What needs to be done'),
  activeForm: z.string().optional().describe(
    'Present continuous form shown in spinner when in_progress (e.g., "Running tests")'
  ),
  metadata: z.record(z.string(), z.unknown()).optional().describe(
    'Arbitrary metadata to attach to the task'
  ),
}))

activeForm 字段 是一个 UX 优化——当任务处于进行中状态时,spinner 会显示这个描述(如 “Running tests”),而非通用的 “Working…”。

8.5.3 Task 工具族的设计模式

Task 工具族共享几个设计特征:

  1. 全部 shouldDefer: true —— 任务管理不是每次对话都需要的
  2. 全部 isConcurrencySafe: true —— 任务操作是独立的,可以并行
  3. 条件启用 —— 通过 isEnabled() 检查功能标记
  4. 与 hooks 集成 —— TaskCreate 执行后触发 task-created hooks
graph TD
    subgraph Task 工具族
        TC[TaskCreateTool<br/>创建任务]
        TG[TaskGetTool<br/>获取任务详情]
        TU[TaskUpdateTool<br/>更新任务状态]
        TL[TaskListTool<br/>列出所有任务]
    end

    TC -->|创建| DB[(任务存储)]
    TG -->|读取| DB
    TU -->|更新| DB
    TL -->|查询| DB

    TC -->|触发| H[task-created hooks]
    TU -->|触发| H2[task-updated hooks]

    subgraph 任务生命周期
        S1[created] --> S2[in_progress]
        S2 --> S3[completed]
        S2 --> S4[failed]
        S2 --> S5[cancelled]
    end

8.5.4 共同的工具模式

回顾本章分析的所有工具,可以提炼出几个共同的设计模式:

模式一:lazySchema。 所有工具都使用 lazySchema 延迟构建 Zod schema,避免模块加载时的开销:

const inputSchema = lazySchema(() => z.strictObject({ /* ... */ }))

模式二:buildTool 工厂。 每个工具都通过 buildTool 构建,享受失败关闭的默认值。

模式三:分离的 UI。 工具的业务逻辑和 UI 渲染分布在不同文件中(如 GrepTool.tsUI.tsx),但通过 Tool 接口统一暴露。

模式四:权限委托。 文件工具委托给 checkReadPermissionForTool / checkWritePermissionForTool,BashTool 有独立的权限链,网络工具基于域名。

本章小结

本章深入分析了 Claude Code 最核心的内置工具:

  1. BashTool 是最复杂的工具,18 个文件、多层安全防线、命令语义分类、推测性分类器等设计使其在提供强大 shell 能力的同时保持安全
  2. FileRead/Edit/Write 三剑客各司其职,Read 以 Infinity 的 maxResultSizeChars 避免循环读取,Edit 通过精确替换最小化修改,Write 提供完整的 diff 信息
  3. Glob/Grep 工具与 Ripgrep 深度集成,提供结构化输出、分页、结果限制等高级功能
  4. WebFetch/WebSearch 分别通过 HTTP 抓取和嵌套 API 调用提供网络能力
  5. Task 工具族 展示了条件启用、延迟加载、hooks 集成等模式

下一章将分析这些工具如何在 StreamingToolExecutor 中被调度执行,以及工具结果如何被截断和持久化。