## 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

# 第 7 章：工具架构

> “好的架构允许重大决策推迟到最后一刻。” —— Robert C. Martin

Claude Code 的工具系统是整个交互循环中最关键的子系统。当模型决定调用一个工具时，控制权从 LLM 推理侧转移到宿主程序侧——文件被读写、命令被执行、网页被抓取。工具系统的设计直接决定了 Claude Code 的能力边界与安全底线。

本章将深入剖析工具系统的类型契约、工厂模式、依赖注入、注册发现和延迟加载五个层面，展示这套架构如何在保持极高可扩展性的同时实现“失败关闭“（fail-closed）的安全默认。

## 7.1 Tool 接口契约

### 7.1.1 接口全貌

Claude Code 的工具接口定义在 `src/Tool.ts` 中，是一个参数化的 TypeScript 泛型类型。该接口包含超过 30 个方法和属性，覆盖了工具生命周期的每一个阶段：输入验证、权限检查、执行、结果序列化和 UI 渲染。

    // src/Tool.ts（简化展示核心签名）
    export type Tool<
      Input extends AnyObject = AnyObject,
      Output = unknown,
      P extends ToolProgressData = ToolProgressData,
    > = {
      readonly name: string
      aliases?: string[]
      searchHint?: string
      readonly inputSchema: Input
      readonly inputJSONSchema?: ToolInputJSONSchema
      outputSchema?: z.ZodType<unknown>
      maxResultSizeChars: number
      readonly shouldDefer?: boolean
      readonly alwaysLoad?: boolean
      readonly strict?: boolean
      isMcp?: boolean
      isLsp?: boolean
      mcpInfo?: { serverName: string; toolName: string }

      // 生命周期方法
      call(args, context, canUseTool, parentMessage, onProgress?): Promise<ToolResult<Output>>
      description(input, options): Promise<string>
      prompt(options): Promise<string>
      validateInput?(input, context): Promise<ValidationResult>
      checkPermissions(input, context): Promise<PermissionResult>

      // 分类属性
      isConcurrencySafe(input): boolean
      isEnabled(): boolean
      isReadOnly(input): boolean
      isDestructive?(input): boolean
      interruptBehavior?(): 'cancel' | 'block'
      isSearchOrReadCommand?(input): { isSearch: boolean; isRead: boolean; isList?: boolean }
      isOpenWorld?(input): boolean
      requiresUserInteraction?(): boolean

      // 路径与权限
      getPath?(input): string
      preparePermissionMatcher?(input): Promise<(pattern: string) => boolean>
      backfillObservableInput?(input: Record<string, unknown>): void

      // UI 渲染（10+ 方法）
      userFacingName(input): string
      renderToolUseMessage(input, options): React.ReactNode
      renderToolResultMessage?(content, progressMessages, options): React.ReactNode
      renderToolUseProgressMessage?(progressMessages, options): React.ReactNode
      // ... 更多渲染方法

      // 序列化与分类器
      toAutoClassifierInput(input): unknown
      mapToolResultToToolResultBlockParam(content, toolUseID): ToolResultBlockParam
      inputsEquivalent?(a, b): boolean
    }

这个接口的设计哲学体现了几个核心原则：

原则一：输入驱动的属性推断。 注意 `isConcurrencySafe`、`isReadOnly`、`isDestructive` 等方法都接收 `input` 参数。这意味着同一个工具可以根据不同输入表现出不同的安全特征。例如，`BashTool` 执行 `ls` 时是只读且并发安全的，但执行 `rm -rf /` 时是破坏性的。

原则二：渲染与逻辑分离。 接口中 UI 渲染方法占了近半数，包括 `renderToolUseMessage`、`renderToolResultMessage`、`renderToolUseProgressMessage`、`renderToolUseRejectedMessage`、`renderToolUseErrorMessage` 和 `renderGroupedToolUse` 等。每个工具完全掌控自己在终端中的呈现方式，这与 React 的组件化思想一脉相承。

