面试官:OpenCode Tool 工具系统了解吗?

本系列文章皆基于开源 Vibecoding 工具 Opencode 源码进行详细拆解。

源码链接:github.com/anomalyco/o...


写在前面

上次咱们聊完了 Session 会话机制,这次咱们来聊聊 Tool 工具系统

有没有想过这个问题:AI 是怎么"动手"写代码的?它光在那边"想了想",就能把文件给我改了?它是通过什么做到的?

不卖关子,今天咱们就来看看,AI 背后的"手"和"眼"到底是咋工作的!


一、什么是 Tool?

Tool 这个东西太好理解了!AI 光动嘴不动手可不行,得有工具才能干活!

在 OpenCode 里,Tool = 能力定义 + 参数校验 + 执行逻辑。

当 AI 说"让我读取这个文件"时,实际上是调用了 read Tool。

它跟咱们人一样:

  • 眼睛负责看 → read 读取文件
  • 手负责写 → write 写入文件
  • 腿负责跑 → bash 执行命令

Tool 解决了什么问题?

问题 Tool 方案
AI 只回答不动手 提供可执行工具
参数不安全 Zod schema 校验
输出太长 自动截断机制
权限失控 权限检查钩子

你说这些重要吗?简直不要太重要!没有 Tool,AI 就是个"嘴强王者",光说不练!


二、核心概念:Tool 四要素

看图!这四个东西就是 Tool 的核心:

graph TB subgraph Tool D[description 描述] P[parameters 参数] E[execute 执行] C[context 上下文] end D -->|输入格式| P P -->|校验| E E -->|权限检查| C C -->|执行| R[Result 结果]

2.1 Tool.define 定义模式

typescript 复制代码
// 源码位置: packages/opencode/src/tool/tool.ts:49
export function define<Parameters extends z.ZodType, Result extends Metadata>(
  id: string,                              // 工具名称
  init: (ctx?) => Promise<{               // 初始化函数
    description: string                    // 工具描述
    parameters: Parameters                 // 参数 schema
    execute(args, ctx): Promise<Result>   // 执行函数
  }>
): Info

2.2 Tool.Info 结构

typescript 复制代码
// 源码位置: packages/opencode/src/tool/tool.ts:28
export interface Info<Parameters, M extends Metadata> {
  id: string                    // 工具 ID
  init: (ctx?) => Promise<{
    description: string         // 描述,供 LLM 理解
    parameters: Parameters     // Zod 参数校验
    execute(args, ctx): {       // 执行函数
      title: string            // 标题
      metadata: M              // 元数据
      output: string          // 输出内容
      attachments?: File[]    // 附件
    }
  }>
}

2.3 Context 上下文

typescript 复制代码
// 源码位置: packages/opencode/src/tool/tool.ts:17
export type Context<M extends Metadata> = {
  sessionID: SessionID         // 会话 ID
  messageID: MessageID         // 消息 ID
  agent: string               // 当前 Agent
  abort: AbortSignal          // 中止信号
  callID?: string            // 调用 ID
  messages: MessageV2[]      // 消息历史
  metadata(input): void       // 设置元数据
  ask(input): Promise<void>  // 请求用户确认
}

三、内置 Tool 详解

3.1 工具分类

分类 工具 用途
文件操作 read, write, edit, glob 读写文件、搜索文件
内容搜索 grep, codesearch, websearch 搜索文件内容,网络
命令执行 bash 执行 Shell 命令
网络请求 webfetch 获取网页内容
任务管理 task, todo 子 Agent、待办事项
其他 skill, apply_patch, lsp 技能、补丁、LSP

3.2 核心工具详解

read - 读取文件

这个最常用!AI 想看代码,就靠它!

typescript 复制代码
// 源码位置: packages/opencode/src/tool/read.ts:21
export const ReadTool = Tool.define("read", {
  description: DESCRIPTION,
  parameters: z.object({
    filePath: z.string().describe("The absolute path to the file or directory to read"),
    offset: z.coerce.number().describe("The line number to start reading from (1-indexed)").optional(),
    limit: z.coerce.number().describe("The maximum number of lines to read (defaults to 2000)").optional(),
  }),
  async execute(params, ctx) {
    // 1. 权限检查
    await ctx.ask({
      permission: "read",
      patterns: [filepath],
    })
    // 2. 读取文件
    const content = await fs.readFile(filepath, "utf-8")
    // 3. 返回结果
    return { title, metadata: {}, output: content }
  }
})

关键特性

  • 支持 offset/limit 分页读取,文件太长不怕
  • 目录会列出文件列表
  • 文件不存在时提供建议,真贴心
write - 写入文件

AI 写文件就靠它!

