跳转至

51. coding-agent 内建工具:schema、执行器与输出边界

这个模块干什么

core/tools/ 定义 7 个内建工具:readbasheditwritegrepfindls。 【packages/coding-agent/src/core/tools/index.ts:81-84】

每个工具先以 ToolDefinition 描述 schema、executor、prompt metadata 和 renderer,再包装成 AgentTool 交给底层 Agent。 【packages/coding-agent/src/core/tools/tool-definition-wrapper.ts:4-19】

AgentSession 把内建、extension 和 SDK custom tools 合成同名覆盖的 registry,再把选中的实例写入 agent.state.tools。 【packages/coding-agent/src/core/agent-session.ts:2454-2545】

共享层负责 cwd 路径解析、同文件写入串行化、UTF-8 安全截断、完整输出临时文件和进程取消。 【packages/coding-agent/src/core/tools/path-utils.ts:40-50】 【packages/coding-agent/src/core/tools/file-mutation-queue.ts:28-60】 【packages/coding-agent/src/core/tools/truncate.ts:1-13】

核心文件速查

文件 一句话职责 必读优先级
core/tools/index.ts 列出全部工具名,提供单个、coding、read-only 和 all factories。 【packages/coding-agent/src/core/tools/index.ts:81-196】 ⭐⭐⭐
core/tools/tool-definition-wrapper.ts ToolDefinition 与底层 AgentTool 之间做薄适配。 【packages/coding-agent/src/core/tools/tool-definition-wrapper.ts:4-47】 ⭐⭐⭐
core/tools/read.ts 读取文本或图片,支持 offset/limit 和 head truncation。 【packages/coding-agent/src/core/tools/read.ts:20-63】 【packages/coding-agent/src/core/tools/read.ts:203-350】 ⭐⭐⭐
core/tools/bash.ts 执行 shell、流式回传、注入 session env,并保留截断后的完整输出。 【packages/coding-agent/src/core/tools/bash.ts:40-74】 【packages/coding-agent/src/core/tools/bash.ts:316-505】 ⭐⭐⭐
core/tools/edit.ts 对单文件执行一个或多个精确、非重叠替换并返回 diff/patch。 【packages/coding-agent/src/core/tools/edit.ts:33-68】 【packages/coding-agent/src/core/tools/edit.ts:287-362】 ⭐⭐⭐
core/tools/write.ts 创建父目录并新建或完整覆盖文件。 【packages/coding-agent/src/core/tools/write.ts:14-40】 【packages/coding-agent/src/core/tools/write.ts:181-226】 ⭐⭐⭐
core/tools/grep.ts 用 ripgrep 搜内容,控制 match、byte 和单行长度。 【packages/coding-agent/src/core/tools/grep.ts:24-45】 【packages/coding-agent/src/core/tools/grep.ts:123-369】 ⭐⭐
core/tools/find.ts 用 fd 或自定义 glob 按 pattern 找路径。 【packages/coding-agent/src/core/tools/find.ts:20-57】 【packages/coding-agent/src/core/tools/find.ts:109-358】 ⭐⭐
core/tools/ls.ts 列目录、排序、标记子目录,并限制 entry 数。 【packages/coding-agent/src/core/tools/ls.ts:14-50】 【packages/coding-agent/src/core/tools/ls.ts:95-209】 ⭐⭐
core/tools/truncate.ts 实现 head、tail 和单行截断的统一规则。 【packages/coding-agent/src/core/tools/truncate.ts:1-45】 ⭐⭐⭐
core/tools/output-accumulator.ts 为流式 bash 保留有界 tail,必要时把完整 raw output 写临时文件。 【packages/coding-agent/src/core/tools/output-accumulator.ts:28-62】 ⭐⭐⭐
core/tools/file-mutation-queue.ts 同一真实路径的 edit/write 串行,不同文件仍可并行。 【packages/coding-agent/src/core/tools/file-mutation-queue.ts:28-60】 ⭐⭐
core/bash-executor.ts 执行用户 ! bash 路径,独立于 LLM 可调用的 bash tool executor。 【packages/coding-agent/src/core/bash-executor.ts:1-16】 ⭐⭐
core/exec.ts 给 extension pi.exec() 提供无 shell 的 command+args 执行。 【packages/coding-agent/src/core/exec.ts:1-39】
core/output-guard.ts 把普通 stdout 改道 stderr,同时保留带 backpressure 的 raw stdout 通道。 【packages/coding-agent/src/core/output-guard.ts:45-108】