原则三：渐进式增强。 大量方法标记为可选（`?`），工具可以只实现核心方法，让框架提供合理默认值。

### 7.1.2 核心方法详解

#### call —— 工具执行入口

    call(
      args: z.infer<Input>,
      context: ToolUseContext,
      canUseTool: CanUseToolFn,
      parentMessage: AssistantMessage,
      onProgress?: ToolCallProgress<P>,
    ): Promise<ToolResult<Output>>

`call` 是工具的核心执行方法。注意其返回类型 `ToolResult<Output>`：

    export type ToolResult<T> = {
      data: T
      newMessages?: (UserMessage | AssistantMessage | AttachmentMessage | SystemMessage)[]
      contextModifier?: (context: ToolUseContext) => ToolUseContext
      mcpMeta?: { _meta?: Record<string, unknown>; structuredContent?: Record<string, unknown> }
    }

`ToolResult` 不仅包含工具输出数据，还可以携带新的消息（注入到对话历史中）和上下文修改器。这个 `contextModifier` 是一个精妙的设计——它允许非并发工具在执行后修改后续工具的执行环境。例如，一个切换目录的命令可以通过 `contextModifier` 更新后续工具看到的工作目录。

#### checkPermissions —— 权限闸门

    checkPermissions(
      input: z.infer<Input>,
      context: ToolUseContext,
    ): Promise<PermissionResult>

每个工具可以定义自己的权限检查逻辑。`PermissionResult` 的行为有三种：`allow`（允许执行）、`deny`（拒绝并告知原因）、`ask`（弹出交互式权限确认对话框）。这个方法只包含工具特有的逻辑，通用权限检查（如基于规则的匹配）在 `permissions.ts` 中处理。

#### isConcurrencySafe —— 并发安全标记

    isConcurrencySafe(input: z.infer<Input>): boolean

这个方法的返回值直接控制 `StreamingToolExecutor` 的调度策略。返回 `true` 的工具可以与其他并发安全工具并行执行；返回 `false` 的工具会独占执行槽位。稍后在第 9 章将详细分析调度器的工作原理。

#### isDestructive —— 破坏性标记

    isDestructive?(input: z.infer<Input>): boolean

注释说得很明确：“Only set when the tool performs irreversible operations (delete, overwrite, send).” 这个标记影响安全分类器的判断和 UI 的警告提示。

### 7.1.3 接口 UML 类图

    classDiagram
        class Tool~Input, Output, P~ {
            <<interface>>
            +name: string
            +aliases?: string[]
            +searchHint?: string
            +inputSchema: Input
            +inputJSONSchema?: ToolInputJSONSchema
            +outputSchema?: ZodType
            +maxResultSizeChars: number
            +shouldDefer?: boolean
            +alwaysLoad?: boolean
            +strict?: boolean
            +isMcp?: boolean
            +mcpInfo?: McpInfo
            +call(args, context, canUseTool, parentMsg, onProgress?) ToolResult~Output~
            +description(input, options) string
            +prompt(options) string
            +validateInput?(input, context) ValidationResult
            +checkPermissions(input, context) PermissionResult
            +isConcurrencySafe(input) boolean
            +isEnabled() boolean
            +isReadOnly(input) boolean
            +isDestructive?(input) boolean
            +interruptBehavior?() cancel|block
            +isSearchOrReadCommand?(input) SearchReadResult
            +getPath?(input) string
            +preparePermissionMatcher?(input) MatcherFn
            +backfillObservableInput?(input) void
            +toAutoClassifierInput(input) unknown
            +userFacingName(input) string
            +mapToolResultToToolResultBlockParam(content, id) ToolResultBlockParam
            +renderToolUseMessage(input, options) ReactNode
            +renderToolResultMessage?(content, progress, options) ReactNode
            +renderToolUseProgressMessage?(progress, options) ReactNode
            +renderToolUseRejectedMessage?(input, options) ReactNode
            +renderToolUseErrorMessage?(result, options) ReactNode
            +renderGroupedToolUse?(toolUses, options) ReactNode
        }

        class ToolResult~T~ {
            +data: T
            +newMessages?: Message[]
            +contextModifier?: ContextModifierFn
            +mcpMeta?: McpMeta
        }

        class ToolDef~Input, Output, P~ {
            <<type>>
            Omit Tool without DefaultableKeys
            + Partial DefaultableKeys
        }

        class BuiltTool~D~ {
            <<type>>
            Omit D without DefaultableKeys
            + Required DefaultableKeys
        }

        Tool --> ToolResult : returns
        ToolDef --|> Tool : buildTool fills defaults
        BuiltTool --|> Tool : concrete tool type