typescript 复制代码
// 源码位置: packages/opencode/src/tool/write.ts
export const WriteTool = Tool.define("write", {
  parameters: z.object({
    filePath: z.string().describe("The absolute path to the file to write"),
    content: z.string().describe("The content to write to the file"),
  }),
  async execute(params, ctx) {
    // 权限检查
    await ctx.ask({ permission: "write", patterns: [filepath] })
    // 写入文件
    await fs.writeFile(filepath, params.content)
    return { title, metadata: {}, output: "File written successfully" }
  }
})
edit - 编辑文件

这个可太重要了!AI 改代码全靠它!

typescript 复制代码
// 源码位置: packages/opencode/src/tool/edit.ts:36
export const EditTool = Tool.define("edit", {
  parameters: z.object({
    filePath: z.string().describe("The absolute path to the file to modify"),
    oldString: z.string().describe("The text to replace"),
    newString: z.string().describe("The text to replace it with"),
    replaceAll: z.boolean().optional().describe("Replace all occurrences"),
  }),
  async execute(params, ctx) {
    // 使用 diff 算法计算变更
    const diff = createTwoFilesPatch(...)
    // 权限检查
    await ctx.ask({ permission: "edit", patterns: [filepath] })
    // 执行编辑
    await Filesystem.write(filePath, newContent)
    return { title, metadata: { diff }, output: "Edit applied successfully" }
  }
})

关键特性

  • 支持 replaceAll 批量替换
  • 使用 diff 算法精确计算变更
  • 失败时提供修正建议
bash - 执行命令

这个就是 AI 的"腿",能让 AI 执行各种命令!

typescript 复制代码
// 源码位置: packages/opencode/src/tool/bash.ts:55
export const BashTool = Tool.define("bash", async () => {
  return {
    description: DESCRIPTION,
    parameters: z.object({
      command: z.string().describe("The command to execute"),
      timeout: z.number().describe("Optional timeout in milliseconds").optional(),
      workdir: z.string().describe("The working directory").optional(),
      description: z.string().describe("Command description in 5-10 words"),
    }),
    async execute(params, ctx) {
      // 执行命令
      const proc = spawn(params.command, [], { cwd: params.workdir })
      // 捕获输出
      const output = await text(proc.stdout)
      return { title, metadata: {}, output }
    }
  }
})

关键特性

  • 支持 timeout 超时控制,命令卡死也不怕
  • 支持 workdir 指定工作目录
  • 自动解析命令描述
grep - 搜索内容

AI 想在代码库里找东西,就用这个!

typescript 复制代码
// 源码位置: packages/opencode/src/tool/grep.ts:15
export const GrepTool = Tool.define("grep", {
  parameters: z.object({
    pattern: z.string().describe("The regex pattern to search for"),
    path: z.string().optional().describe("The directory to search in"),
    include: z.string().optional().describe("File pattern to include"),
  }),
  async execute(params, ctx) {
    // 使用 ripgrep 搜索,这玩意儿快!
    const proc = Process.spawn(["rg", "-nH", "--hidden", ...])
    const output = await text(proc.stdout)
    return { title, metadata: {}, output }
  }
})
glob - 搜索文件

想找文件?用这个!

typescript 复制代码
// 源码位置: packages/opencode/src/tool/glob.ts
export const GlobTool = Tool.define("glob", {
  parameters: z.object({
    pattern: z.string().describe("The glob pattern to match files"),
    path: z.string().optional().describe("The directory to search in"),
  }),
  async execute(params, ctx) {
    // 使用 glob 库搜索
    const files = await Glob.scan(params.pattern, { cwd: params.path })
    return { title, metadata: {}, output: files.join("\n") }
  }
})
task - 调用子 Agent

这个可太猛了!AI 能让自己"分身"!

typescript 复制代码
// 源码位置: packages/opencode/src/tool/task.ts:28
export const TaskTool = Tool.define("task", async (ctx) => {
  const agents = await Agent.list().then((x) => 
    x.filter((a) => a.mode !== "primary")  // 只能选 subagent
  )
  
  return {
    description: DESCRIPTION.replace("{agents}", ...),
    parameters: z.object({
      description: z.string().describe("A short description of the task"),
      prompt: z.string().describe("The task for the agent to perform"),
      subagent_type: z.string().describe("The type of agent to use"),
      task_id: z.string().optional().describe("Resume a previous task"),
    }),
    async execute(params, ctx) {
      const agent = await Agent.get(params.subagent_type)
      const session = await Session.create({ parentID: ctx.sessionID })
      // 执行子任务
      return { title, metadata: {}, output: result }
    }
  }
})

四、工具注册机制

4.1 ToolRegistry

工具是怎么注册的呢?看这里!