内建工具总表

工具名 文件 一句话作用 危险/权限注意点
read core/tools/read.ts 读取文本或图片;文本从指定行开始并限制行数。 【packages/coding-agent/src/core/tools/read.ts:20-24】 可读取进程用户能读的绝对或相对路径;默认 operations 直接调用本地 fs。 【packages/coding-agent/src/core/tools/read.ts:43-56】
bash core/tools/bash.ts 在 session cwd 中执行 shell command,合并 stdout/stderr 并流式更新。 【packages/coding-agent/src/core/tools/bash.ts:40-43】 【packages/coding-agent/src/core/tools/bash.ts:316-358】 直接启动本地 shell,可访问网络、文件和凭据;abort/timeout 会杀进程树,但不是权限沙箱。 【packages/coding-agent/src/core/tools/bash.ts:82-145】
edit core/tools/edit.ts edits[] 对原文件中的唯一文本块做精确替换。 【packages/coding-agent/src/core/tools/edit.ts:33-53】 要求目标文件可读写;成功后直接覆盖原文件。 【packages/coding-agent/src/core/tools/edit.ts:74-87】 【packages/coding-agent/src/core/tools/edit.ts:323-360】
write core/tools/write.ts 创建父目录并完整写入文件,不存在则新建,存在则覆盖。 【packages/coding-agent/src/core/tools/write.ts:14-17】 【packages/coding-agent/src/core/tools/write.ts:181-225】 不做“仅新文件”保护;路径指向已有文件时会整体覆盖。 【packages/coding-agent/src/core/tools/write.ts:187-193】
grep core/tools/grep.ts 用 regex 或 literal 搜索文件内容,返回路径、行号和可选上下文。 【packages/coding-agent/src/core/tools/grep.ts:24-36】 默认会自动确保/下载 rg;搜索 --hidden 且尊重 ignore 规则,结果仍可能包含敏感文本。 【packages/coding-agent/src/core/tools/grep.ts:170-219】
find core/tools/find.ts 用 glob pattern 搜路径,默认 backend 是 fd。 【packages/coding-agent/src/core/tools/find.ts:20-26】 【packages/coding-agent/src/core/tools/find.ts:213-255】 默认会自动确保/下载 fd--hidden 会包含 dotfiles,但过滤规则仍由 fd/gitignore 决定。 【packages/coding-agent/src/core/tools/find.ts:213-241】
ls core/tools/ls.ts 列出一层目录内容,按不区分大小写排序,目录加 /。 【packages/coding-agent/src/core/tools/ls.ts:14-17】 【packages/coding-agent/src/core/tools/ls.ts:149-171】 可枚举任意进程可访问目录;单个无法 stat 的 entry 会被跳过。 【packages/coding-agent/src/core/tools/ls.ts:127-169】

精读

1. 先读 tools/index.ts:工具集合与默认激活集合不是一回事

ToolName 是严格联合:

type ToolName = "read" | "bash" | "edit" | "write" | "grep" | "find" | "ls"

allToolNamescreateAllToolDefinitions() 都覆盖这 7 个名字。 【packages/coding-agent/src/core/tools/index.ts:81-84】 【packages/coding-agent/src/core/tools/index.ts:156-166】

createCodingToolDefinitions() 只返回 readbasheditwrite。 【packages/coding-agent/src/core/tools/index.ts:138-145】

createReadOnlyToolDefinitions() 返回 readgrepfindls。 【packages/coding-agent/src/core/tools/index.ts:147-154】

因此“仓库内建 7 个工具”不等于“默认给模型 7 个工具”;默认 active names 仍是四个 coding tools。 【packages/coding-agent/src/core/agent-session.ts:2591-2598】

每个 factory 同时有 definition 版和 AgentTool 版,便于 extension/SDK 复用同一 executor。 【packages/coding-agent/src/core/tools/index.ts:96-136】

2. ToolDefinition 如何进入 Agent loop

