跳转至

第 7 章:工具与安全 —— Agent 的手脚、权限和输出边界

1. 一个工具至少跨过四个边界

OpenCode 的 tool 不是简单的 (args) => string,它要跨过:

模型 ToolDefinition
  → Tool Registry materialization
  → 参数 Schema decode
  → Permission / Question gate
  → side effect(filesystem / shell / network / child agent)
  → bounded output + metadata
  → Session tool part / next provider turn

每个阶段都可能失败,而且失败必须能被模型、UI 和用户理解。

2. Tool Registry:按当前上下文物化工具

packages/opencode/src/tool/registry.tspackages/core/src/tool/registry.ts 负责把 built-in、plugin、MCP、code mode 等工具合并成当前 agent 可见的工具集。

Registry 不只是一个 map,它还处理:

  • tool 初始化和惰性加载;
  • permission 过滤;
  • model-specific tool 选择,例如 apply_patch 与 edit/write 的差异;
  • agent step limit 下的最后一步禁用工具;
  • plugin 对 tool.definition 的修改;
  • unknown / plugin tool 的稳定 ID;
  • code mode 对 MCP tool catalog 的描述。

因此 tools 是 provider turn 的投影,每一轮都可能因为 agent、model、permission 或 feature flag 不同而变化。

3. 参数验证:错误要回到模型

packages/opencode/src/tool/tool.ts 中的 InvalidArgumentsError 把 Schema decode 失败变成模型可理解的错误:哪个 tool、哪一项参数、怎样重写。

设计原则是:

  • 调用前 decode,不要让业务函数接受任意 unknown;
  • 验证错误是工具结果语义的一部分,不是整个 session 的未处理异常;
  • 结果统一截断,即使某个自定义工具忘记做 output limit,wrapper 仍能治理。

4. Permission 与 HTTP Authorization 不是一回事

机制 保护对象 典型问题
HTTP Authorization 谁能访问 Server API 这个客户端能否调用 endpoint?
Tool Permission 当前 agent 是否能执行某类动作 是否允许 shell / write / task
Question 需要用户一次性回答的互动 gate 用户选择允许、拒绝或修改参数?

Permission 规则会按 tool name / pattern 合并 agent ruleset、session ruleset 和用户选择。Permission.evaluate 的结果通常是 allow / ask / deny;ask 会通过 Event V2 或 Question API 等待用户响应。

5. Side effect 前后的状态机

stateDiagram-v2
    [*] --> Pending: persist tool call
    Pending --> Asking: permission = ask
    Asking --> Running: user approves
    Asking --> Error: user rejects
    Pending --> Running: permission = allow
    Running --> Completed: side effect succeeded
    Running --> Error: exception / abort / invalid result
    Completed --> OutputProjection: truncate + metadata
    Error --> OutputProjection: model-facing error
    OutputProjection --> [*]: persist tool part

这里的“persist pending before side effect”让崩溃、取消和 UI 显示都有明确落点。

6. Built-in 工具的领域分组

源码中可以按能力分成:

  • 文件:readwriteeditapply_patch
  • 搜索:globgrepwebsearchwebfetch
  • 进程:shell / PTY;
  • 编排:tasktodoquestionplan
  • 知识与能力:skilllsp、MCP tools;
  • 高级执行:code-mode,在受约束的 runtime 中调用 schema-described tools。

工具名是跨模型、Session、事件和 UI 的稳定 vocabulary。UI 不应该从 backend tool class 的名字推断显示逻辑,而应依赖 SDK wire name 和 metadata。

7. 大输出治理:完整性与窗口预算同时满足

工具输出处理有三个目标:

  1. 让模型收到可用的摘要 / 截断内容;
  2. 让用户能找到完整输出;
  3. 不让一次 git diff 或日志命令耗尽上下文。

Core 的 ToolOutputStore 和 OpenCode 的 truncate 组合出 bounded model projection;被截断时写出 outputPath 等 metadata。后续工具或 UI 可以继续读取文件,而不是强迫模型重复运行副作用命令。

8. Subagent / Task:工具系统里的递归边界

task 不是普通 shell wrapper。它会:

  • 选择一个非 primary agent;
  • 创建或关联子 Session;
  • 为子任务提供更窄的权限和 prompt;
  • 把子任务的状态和结果投影到父 Session;
  • 避免子 agent 误用父级所有能力。

这使 OpenCode 的“多 agent”仍然落在 Session / Permission / Tool 这些已有抽象上,而不是额外造一套隐式线程模型。

本章小结

工具系统的核心不是工具数量,而是把 schema、权限、side effect、输出预算和 durable projection 串成一个可恢复管道。对模型来说,工具是 ToolDefinition;对运行时来说,工具是带授权、观测、截断和事件的服务调用。

源码锚点