内置工具详解
概述
本章深入分析几个代表性工具的实现,展示工具系统的设计模式。
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] (单个)
}下一步
- Ink 框架概述 — 终端 UI 框架