wrapToolDefinition() 复制 name、label、description、parameters、sampling、prepareArguments、executionMode,并把 execute 参数原样转交 definition。 【packages/coding-agent/src/core/tools/tool-definition-wrapper.ts:4-19】

AgentSession._buildRuntime() 读取 image resize、shell prefix、shell path settings,然后调用 createAllToolDefinitions(cwd, options)。 【packages/coding-agent/src/core/agent-session.ts:2547-2569】

内建 definitions 先进入 _baseToolDefinitions;extensions 和 SDK custom definitions 后加入同一个 definition registry。 【packages/coding-agent/src/core/agent-session.ts:2462-2487】

Map 的后写覆盖前写,所以同名 extension/custom tool 会替换内建 definition 的 registry 项。 【packages/coding-agent/src/core/agent-session.ts:2470-2486】

随后内建与 extension definitions 都通过 extension wrapper 转成 AgentTool,合并成 _toolRegistry。 【packages/coding-agent/src/core/agent-session.ts:2504-2520】

active names 经过 allowlist/denylist、初始 names 和 extension 自动启用规则后,交给 setActiveToolsByName()。 【packages/coding-agent/src/core/agent-session.ts:2522-2545】

setActiveToolsByName() 从 registry 取实例写入 agent.state.tools,同时按 active tools 重建 system prompt。 【packages/coding-agent/src/core/agent-session.ts:920-941】

多轮工具循环之间,next-turn refresh 再复制最新 agent.state.tools 到下一轮 context。 【packages/coding-agent/src/core/agent-session.ts:520-540】

这条链可以压缩为:

createAllToolDefinitions(cwd)
  -> definition registry
  -> wrapRegisteredTools(...)
  -> AgentTool registry
  -> setActiveToolsByName(names)
  -> agent.state.tools
  -> next agent-loop turn context.tools

3. read.ts:text 从头截,image 走附件

schema 是 { path: string, offset?: number, limit?: number },offset 从 1 开始。 【packages/coding-agent/src/core/tools/read.ts:20-26】

默认 operations 是 fs.readFile、可读性 access(R_OK) 和图片 MIME 检测;调用方可替换为远端 backend。 【packages/coding-agent/src/core/tools/read.ts:39-63】

路径先经过 resolveReadPathAsync():相对 cwd、支持绝对路径和 ~,还会去掉常见的 @ 前缀。 【packages/coding-agent/src/core/tools/path-utils.ts:40-50】 【packages/coding-agent/src/core/tools/path-utils.ts:86-117】

macOS screenshot 名称还会尝试窄空格 AM/PM、NFD Unicode 和 curly quote 变体。 【packages/coding-agent/src/core/tools/path-utils.ts:5-19】 【packages/coding-agent/src/core/tools/path-utils.ts:93-115】

检测到图片时读取 Buffer,交给 processImage();成功结果包含一段 text note 和 image block。 【packages/coding-agent/src/core/tools/read.ts:236-263】

当前 model 不支持 image 时,tool result 仍会加说明,提醒该附件会从请求中省略。 【packages/coding-agent/src/core/tools/read.ts:87-92】 【packages/coding-agent/src/core/tools/read.ts:246-262】

文本路径先按 offset/limit 切片,再调用 truncateHead(),所以 read 优先保留文件前部。 【packages/coding-agent/src/core/tools/read.ts:264-289】

超限时结果附带下一次 offset;如果第一行本身超过 50KB,则不返回半行,而是给出 bash fallback。 【packages/coding-agent/src/core/tools/read.ts:287-305】

4. bash.ts:一个 tool definition 内含 shell backend、stream 和 tail retention

schema 是 { command: string, timeout?: number };timeout 单位是秒,必须为有限正数且不能超过 int32 timer 上限。 【packages/coding-agent/src/core/tools/bash.ts:24-45】

BashOperations.exec() 是可插拔边界,接收 command、cwd、onData、signal、timeout 和 env。 【packages/coding-agent/src/core/tools/bash.ts:52-74】

默认 local backend 先检查 cwd 是否存在,再根据 shell config 选择 argv 或 stdin 传 command。 【packages/coding-agent/src/core/tools/bash.ts:82-107】

子进程在非 Windows 平台 detached 启动;stdout/stderr 都接到同一个 onData,abort/timeout 会调用 killProcessTree()。 【packages/coding-agent/src/core/tools/bash.ts:96-145】