## 7.2 buildTool 工厂 —— 失败关闭的默认值设计

### 7.2.1 设计动机

Claude Code 拥有 40+ 个内置工具，如果每个工具都必须实现 Tool 接口的全部 30+ 方法，代码将充斥着重复的样板。更危险的是，如果开发者忘记实现某个安全相关的方法，可能会留下安全漏洞。

`buildTool` 工厂函数的设计目标是：让安全默认值自动生效，让开发者显式选择放宽。

### 7.2.2 TOOL_DEFAULTS —— 失败关闭的守卫

    // src/Tool.ts
    const TOOL_DEFAULTS = {
      isEnabled: () => true,
      isConcurrencySafe: (_input?: unknown) => false,    // 假定不安全
      isReadOnly: (_input?: unknown) => false,            // 假定会写入
      isDestructive: (_input?: unknown) => false,
      checkPermissions: (input, _ctx?) =>
        Promise.resolve({ behavior: 'allow', updatedInput: input }),
      toAutoClassifierInput: (_input?: unknown) => '',
      userFacingName: (_input?: unknown) => '',
    }

这段代码的关键设计决策值得逐一分析：

默认值 | 值 | 安全含义
--- | --- | ---
`isConcurrencySafe` | `false` | 新工具默认独占执行，不会与其他工具并发运行。这避免了潜在的竞态条件。
`isReadOnly` | `false` | 新工具默认被视为有写操作。这确保权限系统不会错误地跳过写入检查。
`isDestructive` | `false` | 这个看似违反“失败关闭“原则——新工具不会被标记为破坏性的。但这里的权衡是：将所有工具默认标记为破坏性会导致过多的用户确认弹窗，反而降低用户体验且造成“告警疲劳“。
`checkPermissions` | `allow` | 默认允许——工具级权限检查延迟到通用权限系统（`permissions.ts`）。这避免了工具漏实现 `checkPermissions` 就完全无法使用的问题。
`toAutoClassifierInput` | `''`（空字符串） | 空字符串意味着该工具从安全分类器的视角被跳过。源码注释明确指出：“skip classifier —— security-relevant tools must override”。

### 7.2.3 buildTool 实现

    // src/Tool.ts
    export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
      return {
        ...TOOL_DEFAULTS,
        userFacingName: () => def.name,
        ...def,
      } as BuiltTool<D>
    }

实现极为简洁——利用 JavaScript 对象展开的后值覆盖特性，工具定义中显式提供的方法会覆盖默认值。类型系统通过 `BuiltTool<D>` 保证结果始终是一个完整的 `Tool`。

### 7.2.4 DefaultableToolKeys —— 可省略的方法集

    type DefaultableToolKeys =
      | 'isEnabled'
      | 'isConcurrencySafe'
      | 'isReadOnly'
      | 'isDestructive'
      | 'checkPermissions'
      | 'toAutoClassifierInput'
      | 'userFacingName'

