本篇是《深入 Claude Code 源码》系列的第 9 篇。我们将深入工具系统的核心设计:从 Tool 接口的核心方法,到 buildTool() 的 builder 模式,到 tools.ts 的注册表架构,再到 ToolSearch 的延迟加载机制,揭示一个生产级 AI Agent 如何管理 40+ 个内置工具和无限数量的 MCP 工具。
为什么工具系统是 Agent 的灵魂?
一个 AI Agent 与普通 chatbot 的本质区别在于:Agent 能执行动作。当模型决定读取一个文件、运行一条 Shell 命令、或搜索代码时,它依赖的就是工具系统。
Claude Code 拥有 40+ 个内置工具(BashTool、FileReadTool、GlobTool……)和通过 MCP 协议接入的无限数量外部工具。管理这样规模的工具集面临几个核心挑战:
- 接口一致性:每个工具需要统一的调用、验证、权限检查、UI 渲染协议
- 安全默认:工具涉及文件系统和 Shell 操作,默认行为必须 fail-closed
- 条件注册:不同构建版本、不同运行模式下可用的工具不同
- 规模可扩展:当工具数量超过模型上下文窗口的承载能力时,需要动态发现机制
本篇将回答这些问题,并从中提炼出可迁移到自己项目的设计模式。
1.1 接口定义
Tool.ts 是整个工具系统的类型基础,定义了完整的 Tool 接口。这个接口有 30+ 个方法/属性,下面展示按职责域分组的核心子集(完整接口还包括 interruptBehavior、isSearchOrReadCommand、shouldDefer/alwaysLoad、mcpInfo、strict、backfillObservableInput、preparePermissionMatcher、extractSearchText、renderToolUseTag、renderToolUseQueuedMessage、isResultTruncated 等方法,详见 Tool.ts:362-695):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| 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>
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> isReadOnly(input): boolean isDestructive?(input): boolean isConcurrencySafe(input): boolean isEnabled(): boolean
renderToolUseMessage(input, options): React.ReactNode renderToolResultMessage?(content, progressMessages, options): React.ReactNode renderToolUseProgressMessage?(progressMessages, options): React.ReactNode renderToolUseRejectedMessage?(input, options): React.ReactNode renderToolUseErrorMessage?(result, options): React.ReactNode renderGroupedToolUse?(toolUses, options): React.ReactNode | null
mapToolResultToToolResultBlockParam(content, toolUseID): ToolResultBlockParam
maxResultSizeChars: number userFacingName(input): string getPath?(input): string toAutoClassifierInput(input): unknown }
|
这个接口体现了一个重要的设计哲学:工具不仅仅是”执行逻辑”,它是一个完整的”微服务”,自带输入验证、权限控制、UI 渲染、结果序列化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| classDiagram class Tool { +name: string +aliases?: string[] +searchHint?: string +inputSchema: ZodType +outputSchema?: ZodType +call(args, context): ToolResult +validateInput?(input): ValidationResult +checkPermissions(input): PermissionResult +isReadOnly(input): boolean +isConcurrencySafe(input): boolean +isEnabled(): boolean +prompt(options): string +renderToolUseMessage(): ReactNode +renderToolResultMessage?(): ReactNode +mapToolResultToToolResultBlockParam(): ToolResultBlockParam +maxResultSizeChars: number }
class ToolDef { <<buildTool 输入>> isEnabled 可选 isConcurrencySafe 可选 isReadOnly 可选 checkPermissions 可选 toAutoClassifierInput 可选 userFacingName 可选 }
class TOOL_DEFAULTS { +isEnabled() → true +isConcurrencySafe() → false +isReadOnly() → false +isDestructive() → false +checkPermissions() → allow +toAutoClassifierInput() → '' +userFacingName() → '' }
ToolDef --> Tool : "buildTool() 补全默认值\nuserFacingName 补为 def.name" TOOL_DEFAULTS --> Tool : 提供安全默认
|
1.2 方法分组详解
安全相关方法(fail-closed 设计):
| 方法 |
作用 |
默认行为 |
isEnabled() |
工具是否在当前环境下可用 |
true |
isReadOnly() |
是否为只读操作 |
false(假设写入) |
isConcurrencySafe() |
是否可以并发执行 |
false(假设不安全) |
isDestructive() |
是否为不可逆操作 |
false |
validateInput() |
输入预校验(在权限检查之前) |
无(跳过) |
checkPermissions() |
权限检查 |
allow(交给通用权限系统) |
注意默认值的设计意图:isReadOnly 和 isConcurrencySafe 都默认 false,这是 fail-closed 原则 —— 如果工具作者忘了声明,系统会采用最保守的假设(假设会写入、假设不能并发)。
UI 渲染协议(1 个必需 + 5 个可选的 render 方法,以及 renderToolUseTag、renderToolUseQueuedMessage、isResultTruncated、extractSearchText 等辅助展示方法):
每个工具可以定制从「工具调用中」到「结果展示」的完整 UI 生命周期。其中 renderToolUseMessage 是必需的(展示工具调用意图),其余为可选:
1 2 3 4 5 6
| 模型发出 tool_use → renderToolUseMessage(展示工具调用意图) → renderToolUseProgressMessage(展示执行进度) → renderToolResultMessage(展示结果) → renderToolUseRejectedMessage(用户拒绝时) → renderToolUseErrorMessage(执行出错时) → renderGroupedToolUse(多个同类工具批量展示)
|
这套协议让 BashTool 可以展示命令输出和进度条,FileEditTool 可以展示 diff 视图,GlobTool 可以展示文件列表 —— 全部通过统一的接口,由 Ink React 组件渲染。
Tool 接口有 30+ 个方法,如果每个工具都要实现全部方法,开发体验会很差。更重要的是,忘记实现安全相关方法可能导致安全漏洞(比如忘记声明 isConcurrencySafe 应返回 true,会导致本可并发执行的只读工具串行执行,影响性能但安全;反过来如果默认 true 就危险了)。
buildTool() 解决了这两个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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) => '', }
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> { return { ...TOOL_DEFAULTS, userFacingName: () => def.name, ...def, } as BuiltTool<D> }
|
运行时逻辑只有一行:{ ...TOOL_DEFAULTS, userFacingName: () => def.name, ...def }。这是一个经典的 对象展开合并(spread merge)—— 先铺好默认值,然后用 () => def.name 覆盖 TOOL_DEFAULTS 中返回空字符串的 userFacingName,最后再用工具定义 def 覆盖(如果工具自己定义了 userFacingName,会覆盖这个默认的 def.name)。
2.2 类型层的精巧设计
buildTool() 真正复杂的部分不在运行时,而在 类型系统:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
type DefaultableToolKeys = | 'isEnabled' | 'isConcurrencySafe' | 'isReadOnly' | 'isDestructive' | 'checkPermissions' | 'toAutoClassifierInput' | 'userFacingName'
export type ToolDef<...> = Omit<Tool<...>, DefaultableToolKeys> & Partial<Pick<Tool<...>, DefaultableToolKeys>>
type BuiltTool<D> = Omit<D, DefaultableToolKeys> & { [K in DefaultableToolKeys]-?: K extends keyof D ? undefined extends D[K] ? ToolDefaults[K] : D[K] : ToolDefaults[K] }
|
关键在 BuiltTool<D> 类型:它精确地在类型层面模拟了运行时的 spread 语义。如果工具定义了 isReadOnly,返回类型中就用工具自己的实现类型;如果没定义,返回类型中就是默认值的类型。
来看一个中等复杂度的工具 —— GlobTool:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| export const GlobTool = buildTool({ name: GLOB_TOOL_NAME, searchHint: 'find files by name pattern or wildcard', maxResultSizeChars: 100_000,
async description() { return DESCRIPTION },
get inputSchema(): InputSchema { return inputSchema() }, get outputSchema(): OutputSchema { return outputSchema() },
isConcurrencySafe() { return true }, isReadOnly() { return true },
async validateInput({ path }): Promise<ValidationResult> { if (path) { const absolutePath = expandPath(path) if (absolutePath.startsWith('\\\\') || absolutePath.startsWith('//')) { return { result: true } } } return { result: true } },
async checkPermissions(input, context) { return checkReadPermissionForTool(GlobTool, input, ...) },
async call(input, { abortController, getAppState, globLimits }) { const { files, truncated } = await glob( input.pattern, GlobTool.getPath(input), { limit: globLimits?.maxResults ?? 100 }, abortController.signal, ) return { data: { filenames: files.map(toRelativePath), ... } } },
renderToolUseMessage, renderToolResultMessage, renderToolUseErrorMessage, } satisfies ToolDef<InputSchema, Output>)
|
几个值得注意的模式:
satisfies ToolDef<...>:TypeScript 4.9 的 satisfies 关键字确保对象结构符合 ToolDef,同时保留字面量类型(比 as 更安全)
- UI 分离:渲染逻辑在独立的
UI.tsx 文件中,工具定义文件专注于业务逻辑
- Schema 延迟求值:
get inputSchema() { return inputSchema() } — 为什么不直接赋值?
2.4 lazySchema:延迟 Zod Schema 构造
1 2 3 4 5
| export function lazySchema<T>(factory: () => T): () => T { let cached: T | undefined return () => (cached ??= factory()) }
|
这个 8 行的工具函数解决了一个实际问题:Zod schema 的构造在模块加载时就会执行,而 CLI 工具需要极快的启动速度。lazySchema 把 schema 构造推迟到第一次访问时,配合 get inputSchema() getter,实现了按需构造。
tools.ts 是所有内置工具的单一注册来源(single source of truth)。getAllBaseTools() 函数返回完整的工具列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| export function getAllBaseTools(): Tools { return [ AgentTool, TaskOutputTool, BashTool, ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]), ExitPlanModeV2Tool, FileReadTool, FileEditTool, FileWriteTool, NotebookEditTool, WebFetchTool, TodoWriteTool, WebSearchTool, ...(process.env.USER_TYPE === 'ant' ? [ConfigTool] : []), ...(SleepTool ? [SleepTool] : []), ...cronTools, ...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []), ...(process.env.NODE_ENV === 'test' ? [TestingPermissionTool] : []), ] }
|
3.2 三种条件注册机制
工具注册表使用三种不同的条件机制来控制工具可用性:
机制一:编译期 feature() + 条件 require()(DCE)
1 2 3 4 5
| const SleepTool = feature('PROACTIVE') || feature('KAIROS') ? require('./tools/SleepTool/SleepTool.js').SleepTool : null
|
当 feature('PROACTIVE') 编译为 false 时,整个 require() 分支被 Bun bundler 删除,不占用最终包体积。这是编译期 DCE。
机制二:process.env 运行时检查
1 2 3 4 5
| const REPLTool = process.env.USER_TYPE === 'ant' ? require('./tools/REPLTool/REPLTool.js').REPLTool : null
|
这些检查在运行时执行,用于区分内部版(ant)和外部版。
机制三:isEnabled() 运行时检查
1 2 3
| const isEnabled = allowedTools.map(_ => _.isEnabled()) return allowedTools.filter((_, i) => isEnabled[i])
|
每个工具的 isEnabled() 方法可以检查更复杂的运行时条件(GrowthBook feature flag、环境变量组合等)。
这三种机制形成了一个层级过滤漏斗:
1
| 编译期 DCE → 模块加载时环境变量 → 运行时 isEnabled() → 权限 deny rules
|
从注册表到模型实际可用的工具,经过多层过滤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| export const getTools = (permissionContext: ToolPermissionContext): Tools => { if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) { return filterToolsByDenyRules([BashTool, FileReadTool, FileEditTool], ...) }
const tools = getAllBaseTools().filter(tool => !specialTools.has(tool.name))
let allowedTools = filterToolsByDenyRules(tools, permissionContext)
if (isReplModeEnabled()) { }
return allowedTools.filter((_, i) => isEnabled[i]) }
export function assembleToolPool( permissionContext: ToolPermissionContext, mcpTools: Tools, ): Tools { const builtInTools = getTools(permissionContext) const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)
const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name) return uniqBy( [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)), 'name', ) }
|
assembleToolPool() 的排序设计值得关注:built-in 和 MCP 工具分别排序后再拼接,而不是混合排序。注释解释了原因 —— prompt cache 稳定性。API 服务端在 built-in 工具的最后一个位置设置了 cache breakpoint,如果 MCP 工具混入 built-in 区间,会导致所有下游 cache key 失效。
3.4 懒 require() 打破循环依赖
1 2 3 4 5 6
|
const getTeamCreateTool = () => require('./tools/TeamCreateTool/TeamCreateTool.js') .TeamCreateTool as typeof import('./tools/TeamCreateTool/TeamCreateTool.js').TeamCreateTool
|
函数包装 + as typeof import(...) 是处理循环依赖的标准模式:
- 函数包装
require() —— 延迟执行,避免模块加载时的循环
as typeof import(...) —— 保留完整类型信息,不丢失类型安全
四、工具执行编排:并发安全分区
当模型一次返回多个 tool_use 调用时,工具系统需要决定哪些可以并行、哪些必须串行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function partitionToolCalls( toolUseMessages: ToolUseBlock[], toolUseContext: ToolUseContext, ): Batch[] { return toolUseMessages.reduce((acc: Batch[], toolUse) => { const tool = findToolByName(toolUseContext.options.tools, toolUse.name) const parsedInput = tool?.inputSchema.safeParse(toolUse.input) const isConcurrencySafe = parsedInput?.success ? (() => { try { return Boolean(tool?.isConcurrencySafe(parsedInput.data)) } catch { return false } })() : false
if (isConcurrencySafe && acc[acc.length - 1]?.isConcurrencySafe) { acc[acc.length - 1]!.blocks.push(toolUse) } else { acc.push({ isConcurrencySafe, blocks: [toolUse] }) } return acc }, []) }
|
算法逻辑清晰:
- 先用 Zod 的
safeParse 验证输入
- 调用
isConcurrencySafe() 判断能否并发
- 连续的安全工具合并为一个并发 batch
- 不安全的工具各自独立为一个串行 batch
执行时,并发 batch 内的工具通过 all() 工具并行执行(最大并发度默认 10),串行 batch 逐个执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| export async function* runTools(...): AsyncGenerator<MessageUpdate, void> { for (const { isConcurrencySafe, blocks } of partitionToolCalls(...)) { if (isConcurrencySafe) { for await (const update of runToolsConcurrently(blocks, ...)) { yield { message: update.message, newContext: currentContext } } } else { for await (const update of runToolsSerially(blocks, ...)) { yield { message: update.message, newContext: currentContext } } } } }
|
4.2 ToolUseContext:工具执行的运行时上下文
每次工具调用都接收一个 ToolUseContext 对象,它是工具执行的完整运行时环境:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| export type ToolUseContext = { options: { tools: Tools commands: Command[] mcpClients: MCPServerConnection[] mainLoopModel: string thinkingConfig: ThinkingConfig isNonInteractiveSession: boolean } abortController: AbortController readFileState: FileStateCache getAppState(): AppState setAppState(f): void messages: Message[] setInProgressToolUseIDs: (f) => void }
|
ToolUseContext 是第 3 篇讲过的”运行时上下文容器”。它最关键的设计是 getAppState/setAppState 对 —— subagent 的 setAppState 可以是 no-op(参见 createSubagentContext()),实现了 Agent 隔离。
当 MCP 工具数量很多时(几十甚至上百个),把所有工具定义都塞进 system prompt 会消耗大量 token。ToolSearch 机制解决了这个问题。
工具是否应该延迟加载,由 isDeferredTool() 决定:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| export function isDeferredTool(tool: Tool): boolean { if (tool.alwaysLoad === true) return false
if (tool.isMcp === true) return true
if (tool.name === TOOL_SEARCH_TOOL_NAME) return false
if (feature('FORK_SUBAGENT') && tool.name === AGENT_TOOL_NAME) { if (m.isForkSubagentEnabled()) return false }
return tool.shouldDefer === true }
|
延迟的工具以名称列表形式告知模型,但具体的呈现方式取决于运行模式。源码中存在两条路径(tools/ToolSearchTool/prompt.ts:31-42):
- Delta 模式(
isDeferredToolsDeltaEnabled() 为 true,即内部用户或 tengu_glacier_2xr feature flag 开启时):deferred 工具通过增量 attachment 出现在 <system-reminder> 消息中,只通知新增/移除的工具
- Legacy 模式:deferred 工具出现在
<available-deferred-tools> 消息块中,每次列出全量
无论哪种模式,模型都看不到 deferred 工具的参数 schema 和详细描述,只能看到工具名称:
1 2 3 4 5 6
| <available-deferred-tools> mcp__slack__slack_send_message mcp__slack__slack_list_conversations mcp__github__create_pull_request ... </available-deferred-tools>
|
当模型需要使用某个 deferred 工具时,它先调用 ToolSearchTool:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| export const ToolSearchTool = buildTool({ name: TOOL_SEARCH_TOOL_NAME, isEnabled() { return isToolSearchEnabledOptimistic() }, isConcurrencySafe() { return true }, isReadOnly() { return true },
async call(input, { options: { tools }, getAppState }) { const { query, max_results = 5 } = input const deferredTools = tools.filter(isDeferredTool)
const selectMatch = query.match(/^select:(.+)$/i) if (selectMatch) { }
const matches = await searchToolsWithKeywords(query, deferredTools, tools, max_results) return buildSearchResult(matches, query, deferredTools.length) },
mapToolResultToToolResultBlockParam(content, toolUseID) { return { type: 'tool_result', tool_use_id: toolUseID, content: content.matches.map(name => ({ type: 'tool_reference', tool_name: name, })), } }, })
|
搜索算法支持两种模式:
select:Name — 精确选择,按工具名查找(支持逗号分隔多选)
- 关键词搜索 — 按 tool name 分词 + searchHint + description 综合评分
关键词搜索的评分逻辑(searchToolsWithKeywords):
searchHint 是每个工具的简短能力描述(3-10 个词),专门用于帮助模型通过关键词找到工具:
1 2 3 4 5
| searchHint: 'find files by name pattern or wildcard'
searchHint: 'read files, images, PDFs, notebooks'
|
5.3 启用策略:从乐观检查到最终决策
ToolSearch 的启用分为两层:乐观检查(isToolSearchEnabledOptimistic())和最终决策(isToolSearchEnabled())。
乐观检查用于决定是否将 ToolSearchTool 注册到工具列表中。它首先检查 getToolSearchMode():
但即使 mode 不是 standard,乐观检查还有一个重要的代理网关守卫(utils/toolSearch.ts:299-311):当 ENABLE_TOOL_SEARCH 未显式设置、且 ANTHROPIC_BASE_URL 指向非 Anthropic 第一方地址时(即用户通过第三方代理访问 API),ToolSearch 会被禁用。这是因为 tool_reference 是一个 beta content type,第三方代理通常不支持。用户如果确认代理支持 tool_reference,可以显式设置 ENABLE_TOOL_SEARCH=true 来覆盖。
最终决策 isToolSearchEnabled() 在实际 API 调用前执行,额外检查模型兼容性(Haiku 不支持 tool_reference)和 tst-auto 模式的阈值判定。
tst-auto 模式特别有趣:它计算所有 deferred 工具定义占上下文窗口的比例,超过阈值(默认 10%)才启用延迟加载:
1 2 3 4 5 6
| function getAutoToolSearchTokenThreshold(model: string): number { const contextWindow = getContextWindowForModel(model, betas) const percentage = getAutoToolSearchPercentage() / 100 return Math.floor(contextWindow * percentage) }
|
六、工具的目录组织模式
每个工具遵循统一的目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| tools/ ├── GlobTool/ │ ├── GlobTool.ts # 工具定义(buildTool + call 逻辑) │ ├── prompt.ts # 工具名常量 + description 文本 │ └── UI.tsx # render* 方法的实现 ├── BashTool/ │ ├── BashTool.tsx # 主文件(最复杂的单个工具) │ ├── prompt.ts # description + 超时配置 │ ├── UI.tsx # 渲染逻辑 │ ├── bashPermissions.ts # 权限匹配逻辑 │ ├── bashSecurity.ts # AST 安全分析 │ ├── commandSemantics.ts # 命令语义分类 │ ├── shouldUseSandbox.ts # 沙箱决策 │ └── ...(18 个文件) ├── FileReadTool/ │ ├── FileReadTool.ts │ ├── prompt.ts │ ├── UI.tsx │ ├── limits.ts # 读取限制配置 │ └── imageProcessor.ts └── ...(40+ 个工具目录)
|
这种组织方式的核心原则:
- 工具名和 description 作为常量 放在
prompt.ts 中,其他模块(如 constants/tools.ts)可以不加载整个工具就引用工具名
- UI 渲染逻辑独立 放在
UI.tsx,保持工具定义的纯粹性
- 复杂工具可自由拆分 子模块(BashTool 有 18 个文件),不需要遵循固定模板
七、可迁移的设计模式
模式 1:Builder + 安全默认
用 builder 函数包装接口实现,提供 fail-closed 的默认值。工具作者只需关注自己的特殊逻辑,不可能忘记处理安全性。
1 2 3 4 5 6 7 8 9
| const DEFAULTS = { isReadOnly: () => false, canRunConcurrently: () => false, }
function buildPlugin(def) { return { ...DEFAULTS, ...def } }
|
适用场景:任何插件/中间件系统,特别是涉及安全敏感操作时。
模式 2:分层条件注册
将条件注册分为编译期(DCE)、模块加载期(env var)、运行时(isEnabled())三层,形成过滤漏斗。编译期条件可以彻底删除代码路径,运行时条件可以响应动态配置。
1
| 编译期 feature() → 模块加载时 env check → 运行时 isEnabled() → 权限 deny rules
|
适用场景:需要从同一份代码构建多个版本,且不同版本有不同功能集的项目。
模式 3:并发安全分区
通过 isConcurrencySafe 标记将批量操作分区为可并发组和必须串行组。默认 false 确保安全,显式声明 true 才启用并发。
适用场景:任何需要批量执行异构任务的系统(如构建工具、数据管道、API 网关)。
下一篇预告
第 10 篇:BashTool 深度剖析 — 最复杂的单个工具
我们将深入 BashTool 的 18 个文件,看看一个 Shell 命令执行工具如何处理命令语义分析、AST 安全检查、沙箱执行、输出截断、权限匹配等复杂问题。BashTool 是工具系统设计模式的极致体现。
全部内容请关注 https://github.com/luyao618/Claude-Code-Source-Study (求一颗免费的小星星)