每次 spawn 前先从环境删除旧的 PI_SESSION_IDPI_SESSION_FILEPI_PROVIDERPI_MODELPI_REASONING_LEVEL,再按当前 ExtensionContext 重新注入。 【packages/coding-agent/src/core/tools/bash.ts:158-184】

这避免父进程残留的 session metadata 泄漏成错误值;也可以通过 exposeSessionEnvironment: false 关闭注入。 【packages/coding-agent/src/core/tools/bash.ts:186-197】

executor 用 OutputAccumulator 收集流;partial update 最多每 100ms 发一次。 【packages/coding-agent/src/core/tools/bash.ts:199-200】 【packages/coding-agent/src/core/tools/bash.ts:340-401】

最终只保留最后 2000 行或 50KB;如果截断,details 带完整临时文件路径和具体范围。 【packages/coding-agent/src/core/tools/bash.ts:404-421】

非零 exit code 会 throw,并把已经捕获的输出拼进错误文本;abort 和 timeout 也走带输出的错误。 【packages/coding-agent/src/core/tools/bash.ts:426-454】

5. edit.ts:所有 replacement 都对原文件匹配

schema 是 { path, edits: Array<{ oldText, newText }> },并明确要求 oldText 唯一且 edits 互不重叠。 【packages/coding-agent/src/core/tools/edit.ts:33-53】

prepareEditArguments() 兼容两种模型偏差:把 JSON string 的 edits 解析成数组,也把旧式 top-level oldText/newText 合入 edits。 【packages/coding-agent/src/core/tools/edit.ts:94-118】

空 edits 在 executor 前被拒绝。 【packages/coding-agent/src/core/tools/edit.ts:120-125】

真正写入放在 withFileMutationQueue(absolutePath, fn) 内,避免同一文件的并发 edit/write 互相覆盖。 【packages/coding-agent/src/core/tools/edit.ts:308-318】

实现检查读写权限,读取原文件,去 BOM、检测原换行风格、统一成 LF 后计算所有 edits。 【packages/coding-agent/src/core/tools/edit.ts:321-346】

写回时恢复 BOM 和原换行风格,结果 details 同时提供展示 diff、标准 unified patch 和 firstChangedLine。 【packages/coding-agent/src/core/tools/edit.ts:346-360】

abort 不在事件 listener 中提前 reject,而是在每个 await 后检查;这样 mutation queue 不会在底层文件操作仍未结束时释放。 【packages/coding-agent/src/core/tools/edit.ts:312-319】

6. write.ts:完整覆盖,和 edit 共用同文件队列

schema 只有 { path, content }。 【packages/coding-agent/src/core/tools/write.ts:14-19】

默认 operations 是递归 mkdir 和 UTF-8 writeFile。 【packages/coding-agent/src/core/tools/write.ts:21-40】

executor 解析绝对路径、创建父目录、完整写入 content,并在每个 await 前后检查 abort。 【packages/coding-agent/src/core/tools/write.ts:194-225】

它同样用 withFileMutationQueue(),所以同一文件的 write 与 edit 会按注册顺序执行。 【packages/coding-agent/src/core/tools/write.ts:201-225】 【packages/coding-agent/src/core/tools/file-mutation-queue.ts:32-60】

队列 key 优先取 realpath;文件尚不存在时退回 resolved path。 【packages/coding-agent/src/core/tools/file-mutation-queue.ts:7-25】

7. grep.ts:match 数、总 bytes、单行长度是三层限制

schema 支持 pattern、path、glob、ignoreCase、literal、context、limit,默认 limit 100。 【packages/coding-agent/src/core/tools/grep.ts:24-40】

默认实现调用 ensureTool("rg", true),缺失时尝试取得 ripgrep;失败才返回不可用错误。 【packages/coding-agent/src/core/tools/grep.ts:170-176】

rg 参数使用 JSON 输出、行号、hidden、无颜色,并按 options 添加 ignore-case、fixed-strings、glob。 【packages/coding-agent/src/core/tools/grep.ts:215-221】

流式读取 match event,到达 effective limit 就 kill child;code 1 被视为“无匹配”而不是执行错误。 【packages/coding-agent/src/core/tools/grep.ts:221-307】