只有这 7 个方法被列为可省略。其他方法（如 `call`、`inputSchema`、`prompt`）是每个工具必须实现的——编译器会强制要求。

    graph TD
        A[开发者定义 ToolDef] -->|省略 isConcurrencySafe| B[buildTool 工厂]
        A -->|省略 isReadOnly| B
        A -->|省略 checkPermissions| B
        B -->|注入 TOOL_DEFAULTS| C[完整的 Tool 对象]
        C -->|isConcurrencySafe = false| D[调度器独占执行]
        C -->|isReadOnly = false| E[权限系统检查写入]
        C -->|checkPermissions = allow| F[交由通用权限系统]

        style B fill:#f9f,stroke:#333,stroke-width:2px
        style D fill:#fbb,stroke:#333
        style E fill:#fbb,stroke:#333

## 7.3 ToolUseContext —— 依赖注入的核心载体

### 7.3.1 Context 的角色

在传统的面向对象设计中，工具可能是一个类实例，通过构造器注入依赖。Claude Code 选择了函数式的方式——每个工具的 `call` 方法接收一个 `ToolUseContext` 对象，其中包含了工具执行所需的全部上下文。

`ToolUseContext` 定义在 `src/Tool.ts` 中，是一个拥有 40+ 字段的大型结构体。这看似违反了“接口隔离原则“，但在 AI 编码工具的特殊场景下，这种设计有其合理性：工具需要的上下文是动态变化的（每次 API 调用可能有不同的配置），且工具之间共享大量上下文（如 MCP 客户端、权限配置、消息历史）。

### 7.3.2 Context 的核心字段分组

将 40+ 个字段按功能分组，可以看到清晰的层次结构：

配置层（options）：

    options: {
      commands: Command[]           // 可用斜杠命令
      debug: boolean                // 调试模式
      mainLoopModel: string         // 当前模型名称
      tools: Tools                  // 可用工具列表
      verbose: boolean              // 详细输出
      thinkingConfig: ThinkingConfig // 思考配置
      mcpClients: MCPServerConnection[] // MCP 服务器连接
      mcpResources: Record<string, ServerResource[]>
      isNonInteractiveSession: boolean  // 非交互模式（SDK/CI）
      agentDefinitions: AgentDefinitionsResult
      maxBudgetUsd?: number         // 成本预算上限
      customSystemPrompt?: string   // 自定义系统提示
      appendSystemPrompt?: string   // 追加系统提示
      refreshTools?: () => Tools    // 动态刷新工具列表
    }

状态管理层：

    abortController: AbortController           // 取消信号
    readFileState: FileStateCache              // 文件读取缓存
    getAppState(): AppState                     // 获取全局状态
    setAppState(f: (prev) => AppState): void   // 更新全局状态
    setAppStateForTasks?: (f) => void          // 任务级状态更新（穿透子代理）
    messages: Message[]                         // 对话消息历史

UI 交互层：

    setToolJSX?: SetToolJSXFn           // 自定义 JSX 渲染
    addNotification?: (notif) => void    // 推送通知
    appendSystemMessage?: (msg) => void  // 追加系统消息
    sendOSNotification?: (opts) => void  // 系统级通知
    setInProgressToolUseIDs: (f) => void // 标记进行中的工具
    setHasInterruptibleToolInProgress?: (v) => void
    setStreamMode?: (mode) => void       // 设置 spinner 模式
    openMessageSelector?: () => void     // 打开消息选择器

跟踪与遥测层：

    setResponseLength: (f) => void      // 响应长度统计
    pushApiMetricsEntry?: (ttftMs) => void // API 指标
    updateFileHistoryState: (updater) => void
    updateAttributionState: (updater) => void
    queryTracking?: QueryChainTracking   // 查询链跟踪
    toolDecisions?: Map<string, {...}>   // 工具决策记录

子代理相关：

    agentId?: AgentId            // 子代理 ID
    agentType?: string           // 子代理类型
    requireCanUseTool?: boolean  // 强制权限检查（推测执行用）
    renderedSystemPrompt?: SystemPrompt // 父代理冻结的系统提示
    localDenialTracking?: DenialTrackingState // 本地拒绝追踪

