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

Tool 调用系统是 OpenCode 的行动层:模型提出 tool call,runtime 完成参数校验、权限检查、执行、截断、附件补 ID、状态回写。来源:packages/opencode/src/tool/tool.ts:16-45packages/opencode/src/session/tools.ts:24-116

Java 类比

Tool.Def 像 Strategy;ToolRegistry 像工具注册表;SessionTools.resolve 像 adapter;ctx.ask 像安全拦截器;插件工具像 SPI。

5. 最小源码路径

  1. tool/tool.ts:16-45:上下文、结果、定义。
  2. tool/tool.ts:79-130:参数校验和截断。
  3. tool/registry.ts:203-275:自定义/插件/内置工具。
  4. tool/registry.ts:322-367:按模型筛选。
  5. session/tools.ts:24-116:包装成 AI SDK tool。
  6. session/tools.ts:118-205:MCP tool 接入。

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

runLoop
  -> SessionTools.resolve
  -> ToolRegistry.tools
  -> ProviderTransform.schema
  -> AI SDK tool({ execute })
  -> model tool-call
  -> Tool.Context with ask/metadata
  -> item.execute
  -> processor.completeToolCall

7. 核心源码逐段讲解

Tool.Context

export type Context = {
  sessionID: SessionID
  messageID: MessageID
  agent: string
  abort: AbortSignal
  metadata(input: { title?: string; metadata?: M }): Effect.Effect<void>
  ask(input: Omit<Permission.Request, "id" | "sessionID" | "tool">): Effect.Effect<void>
}

路径:packages/opencode/src/tool/tool.ts:16-26

Tool.Def

export interface Def<Parameters extends Schema.Decoder<unknown>, M extends Metadata = Metadata> {
  id: string
  description: string
  parameters: Parameters
  execute(args: Schema.Schema.Type<Parameters>, ctx: Context): Effect.Effect<ExecuteResult<M>>
}

路径:packages/opencode/src/tool/tool.ts:35-45

wrap 校验和截断

const decoded = yield* decode(args)
const result = yield* execute(decoded as Schema.Schema.Type<Parameters>, ctx)
const truncated = yield* truncate.output(result.output, {}, agent)
return { ...result, output: truncated.content, metadata: { ...result.metadata, truncated: truncated.truncated } }

路径:packages/opencode/src/tool/tool.ts:79-130

内置工具列表

shell/read/glob/grep/edit/write/task/fetch/todo/search/skill/patch/lsp/plan 在 registry 中初始化。来源:packages/opencode/src/tool/registry.ts:229-275

包装为 AI SDK tool

tools[item.id] = tool({
  description: item.description,
  inputSchema: jsonSchema(schema),
  execute(args, options) {
    const ctx = context(args, options)
    const result = yield* item.execute(args, ctx)
    return { ...result, attachments: result.attachments?.map(addIds) }
  },
})

路径:packages/opencode/src/session/tools.ts:75-116

8. 关键 TypeScript 语法复习

  • 泛型接口:Def<Parameters extends Schema.Decoder<unknown>>
  • 条件类型:InferParameters<T>
  • Omit:工具 ask 不让调用方传 session/tool id。
  • Record<string, AITool>:工具表。
  • dynamic import:加载自定义工具文件。

9-10. 架构思想与协作

  • Strategy + Registry + Adapter。
  • plugin hook 在定义阶段和执行阶段都可介入。
  • ProviderTransform 负责按模型调整 schema。
  • Session/Processor 提供 messageID、metadata 和 completeToolCall。

11. mini agent 对应代码

function resolveTools(registry, ctxBase) {
  const tools = {}
  for (const item of registry) {
    tools[item.name] = {
      description: item.description,
      inputSchema: item.schema,
      async execute(args, options) {
        const ctx = makeToolContext(ctxBase, options)
        await ctx.ask({ permission: item.name, patterns: ["*"] })
        return item.execute(args, ctx)
      },
    }
  }
  return tools
}

12. 费曼复述区

  1. ToolRegistry 和 SessionTools.resolve 的职责差异是什么?
  2. 为什么工具参数必须运行时 decode?
  3. 为什么工具上下文要有 askmetadata

13-15. 练习与自测

  • 找到 Tool.Context 并列出字段。
  • 解释 patch/edit/write 的模型过滤规则。
  • 实现一个 echo tool。
  • 追踪 SessionTools.resolveToolRegistry.tools 再到 Tool.init(read)
  • 回答:错误参数、超长输出、外部目录分别由哪一层处理?

16. 下一步阅读建议

继续读 “文件读写与代码修改”,看 Tool 系统如何真实改变工程文件。