context=0 时直接用 rg 返回的行文本;需要上下文时再读取原文件拼 before/after lines。 【packages/coding-agent/src/core/tools/grep.ts:250-268】 【packages/coding-agent/src/core/tools/grep.ts:316-331】

每条展示行最多 500 字符,最终整体再按 50KB 做 head truncation。 【packages/coding-agent/src/core/tools/grep.ts:15-22】 【packages/coding-agent/src/core/tools/grep.ts:333-356】

8. find.ts:默认 fd,自定义 backend 走同一结果协议

schema 是 { pattern, path?, limit? },默认最多 1000 个结果。 【packages/coding-agent/src/core/tools/find.ts:20-30】

自定义 operations 可以直接提供 glob();结果会相对 search root 规范化,再做 result/byte 限制。 【packages/coding-agent/src/core/tools/find.ts:37-57】 【packages/coding-agent/src/core/tools/find.ts:148-210】

默认路径确保 fd 可用,并在非 git repo 时加 --no-require-git。 【packages/coding-agent/src/core/tools/find.ts:213-241】

pattern 含 / 时启用 --full-path,必要时前置 **/,让 src/**/*.spec.ts 能匹配绝对候选路径。 【packages/coding-agent/src/core/tools/find.ts:243-253】

输出按 search root 相对化并转 /,到达数量上限或 50KB 时给出可操作 notice。 【packages/coding-agent/src/core/tools/find.ts:307-346】

9. ls.ts:一层目录枚举,不递归

schema 是 { path?, limit? },默认 path 为 .、limit 为 500。 【packages/coding-agent/src/core/tools/ls.ts:14-21】

executor 先检查存在与 directory 类型,再读 entries。 【packages/coding-agent/src/core/tools/ls.ts:122-147】

排序不区分大小写;每个 entry 再 stat,一旦是目录就加 /,无法 stat 的项直接跳过。 【packages/coding-agent/src/core/tools/ls.ts:149-171】

entry count 已经控制行数,所以最终 truncation 只额外关注 byte limit。 【packages/coding-agent/src/core/tools/ls.ts:175-202】

10. truncate.tsoutput-accumulator.ts:为什么 read 看头、bash 看尾

共享默认限制是 2000 行和 50KB;两个限制谁先命中就以谁为准。 【packages/coding-agent/src/core/tools/truncate.ts:1-13】

truncateHead() 只返回完整行;第一行超过 byte limit 时 content 为空并标记 firstLineExceedsLimit。 【packages/coding-agent/src/core/tools/truncate.ts:71-119】

truncateTail() 从后往前保留内容;唯一允许的半行情况是原输出最后一行本身超过 byte limit。 【packages/coding-agent/src/core/tools/truncate.ts:162-241】

UTF-8 tail 截断会移动到合法字符边界,避免从多字节字符中间切开。 【packages/coding-agent/src/core/tools/truncate.ts:243-262】

OutputAccumulator 用 streaming TextDecoder 统计 decoded bytes/lines,只在内存保留有限 tail。 【packages/coding-agent/src/core/tools/output-accumulator.ts:28-62】 【packages/coding-agent/src/core/tools/output-accumulator.ts:148-177】

一旦 raw bytes、decoded bytes 或 lines 超限,它创建临时文件,并先把此前保留的 raw chunks 补写进去。 【packages/coding-agent/src/core/tools/output-accumulator.ts:64-89】 【packages/coding-agent/src/core/tools/output-accumulator.ts:205-221】

snapshot 返回显示 tail、完整 truncation metadata 和可选 fullOutputPath。 【packages/coding-agent/src/core/tools/output-accumulator.ts:91-119】

11. 三个容易混淆的“命令输出”模块

LLM 的 bash tool 使用 tools/bash.ts 的 executor 和 OutputAccumulator。 【packages/coding-agent/src/core/tools/bash.ts:316-458】

用户 !command 使用 bash-executor.ts:它 strip ANSI、清理 binary output、保留 rolling tail,并把结果记录成 BashExecutionMessage。 【packages/coding-agent/src/core/bash-executor.ts:46-129】 【packages/coding-agent/src/core/agent-session.ts:2764-2825】