内容管理层：

    contentReplacementState?: ContentReplacementState  // 工具结果替换预算
    fileReadingLimits?: { maxTokens?; maxSizeBytes? }  // 文件读取限制
    globLimits?: { maxResults? }                        // Glob 结果限制

### 7.3.3 setAppState 与 setAppStateForTasks 的分裂

`ToolUseContext` 中有一个耐人寻味的设计——两个状态更新函数：

    setAppState(f: (prev: AppState) => AppState): void
    setAppStateForTasks?: (f: (prev: AppState) => AppState) => void

注释解释了原因：

> “Always-shared setAppState for session-scoped infrastructure (background tasks, session hooks). Unlike setAppState, which is no-op for async agents (see createSubagentContext), this always reaches the root store so agents at any nesting depth can register/clean up infrastructure that outlives a single turn.”

这是因为 Claude Code 支持多层嵌套的子代理（Agent 调用 Agent），每一层子代理有自己的 `ToolUseContext`。对于普通的状态更新，子代理的修改不应该泄漏到父代理；但对于全局基础设施（如后台任务注册），子代理必须能够穿透到根存储。这种双通道设计精确地解决了这个矛盾。

### 7.3.4 Context 传递的数据流

    sequenceDiagram
        participant REPL as REPL.tsx
        participant Query as query.ts
        participant STE as StreamingToolExecutor
        participant TE as toolExecution.ts
        participant Tool as 具体工具

        REPL->>Query: 创建初始 ToolUseContext
        Note right of REPL: options, abortController,<br/>messages, setAppState...
        Query->>STE: new StreamingToolExecutor(tools, canUseTool, context)
        STE->>STE: addTool(block, assistantMessage)
        STE->>TE: runToolUse(block, assistantMessage, canUseTool, context)
        TE->>TE: 校验输入(zod safeParse)
        TE->>TE: runPreToolUseHooks
        TE->>TE: resolveHookPermissionDecision
        TE->>Tool: tool.call(input, context, canUseTool, msg, onProgress)
        Tool-->>TE: ToolResult { data, newMessages?, contextModifier? }
        TE->>TE: runPostToolUseHooks
        TE->>TE: processToolResultBlock(截断/持久化)
        TE-->>STE: yield MessageUpdate
        STE->>STE: 应用 contextModifier
        STE-->>Query: yield 结果消息

## 7.4 工具注册与发现 —— getAllBaseTools 函数的工具组装

### 7.4.1 工具注册表

Claude Code 没有使用装饰器模式或服务定位器模式来注册工具，而是采用了最直接的方式——在 `src/tools.ts` 中用一个函数显式列出所有工具：

    // src/tools.ts
    export function getAllBaseTools(): Tools {
      return [
        AgentTool,
        TaskOutputTool,
        BashTool,
        ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
        ExitPlanModeV2Tool,
        FileReadTool,
        FileEditTool,
        FileWriteTool,
        NotebookEditTool,
        WebFetchTool,
        TodoWriteTool,
        WebSearchTool,
        TaskStopTool,
        AskUserQuestionTool,
        SkillTool,
        EnterPlanModeTool,
        ...(isTodoV2Enabled() ? [TaskCreateTool, TaskGetTool, TaskUpdateTool, TaskListTool] : []),
        ...(isWorktreeModeEnabled() ? [EnterWorktreeTool, ExitWorktreeTool] : []),
        ...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []),
        // ... 更多条件工具
      ]
    }

这种设计有几个重要特点：

条件编译（Dead Code Elimination）。 许多工具使用 `feature()` 标记进行条件加载：

    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

Bun 的打包器会在编译时对 `feature()` 调用求值，从而将未启用的工具完全从产物中删除。这不仅减少了包大小，还保证了未启用的功能不会泄漏任何代码路径。

嵌入式搜索工具的去重。 当内部构建（ant-native）内嵌了 bfs/ugrep 时，GlobTool 和 GrepTool 会被跳过：

    ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),

