在之前的文章中,我们详细介绍了 OpenCode 的 13 个内置工具——read、write、bash、grep、glob 等。这些工具覆盖了日常开发中的绝大多数场景,但每个项目都有自己的特殊需求:你可能需要查询内部 API、操作数据库、调用特定的微服务,或者执行一些领域特定的计算。
OpenCode 的自定义工具(Custom Tools)系统正是为此而生。它允许你用自己熟悉的编程语言编写函数,并让 AI 模型在对话中像调用内置工具一样调用它们。本文将带你从零开始,全面掌握自定义工具的创建、配置和实战技巧。
自定义工具本质上是注册到 OpenCode 中的函数。当你在对话中下达指令时,AI 模型可以根据需要选择合适的工具来执行任务。与内置工具不同的是,自定义工具由你编写逻辑,可以访问你的内部系统、数据库、第三方 API,甚至执行复杂的业务规则。
核心特性:
自定义工具需要 @opencode-ai/plugin 包:
npm install -D @opencode-ai/plugin # 或者 bun add -d @opencode-ai/plugin
OpenCode 运行时会自动编译加载 .opencode/tools/ 目录下的 TypeScript 文件,无需额外配置。
工具文件放置在 .opencode/tools/ 目录中,文件名即工具名。创建一个获取当前时间的工具:
// .opencode/tools/current-time.ts
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "获取当前系统时间",
args: {
format: tool.schema
.string()
.describe("时间格式,如 'YYYY-MM-DD HH:mm:ss'")
.optional(),
},
async execute(args) {
const now = new Date()
if (args.format === "YYYY-MM-DD HH:mm:ss") {
return now.toISOString().replace("T", " ").substring(0, 19)
}
return now.toString()
},
})
保存后在 OpenCode 中间就可以直接问"现在几点了?",模型会自动调用此工具。
参数定义使用 tool.schema,底层是 Zod 验证库:
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "查询用户信息",
args: {
userId: tool.schema.number().describe("用户 ID"),
includeDeleted: tool.schema
.boolean()
.describe("是否包含已删除用户")
.default(false),
tags: tool.schema
.array(tool.schema.string())
.describe("过滤标签")
.optional(),
},
async execute(args) {
return `查询用户 ${args.userId}`
},
})
也可以直接导入 Zod:
import { z } from "zod"
export default {
description: "计算 BMI 指数",
args: {
weight: z.number().positive().describe("体重(kg)"),
height: z.number().positive().describe("身高(m)"),
},
async execute(args) {
const bmi = args.weight / (args.height * args.height)
return `BMI: ${bmi.toFixed(2)}`
},
}
一个文件可导出多个工具,名称格式为 <文件名>_<导出名>:
// .opencode/tools/string.ts
import { tool } from "@opencode-ai/plugin"
export const reverse = tool({
description: "反转字符串",
args: {
text: tool.schema.string().describe("要反转的文本"),
},
async execute(args) {
return args.text.split("").reverse().join("")
},
})
export const count = tool({
description: "统计字符数",
args: {
text: tool.schema.string().describe("要统计的文本"),
},
async execute(args) {
return `字符数: ${args.text.length}`
},
})
这会创建 string_reverse 和 string_count 两个工具。
execute 的第二个参数提供会话上下文:
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "获取项目信息",
args: {},
async execute(args, context) {
return [
`会话 ID: ${context.sessionID}`,
`消息 ID: ${context.messageID}`,
`AI 身份: ${context.agent}`,
`工作目录: ${context.directory}`,
`Git 根目录: ${context.worktree}`,
].join("\n")
},
})
| 字段 | 类型 | 说明 |
|------|------|------|
| agent | string | 当前 AI 代理身份 |
| sessionID | string | 会话唯一 ID |
| messageID | string | 消息唯一 ID |
| directory | string | 工作目录 |
| worktree | string | Git 工作树根目录 |
工具定义必须用 TypeScript,执行逻辑可以是任何语言。用 Python 写一个 JSON 格式化工:
# .opencode/tools/format_json.py import json, sys data = json.loads(sys.stdin.read()) print(json.dumps(data, indent=2, ensure_ascii=False))
// .opencode/tools/json-formatter.ts
import { tool } from "@opencode-ai/plugin"
import path from "path"
export default tool({
description: "格式化 JSON 字符串",
args: {
json: tool.schema.string().describe("原始 JSON 字符串"),
},
async execute(args, context) {
const script = path.join(context.worktree, ".opencode/tools/format_json.py")
const result = await Bun.$`python3 ${script}`.text(args.json)
return result.trim()
},
})
同样的方式支持 Go、Ruby、Rust 或 Shell 脚本。
自定义工具与内置工具同名时,自定义优先。例如限制 bash 命令范围:
// .opencode/tools/bash.ts
import { tool } from "@opencode-ai/plugin"
const ALLOWED = ["ls", "cat", "pwd", "git status", "npm test"]
export default tool({
description: "受限 bash 执行",
args: {
command: tool.schema.string().describe("要执行的命令"),
},
async execute(args) {
const allowed = ALLOWED.some((c) => args.command.startsWith(c))
if (!allowed) {
return `错误: 命令不在允许列表中`
}
return `执行: ${args.command}`
},
})
如果只是禁用内置工具,用 Permissions 更合适:
{
"permissions": {
"deny": ["bash", "write"]
}
}
连接 SQLite 数据库执行查询:
// .opencode/tools/db-query.ts
import { tool } from "@opencode-ai/plugin"
import path from "path"
export default tool({
description: "查询项目 SQLite 数据库",
args: {
sql: tool.schema.string().describe("SQL 查询语句"),
},
async execute(args, context) {
const dbPath = path.join(context.worktree, "database.sqlite")
const result = await Bun.$`sqlite3 ${dbPath} ${args.sql}`.text()
return result.trim() || "查询完成"
},
})
现在可以直接让 AI"查询最近注册的 5 个用户",它会自动生成 SQL 并调用此工具。
清晰的描述:description 是 AI 理解工具用途的关键,写清楚做什么和何时用
详细的参数说明:每个参数用 .describe() 说明含义和格式
返回可读文本:返回值必须是字符串,结构化数据可用 JSON 格式
全局工具复用:多项目公用工具放 ~/.config/opencode/tools/
错误处理:做好 try/catch,返回友好错误信息
性能注意:外部脚本每次调用会启动新进程,频繁调用考虑缓存或长效服务
OpenCode 自定义工具系统为 AI 编程助手提供了无限扩展可能。从简单的辅助函数到复杂的业务集成,都能用熟悉的语言和工具链实现。结合内置工具和 MCP 服务器,三者互补:内置工具处理通用文件操作,MCP 连接外部服务,自定义工具填补项目和团队的特定需求。
下一步,尝试为你的项目编写第一个自定义工具——从一个小功能开始,逐步构建属于你自己的工具库。