0-4. 学习目标与一句话

文件读写模块是 coding agent 真正改变工程的地方:把 read/edit/write 意图变成受权限保护、可诊断、可反馈的文件系统操作。来源:packages/opencode/src/tool/read.ts:200-260edit.ts:88-208write.ts:38-102

Java 类比

Read/Edit/Write 像 Application Service;AppFileSystem 像 FileRepository;ctx.ask 是审批拦截器;LSP 是 IDE/编译器诊断服务。

5. 最小源码路径

  1. tool/read.ts:29-39200-260
  2. tool/edit.ts:47-6588-208
  3. tool/write.ts:20-3038-102
  4. tool/external-directory.ts:16-45
  5. lsp/lsp.ts:346-379

6. 用户输入到 agent 行动的整体链路

model read/edit/write tool-call
  -> SessionTools.resolve execute
  -> ReadTool/EditTool/WriteTool.execute
  -> path resolve + external directory check
  -> ctx.ask(read/edit)
  -> fs read/write
  -> format.file
  -> File.Event.Edited / FileWatcher.Event.Updated
  -> lsp.touchFile + diagnostics
  -> ToolResult output

7. 核心源码逐段讲解

ReadTool 权限

if (!path.isAbsolute(filepath)) {
  filepath = path.resolve(instance.directory, filepath)
}
yield* assertExternalDirectoryEffect(ctx, filepath, { kind: stat?.type === "Directory" ? "directory" : "file" })
yield* ctx.ask({ permission: "read", patterns: [path.relative(instance.worktree, filepath)], always: ["*"] })

路径:packages/opencode/src/tool/read.ts:200-232

外部目录

if (containsPath(full, ins)) return
const glob = path.join(dir, "*").replaceAll("\\", "/")
yield* ctx.ask({ permission: "external_directory", patterns: [glob], always: [glob] })

路径:packages/opencode/src/tool/external-directory.ts:16-45

EditTool diff + ask + write

diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
yield* ctx.ask({ permission: "edit", patterns: [path.relative(instance.worktree, filePath)], metadata: { filepath: filePath, diff } })
yield* afs.writeWithDirs(filePath, Bom.join(contentNew, desiredBom))
if (yield* format.file(filePath)) {
  contentNew = yield* Bom.syncFile(afs, filePath, desiredBom)
}

路径:packages/opencode/src/tool/edit.ts:88-160

LSP 反馈

yield* lsp.touchFile(filePath, "document")
const diagnostics = yield* lsp.diagnostics()
const block = LSP.Diagnostic.report(filePath, diagnostics[normalizedFilePath] ?? [])
if (block) output += `\n\nLSP errors detected in this file, please fix:\n${block}`

路径:packages/opencode/src/tool/edit.ts:192-198

WriteTool 项目诊断

WriteTool 写入后还会报告其他文件的 LSP errors。来源:packages/opencode/src/tool/write.ts:74-90

8. 关键 TypeScript 语法复习

  • Schema.Struct 定义工具参数。
  • Effect.catchIf 恢复 stat not found。
  • object literal metadata:{ filepath, diff }
  • template literal 拼 diagnostics。
  • 平台分支处理 Windows 路径。

9-10. 架构思想与协作

  • Policy enforcement:外部目录 + read/edit 权限。
  • Domain Event:文件编辑后发布 File/FileWatcher 事件。
  • Feedback Loop:LSP diagnostics 进入 tool output。
  • Provider 不直接参与文件工具执行;它主要影响工具 schema 暴露。

11. mini agent 对应代码

async function editFile(args, ctx) {
  const filePath = resolveInsideProject(args.filePath, ctx.cwd)
  const oldContent = await fs.readFile(filePath, "utf8")
  const newContent = oldContent.replace(args.oldString, args.newString)
  const diff = makeDiff(oldContent, newContent)
  await ctx.ask({ permission: "edit", patterns: [relative(ctx.root, filePath)], metadata: { diff } })
  await fs.writeFile(filePath, newContent)
  return { output: await diagnostics(filePath) }
}

12. 费曼复述区

  1. ReadTool 执行前有哪些边界检查?
  2. EditTool 为什么要生成 diff 再 ask?
  3. LSP diagnostics 为什么要进入 tool output?

13-15. 练习与自测

  • 找到 ReadTool 参数。
  • 找到 EditTool 申请 edit 权限的代码。
  • 实现一个带 diff 和 ask 的 writeFile tool。
  • 追踪 ReadTool.execute -> assertExternalDirectoryEffect
  • 回答:如何防止 agent 修改项目外文件?

16. 下一步阅读建议

继续读 “Shell / 命令执行”,它和文件工具一样操作本地环境,但风险更高。