这是因为内嵌的搜索工具已经被别名到 shell 中的 `find` 和 `grep` 命令，GlobTool/GrepTool 就变得多余了。

### 7.4.2 工具池组装

`getAllBaseTools` 只是原始工具列表。实际交给模型使用的工具列表还要经过多层过滤：

    export function assembleToolPool(
      permissionContext: ToolPermissionContext,
      mcpTools: Tools,
    ): Tools {
      const builtInTools = getTools(permissionContext)         // 过滤后的内置工具
      const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)  // 过滤后的 MCP 工具

      // 按名称排序以保证 prompt-cache 稳定性
      const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)
      return uniqBy(
        [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
        'name',
      )
    }

注意这里的缓存稳定性设计：内置工具和 MCP 工具分别排序后拼接，内置工具作为前缀。源码注释解释了原因：

> “The server’s claude_code_system_cache_policy places a global cache breakpoint after the last prefix-matched built-in tool; a flat sort would interleave MCP tools into built-ins and invalidate all downstream cache keys whenever an MCP tool sorts between existing built-ins.”

这意味着 Anthropic 的 API 服务器会在系统提示中内置工具列表之后设置缓存断点。如果 MCP 工具穿插到内置工具之间，就会破坏这个缓存，导致每次 API 调用都需要重新处理整个工具列表。

### 7.4.3 过滤层次

    graph TD
        A[getAllBaseTools<br/>40+ 工具] -->|feature 条件编译| B[条件工具过滤]
        B -->|isEnabled 检查| C[启用的工具]
        C -->|filterToolsByDenyRules| D[去除拒绝规则匹配的工具]
        D -->|REPL 模式过滤| E[隐藏 REPL 包装的原语工具]
        E -->|SIMPLE 模式过滤| F[仅保留 Bash/Read/Edit]
        F -->|assembleToolPool| G[合并 MCP 工具]
        G -->|uniqBy name| H[最终工具集]
        H -->|按名称排序| I[发送到 API]

## 7.5 延迟工具加载 —— ToolSearchTool 如何减少初始 prompt 大小

### 7.5.1 问题背景

Claude Code 支持大量工具——内置工具加上用户配置的 MCP 工具，总数很容易超过 50 个。每个工具的完整 JSON Schema 定义都要包含在系统提示中，这会消耗大量的上下文窗口空间。更关键的是，这些 token 会计入每次 API 调用的成本。

### 7.5.2 延迟加载机制

ToolSearchTool 实现了一个“按需加载“机制：

1. 初始加载时，被标记为 `shouldDefer: true` 的工具只发送名称，不发送完整 schema
2. 模型需要时，调用 `ToolSearch` 工具搜索并获取完整 schema
3. 后续调用，已加载的工具 schema 对模型可见，可直接调用

    // src/tools/ToolSearchTool/prompt.ts
    export function isDeferredTool(tool: Tool): boolean {
      // alwaysLoad 优先级最高——永不延迟
      if (tool.alwaysLoad) return false
      // MCP 工具始终延迟（工作流特定）
      if (tool.isMcp) return true
      // 工具自声明延迟
      return tool.shouldDefer === true
    }

`ToolSearchTool` 支持三种查询模式：

    const inputSchema = z.object({
      query: z.string().describe(
        'Query to find deferred tools. Use "select:<tool_name>" for direct selection, ' +
        'or keywords to search.'
      ),
      max_results: z.number().optional().default(5),
    })

- 精确选择：`"select:Read,Edit,Grep"` —— 直接按名称获取
- 关键词搜索：`"notebook jupyter"` —— 模糊匹配
- 名称约束搜索：`"+slack send"` —— 要求名称包含 “slack”，按其余词排名

### 7.5.3 Schema 缺失检测