typescript 复制代码
// 源码位置: packages/opencode/src/tool/registry.ts:34
export namespace ToolRegistry {
  // 工具列表
  async function all(): Promise<Tool.Info[]> {
    return [
      InvalidTool,
      QuestionTool,
      BashTool,
      ReadTool,
      GlobTool,
      GrepTool,
      EditTool,
      WriteTool,
      TaskTool,
      WebFetchTool,
      // ... 更多工具
      ...custom,  // 自定义工具
    ]
  }
  
  // 获取可用工具(根据模型和 Agent 过滤)
  async function tools(model, agent?) {
    const tools = await all()
    // 过滤逻辑
    return tools.filter(t => {
      // codesearch/websearch 只对特定用户开放
      if (t.id === "codesearch") return model.providerID === "opencode"
      // GPT 模型使用 apply_patch
      if (t.id === "apply_patch") return model.modelID.includes("gpt-")
      return true
    }).map(async t => {
      const tool = await t.init({ agent })
      return { id: t.id, ...tool }
    })
  }
}

4.2 注册流程图

flowchart TB subgraph 初始化 A[启动应用] --> B[ToolRegistry.state] B --> C[加载内置工具] C --> D[加载自定义工具目录] D --> E[加载插件工具] end subgraph 获取工具 F[Session 请求工具] --> G[ToolRegistry.tools] G --> H{模型过滤} H -->|opencode| I[包含 codesearch] H -->|其他| J[排除 codesearch] I --> K[权限过滤] J --> K K --> L[返回可用工具] end

4.3 自定义工具

也可以自己写工具!

perl 复制代码
my-project/
  └── tool/
      └── mytool.ts    # 自动加载
typescript 复制代码
// tool/mytool.ts
export default {
  description: "My custom tool",
  args: {
    input: z.string(),
  },
  execute: async (args, ctx) => {
    return "Result: " + args.input
  }
}

五、权限检查机制

这个太重要了!AI 不能乱动咱们的东西,得有权限控制!

5.1 工具级别权限

每个工具执行前都要检查权限:

typescript 复制代码
// 源码位置: packages/opencode/src/tool/read.ts:45
async execute(params, ctx) {
  // 请求权限
  await ctx.ask({
    permission: "read",           // 工具名
    patterns: [filepath],        // 文件路径
    always: ["*"],              // 记住此操作
    metadata: {},
  })
  // 权限通过后执行
  const content = await fs.readFile(filepath)
  return { title, metadata: {}, output: content }
}

5.2 权限 action

Action 行为
allow 直接执行,不询问
ask 询问用户确认
deny 拒绝执行

5.3 Agent 权限配置示例

typescript 复制代码
// Agent 的 permission 配置
{
  permission: [
    { permission: "read", pattern: "*", action: "allow" },
    { permission: "read", pattern: "*.env", action: "ask" },
    { permission: "write", pattern: "src/*", action: "allow" },
    { permission: "bash", pattern: "rm *", action: "deny" },
  ]
}

六、输出截断机制

这个太关键了!AI 执行个命令输出几十万行,那还得了?

6.1 Truncate 模块

typescript 复制代码
// 源码位置: packages/opencode/src/tool/truncation.ts:52
export async function output(text, options = {}, agent?): Promise<Result> {
  const maxLines = options.maxLines ?? 2000
  const maxBytes = options.maxBytes ?? 50KB
  
  if (lines.length <= maxLines && totalBytes <= maxBytes) {
    return { content: text, truncated: false }
  }
  
  // 超过限制,写入临时文件
  const filepath = path.join(DIR, ToolID.ascending())
  await Filesystem.write(filepath, text)
  
  // 返回截断内容 + 临时文件路径
  return { 
    content: `${preview}\n\n...${removed} lines truncated...\nFull output saved to: ${filepath}`,
    truncated: true, 
    outputPath: filepath 
  }
}

6.2 截断策略

情况 处理
输出小于 2000 行且小于 50KB 不截断
输出超过限制 截断并保存到临时文件
Agent 有 task 权限 提示用 Task 工具处理
其他 提示用 Grep 或 Read 处理

七、Tool 调用流程

完整流程看这里!

sequenceDiagram participant LLM participant Session participant ToolRegistry participant Tool participant Permission participant Filesystem LLM->>Session: 需要调用工具 Session->>ToolRegistry: 获取可用工具列表 ToolRegistry-->>Session: 返回工具定义 Session->>LLM: 发送工具列表 LLM->>Session: 选择调用 read 工具 Session->>Tool: 初始化工具 Tool->>Permission: 检查权限 Permission-->>Tool: 权限通过 Tool->>Filesystem: 读取文件 Filesystem-->>Tool: 文件内容 Tool->>Tool: 截断处理 Tool-->>Session: 返回结果 Session-->>LLM: 工具执行结果

八、用于构建自己的 AI 项目

