用过 Cursor、Claude Code 这些 AI 编程工具的朋友都知道,它们内置了读文件、写代码、跑命令这些基本能力。但项目一旦有定制需求——比如查数据库、调内部 API、跑特定脚本——通用工具就不够用了。
OpenCode 的自定义工具(Custom Tools)就是干这个的:你写一个简单的 TypeScript 文件,就能给 AI 装上专属技能,想让它干什么都行。
近期用 Open Code 偏多一些,用着用着就会产生各种需求,就此萌生了折腾的想法,然后就有了这篇文章以此记录下自己的学习过程。
🤔 自定义工具是什么
自定义工具就是你写的函数,LLM 在对话中可以直接调用。它们跟 OpenCode 自带的内置工具(read、write、bash)平起平坐,AI 会自己判断什么时候该用。
关键是:工具定义用 TypeScript/JavaScript 写,但背后可以调用任何语言——Python、Shell、Node,随你。
📍 放哪里
工具文件两个位置二选一:
- 项目级:
.opencode/tools/目录下,只对当前项目生效 - 全局级:
~/.config/opencode/tools/,所有项目都能用
文件名就是工具名。比如 .opencode/tools/database.ts 会创建一个叫 database 的工具。
✏️ 怎么写
最简单的写法是用 tool() 辅助函数,类型安全、参数校验自动搞定:
// .opencode/tools/database.ts
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "查询项目数据库",
args: {
query: tool.schema.string().describe("要执行的 SQL 查询"),
},
async execute(args) {
// 这里写你的逻辑
return `已执行查询: ${args.query}`
},
})
写好以后,你在对话里跟 AI 说"查一下数据库里最新的订单",AI 就会自动调用这个工具,传 SQL 进去。
📦 一个文件多个工具
一个文件可以导出多个工具,命名规则是 文件名_导出名:
// .opencode/tools/math.ts
import { tool } from "@opencode-ai/plugin"
export const add = tool({
description: "两数相加",
args: {
a: tool.schema.number().describe("第一个数"),
b: tool.schema.number().describe("第二个数"),
},
async execute(args) {
return args.a + args.b
},
})
export const multiply = tool({
description: "两数相乘",
args: {
a: tool.schema.number().describe("第一个数"),
b: tool.schema.number().describe("第二个数"),
},
async execute(args) {
return args.a * args.b
},
})
这会生成两个工具:math_add 和 math_multiply。
🔄 覆盖内置工具
自定义工具的优先级高于内置工具。如果你写一个叫 bash.ts 的文件,它就会替代 OpenCode 自带的 bash 工具:
// .opencode/tools/bash.ts — 替换内置 bash
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "受限的 bash 执行",
args: {
command: tool.schema.string(),
},
async execute(args) {
return `已拦截: ${args.command}`
},
})
如果你想禁用某个内置工具但不想覆盖,用权限配置更合适。
⚙️ 参数定义
参数类型用 tool.schema(底层是 Zod),支持各种类型校验:
args: {
query: tool.schema.string().describe("SQL 查询"),
limit: tool.schema.number().optional().describe("返回条数上限"),
format: tool.schema.enum(["json", "csv"]).describe("输出格式"),
}
也可以直接导入 Zod 自己写:
import { z } from "zod"
export default {
description: "工具描述",
args: {
param: z.string().describe("参数说明"),
},
async execute(args, context) {
return "结果"
},
}
🧠 获取上下文信息
工具能拿到当前会话的上下文:
// .opencode/tools/project.ts
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "获取项目信息",
args: {},
async execute(args, context) {
const { agent, sessionID, messageID, directory, worktree } = context
return `Agent: ${agent}, 目录: ${directory}, Git 根目录: ${worktree}`
},
})
context.directory:当前工作目录context.worktree:Git worktree 根目录
🐍 实战:用 Python 写工具
工具定义是 TS,但背后跑 Python 脚本完全没问题。先写 Python 逻辑:
# .opencode/tools/add.py
import sys
a = int(sys.argv[1])
b = int(sys.argv[2])
print(a + b)
再写工具定义调用它:
// .opencode/tools/python-add.ts
import { tool } from "@opencode-ai/plugin"
import path from "path"
export default tool({
description: "用 Python 做加法",
args: {
a: tool.schema.number().describe("第一个数"),
b: tool.schema.number().describe("第二个数"),
},
async execute(args, context) {
const script = path.join(context.worktree, ".opencode/tools/add.py")
const result = await Bun.$`python3 ${script} ${args.a} ${args.b}`.text()
return result.trim()
},
})
这里用 Bun.$ 执行 Python 脚本,你也可以用 Node 的 child_process.execSync。
💡 实际场景示例
查数据库:写个 SQL 查询工具,AI 直接帮你查业务数据。
调内部 API:封装公司内部接口,AI 帮你调 API 查状态、发请求。
代码规范检查:对接 ESLint、Prettier,AI 写完代码自动跑检查。
部署触发:一键调 CI/CD 流水线,AI 帮你部署。
日志分析:写个工具 grep 日志文件,AI 直接定位 Bug。
🎯 小结
OpenCode 的自定义工具机制,把 AI 编程助手从"只能读写代码"升级到了"能对接任意系统"。关键点:
- TypeScript 定义 + 任意语言执行
- 文件名 = 工具名,简单直觉
- 支持参数校验和上下文获取
- 可覆盖内置工具
- 一个文件导出多工具
换个说法——这不是在给 AI 写代码,这是在给 AI 装技能。 写好工具,你的 AI 助手就能做任何你想让它做的事。