当模型尝试调用一个未加载 schema 的延迟工具时，输入参数往往会格式错误（因为模型没有看到参数定义）。`toolExecution.ts` 中有专门的检测逻辑：

    // src/services/tools/toolExecution.ts
    export function buildSchemaNotSentHint(
      tool: Tool,
      messages: Message[],
      tools: readonly { name: string }[],
    ): string | null {
      if (!isToolSearchEnabledOptimistic()) return null
      if (!isToolSearchToolAvailable(tools)) return null
      if (!isDeferredTool(tool)) return null
      const discovered = extractDiscoveredToolNames(messages)
      if (discovered.has(tool.name)) return null
      return (
        `This tool's schema was not sent to the API... ` +
        `Load the tool first: call ${TOOL_SEARCH_TOOL_NAME} with query "select:${tool.name}"`
      )
    }

这段代码会追加到 Zod 验证错误信息之后，引导模型先调用 ToolSearch 加载 schema 再重试。

### 7.5.4 缓存与性能

ToolSearchTool 使用 memoized 的工具描述缓存，并在延迟工具集变化时失效：

    const getToolDescriptionMemoized = memoize(
      async (toolName: string, tools: Tools): Promise<string> => {
        const tool = findToolByName(tools, toolName)
        if (!tool) return ''
        return tool.prompt({ /* 最小化参数 */ })
      },
      (toolName: string) => toolName,
    )

    function maybeInvalidateCache(deferredTools: Tools): void {
      const currentKey = getDeferredToolsCacheKey(deferredTools)
      if (cachedDeferredToolNames !== currentKey) {
        getToolDescriptionMemoized.cache.clear?.()
        cachedDeferredToolNames = currentKey
      }
    }

### 7.5.5 延迟加载的工具分布

在当前代码库中，以下工具被标记为 `shouldDefer: true`：

工具 | 延迟原因
--- | ---
WebFetchTool | 非核心操作，使用频率低
TaskCreateTool | 仅在 TodoV2 启用时有效
TaskGetTool | 同上
TaskUpdateTool | 同上
TaskListTool | 同上
所有 MCP 工具 | 工作流特定，数量不可控

而以下工具通过 `alwaysLoad: true` 确保从不延迟（即使启用了 ToolSearch）：

- MCP 工具可通过 `_meta['anthropic/alwaysLoad']` 声明自己不应被延迟

    graph LR
        subgraph 初始加载
            A[BashTool<br/>完整 schema]
            B[FileReadTool<br/>完整 schema]
            C[FileEditTool<br/>完整 schema]
            D[GlobTool<br/>完整 schema]
            E[GrepTool<br/>完整 schema]
            F[ToolSearchTool<br/>完整 schema]
        end

        subgraph 延迟加载
            G[WebFetchTool<br/>仅名称]
            H[TaskCreateTool<br/>仅名称]
            I[MCP工具1<br/>仅名称]
            J[MCP工具2<br/>仅名称]
        end

        F -->|select:WebFetchTool| G
        F -->|keyword search| H
        F -->|select:mcp__...| I

        style F fill:#ff9,stroke:#333,stroke-width:2px
        style G fill:#ddd,stroke:#999
        style H fill:#ddd,stroke:#999
        style I fill:#ddd,stroke:#999
        style J fill:#ddd,stroke:#999

## 本章小结

Claude Code 的工具架构通过以下设计决策构建了一个安全、可扩展、高性能的工具系统：

1. 泛型接口 + 输入驱动属性 —— 同一工具根据不同输入表现不同的安全特征
2. buildTool 工厂 + 失败关闭默认值 —— 新工具自动获得保守的安全配置
3. ToolUseContext 依赖注入 —— 40+ 字段的上下文对象承载了工具执行的全部环境
4. 显式注册 + 条件编译 —— 工具列表在编译时确定，死代码被完全消除
5. ToolSearch 延迟加载 —— 按需加载 schema，在工具数量与 prompt 大小之间取得平衡

下一章将深入到具体工具的实现中，分析 BashTool 如何安全地执行 shell 命令、FileEditTool 如何保证编辑的原子性、以及 GrepTool 如何集成 Ripgrep 提供高速搜索。