本系列文章皆基于开源 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 的核心:
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 注册流程图
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 调用流程
完整流程看这里!
八、用于构建自己的 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 爆炸 |
| 错误处理 | 提供有用的错误信息 |
| 超时控制 | 防止命令卡死 |
总结
核心要点
- Tool 四要素:description + parameters + execute + context
- 内置工具:read、write、edit、bash、grep、glob、task
- 权限检查:allow、ask、deny 三种级别
- 输出截断:防止 token 爆炸
- 注册机制:内置 + 自定义 + 插件
一句话总结
Tool = 描述 + 参数校验 + 权限检查 + 执行逻辑 + 输出截断,是 AI 与现实世界交互的桥梁。
往期好文推荐
写在最后
看到这里,不得不说一句: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 调用 |