bash-executor.ts 的 temp file threshold 从 50KB 开始,最终显示仍用 truncateTail()。 【packages/coding-agent/src/core/bash-executor.ts:56-99】 【packages/coding-agent/src/core/bash-executor.ts:107-129】

exec.ts 是 extensions/custom tools 共用的 command helper;spawnshell: false,分别收 stdout/stderr。 【packages/coding-agent/src/core/exec.ts:1-6】 【packages/coding-agent/src/core/exec.ts:30-45】

execCommand() 支持 AbortSignal 和毫秒 timeout,先 SIGTERM,5 秒后必要时 SIGKILL。 【packages/coding-agent/src/core/exec.ts:47-79】

output-guard.ts 不做模型工具截断;它接管进程 stdout,把普通 process.stdout.write 改发 stderr,并保留 raw stdout 序列化写通道。 【packages/coding-agent/src/core/output-guard.ts:45-70】 【packages/coding-agent/src/core/output-guard.ts:85-108】

raw stdout 遇到 ENOBUFS/EAGAIN/EWOULDBLOCK 会每 10ms retry,并用 Promise tail 保持写入顺序。 【packages/coding-agent/src/core/output-guard.ts:9-43】 【packages/coding-agent/src/core/output-guard.ts:85-102】

数据流

下面追踪模型调用 edit 的完整路径:

AgentSession._buildRuntime()
  -> createAllToolDefinitions(cwd)
       -> createEditToolDefinition(cwd)
  -> _baseToolDefinitions
  -> definition registry + extension/custom definitions
  -> wrapRegisteredTools(...)
  -> _toolRegistry
  -> setActiveToolsByName(...)
  -> agent.state.tools

agent-loop receives toolCall(name="edit", arguments={ path, edits })
  -> AgentTool.prepareArguments()
       -> parse stringified edits / legacy oldText-newText
  -> schema validation
  -> AgentSession.beforeToolCall extension hook
  -> AgentTool.execute()
       -> resolveToCwd(path, cwd)
       -> withFileMutationQueue(path)
       -> access + read
       -> strip BOM + normalize LF
       -> apply all exact replacements against original content
       -> restore line endings + write
       -> return text + diff + unified patch
  -> AgentSession.afterToolCall extension hook
  -> tool_execution_end
  -> toolResult enters next LLM turn

definitions 到 active AgentTool registry 的组装发生在 AgentSession。 【packages/coding-agent/src/core/agent-session.ts:2454-2599】

definition 到 AgentTool.execute() 的薄适配发生在 wrapper。 【packages/coding-agent/src/core/tools/tool-definition-wrapper.ts:4-19】

edit 参数兼容、校验、文件队列和写回分别位于同一 definition 内。 【packages/coding-agent/src/core/tools/edit.ts:94-125】 【packages/coding-agent/src/core/tools/edit.ts:287-362】

活跃工具改变后,下一 agent turn 会刷新 tools snapshot 和 system prompt。 【packages/coding-agent/src/core/agent-session.ts:520-540】 【packages/coding-agent/src/core/agent-session.ts:920-941】

自测

  1. 为什么 allToolNames 有 7 个,而默认 active tools 只有 4 个? 【packages/coding-agent/src/core/tools/index.ts:81-84】 【packages/coding-agent/src/core/agent-session.ts:2591-2598】

  2. readbash 同样受 2000 行/50KB 限制,为什么前者保留头部、后者保留尾部? 【packages/coding-agent/src/core/tools/read.ts:287-305】 【packages/coding-agent/src/core/tools/bash.ts:404-421】

  3. 两个并发 edit/write 指向同一文件时,哪一层防止它们交叉覆盖? 【packages/coding-agent/src/core/tools/file-mutation-queue.ts:28-60】

  4. bash tool、用户 !command 和 extension pi.exec() 分别使用哪三个执行模块? 【packages/coding-agent/src/core/tools/bash.ts:316-458】 【packages/coding-agent/src/core/bash-executor.ts:46-156】 【packages/coding-agent/src/core/exec.ts:30-107】

  5. extension 用同名工具替换内建工具后,为什么 agent loop 无需知道工具来源? 【packages/coding-agent/src/core/agent-session.ts:2470-2520】 【packages/coding-agent/src/core/tools/tool-definition-wrapper.ts:4-19】