8.1 核心架构

想自己写个 AI 工具系统?学起来!

typescript 复制代码
// 1. 定义工具类型
interface Tool<M = any> {
  id: string
  description: string
  parameters: z.ZodType
  execute(args: any, ctx: Context): Promise<{
    title: string
    metadata: M
    output: string
  }>
}

// 2. 定义工具注册表
class ToolRegistry {
  private tools: Map<string, Tool> = new Map()
  
  register(tool: Tool) {
    this.tools.set(tool.id, tool)
  }
  
  async getTools(agent: Agent): Promise<Tool[]> {
    return [...this.tools.values()].filter(tool => 
      agent.canUse(tool.id)
    )
  }
}

// 3. 工具执行器
async function executeTool(tool: Tool, args: any, ctx: Context) {
  // 1. 权限检查
  const permission = await checkPermission(ctx.agent, tool.id, args)
  if (permission === "deny") throw new Error("Permission denied")
  if (permission === "ask") await userConfirm()
  
  // 2. 参数校验
  tool.parameters.parse(args)
  
  // 3. 执行
  const result = await tool.execute(args, ctx)
  
  // 4. 截断处理
  return truncate(result.output)
}

8.2 常用工具模式

typescript 复制代码
// 文件读取工具
const ReadTool = Tool.define("read", {
  parameters: z.object({
    path: z.string()
  }),
  async execute({ path }, ctx) {
    const content = await fs.readFile(path, "utf-8")
    return { title: path, metadata: {}, output: content }
  }
})

// 命令执行工具
const BashTool = Tool.define("bash", {
  parameters: z.object({
    command: z.string(),
    timeout: z.number().optional()
  }),
  async execute({ command, timeout }, ctx) {
    const proc = spawn(command, { timeout })
    const output = await readStream(proc.stdout)
    return { title: command, metadata: {}, output }
  }
})

8.3 设计要点

要点 说明
参数校验 使用 Zod 确保输入安全
权限钩子 每个工具都要有 ctx.ask()
输出截断 防止 token 爆炸
错误处理 提供有用的错误信息
超时控制 防止命令卡死

总结

核心要点

  1. Tool 四要素:description + parameters + execute + context
  2. 内置工具:read、write、edit、bash、grep、glob、task
  3. 权限检查:allow、ask、deny 三种级别
  4. 输出截断:防止 token 爆炸
  5. 注册机制:内置 + 自定义 + 插件

一句话总结

Tool = 描述 + 参数校验 + 权限检查 + 执行逻辑 + 输出截断,是 AI 与现实世界交互的桥梁。


往期好文推荐

面试官:OpenCode Session 会话机制了解吗?

面试官:说说 Cookie 和 Token 的区别?

Vue3 原理解析之响应系统的实现


写在最后

看到这里,不得不说一句:Tool 这玩意儿真是太重要了!

理解了这个,你才能真正搞懂 AI 编程工具是怎么"动手"干活的!

如果文章对您有帮助麻烦亲点赞、收藏 + 关注和博主一起成长哟!!❤️❤️❤️

有问题欢迎评论区聊聊!


相关源码

文件 作用
packages/opencode/src/tool/tool.ts 工具基础定义
packages/opencode/src/tool/registry.ts 工具注册表
packages/opencode/src/tool/truncation.ts 输出截断
packages/opencode/src/tool/bash.ts 命令执行
packages/opencode/src/tool/read.ts 文件读取
packages/opencode/src/tool/write.ts 文件写入
packages/opencode/src/tool/edit.ts 文件编辑
packages/opencode/src/tool/grep.ts 内容搜索
packages/opencode/src/tool/glob.ts 文件搜索
packages/opencode/src/tool/task.ts 子 Agent 调用
相关推荐
NikoAI编程1 小时前
Claude 2026 新功能全景:从语音编程到远程协作
ai编程·claude
天若有情6731 小时前
【原创发布】typechecker:一款轻量级 JS 模板化类型检查工具
开发语言·javascript·npm·ecmascript·类型检查·typechecker
A_nanda2 小时前
vue实现走马灯显示文字效果
前端·javascript·vue.js
chaors2 小时前
Langchain入门到精通0x07:基于Web网页的RAG实战
人工智能·langchain·ai编程
大强同学2 小时前
Obsidian 视觉化技能包
人工智能·ai编程
chaors2 小时前
Langchain入门到精通0x08:摘要链(load_summarize_chain)
人工智能·langchain·ai编程
晴栀ay2 小时前
一文详解JS中的执行顺序——事件循环(宏任务、微任务)
前端·javascript·面试
0vvv02 小时前
JavaScript-1
javascript
张元清2 小时前
React 19 Hooks:新特性及高效使用指南
前端·javascript·面试