OpenCode 作为一款开源的 AI 编程助手,内置了丰富的工具集——文件读写、终端命令执行、代码搜索等——覆盖了日常开发的大部分需求。然而,在实际项目中,我们往往需要一些特定领域的操作:查询内部数据库、调用团队 API、执行自定义的构建脚本等。针对这类需求,OpenCode 提供了 Custom Tools(自定义工具)机制,允许开发者用少量代码创建 AI 可调用的功能扩展。
本文将深入讲解 OpenCode 自定义工具的创建、配置与实战应用,帮助你为 AI 编程助手赋予更贴合项目需求的能力。
Custom Tools 本质上是定义在 TypeScript(或 JavaScript)文件中的函数,AI 模型可以在对话过程中自动调用它们。每个工具包含三个核心部分:
工具定义文件放置在项目的 .opencode/tools/ 目录下,文件名即工具名。OpenCode 启动时会自动加载该目录中的所有工具。
编写自定义工具需要安装 @opencode-ai/plugin 包:
# 在项目目录下安装 npm install -D @opencode-ai/plugin # 或使用 bun bun add -d @opencode-ai/plugin
该包提供了 tool() 辅助函数和类型定义,让工具编写更简洁、类型安全。
让我们从一个简单的示例开始——创建一个天气查询工具。在项目根目录创建 .opencode/tools/weather.ts:
import { tool } from "@opencode-ai/plugin";
export default tool({
description: "查询指定城市的当前天气",
args: {
city: tool.schema.string().describe("城市名称,如 北京、上海"),
},
async execute(args) {
const res = await fetch(
`https://wttr.in/${encodeURIComponent(args.city)}?format=%C+%t`
);
const text = await res.text();
return `${args.city} 天气:${text}`;
},
});
保存后在 OpenCode 对话中输入"北京今天天气怎么样",AI 会自动识别并调用 weather 工具,获取实时天气数据返回给你。
工具 filename(不含 .ts 后缀)就是工具名称,AI 根据 description 判断何时调用。因此,description 的精确性直接影响工具的可用频率。
OpenCode 使用 Zod 进行参数校验,支持丰富的类型系统:
import { tool } from "@opencode-ai/plugin";
export default tool({
description: "查询数据库中的用户信息",
args: {
userId: tool.schema.number().describe("用户 ID"),
includeDeleted: tool.schema
.boolean()
.optional()
.default(false)
.describe("是否包含已删除用户"),
fields: tool.schema
.array(tool.schema.string())
.optional()
.describe("需要返回的字段列表"),
},
async execute(args) {
// args.userId 为 number 类型
// args.includeDeleted 默认为 false
// args.fields 可选
return `查询用户 ${args.userId} 的信息`;
},
});
Zod 支持的类型包括 string、number、boolean、array、enum 等,配合 .describe() 方法提供语义描述,AI 就能准确生成参数。如果 AI 传入的参数不符合定义,OpenCode 会自动校验并提示修正。
工具的 execute 函数第二个参数 context 提供了当前会话的上下文信息:
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 名称
sessionID, // 当前会话 ID
directory, // 会话工作目录
worktree, // Git 工作根目录
};
},
});
context.directory 和 context.worktree 在需要操作文件的工具中尤其有用,比如创建文件、读取配置等。
在一个文件中可以导出多个工具,每个导出项都是一个独立的工具,命名格式为 <文件名>_<导出名>:
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).toString();
},
});
export const multiply = tool({
description: "计算两个数字之积",
args: {
a: tool.schema.number().describe("第一个数字"),
b: tool.schema.number().describe("第二个数字"),
},
async execute(args) {
return (args.a * args.b).toString();
},
});
这样会生成 math_add 和 math_multiply 两个工具。这种方式适合将相关功能的工具归类到同一文件。
在实际项目中,让 AI 直接查询数据库是常见需求。下面是一个安全的只读数据库查询工具:
import { tool } from "@opencode-ai/plugin";
import { execSync } from "child_process";
export default tool({
description: "对项目 MySQL 数据库执行只读查询(SELECT 语句)",
args: {
sql: tool.schema
.string()
.describe("SQL SELECT 查询语句,仅支持 SELECT"),
},
async execute(args) {
const sql = args.sql.trim().toUpperCase();
if (!sql.startsWith("SELECT")) {
return "错误:仅允许执行 SELECT 查询";
}
try {
const result = execSync(
`mysql -u root -p${process.env.DB_PASS} -e "${args.sql}"`,
{ encoding: "utf-8", timeout: 10000 }
);
return result;
} catch (err) {
return `查询失败:${err.message}`;
}
},
});
AI 就可以根据你的需求生成并执行 SQL 查询,快速获取数据。
团队内部常常有各种微服务 API,可以让 AI 直接调用:
import { tool } from "@opencode-ai/plugin";
export default tool({
description: "调用项目部署状态 API,获取当前线上版本信息",
args: {
environment: tool.schema
.enum(["staging", "production"])
.describe("部署环境"),
},
async execute(args) {
const baseUrl = process.env.DEPLOY_API_URL;
const res = await fetch(`${baseUrl}/status?env=${args.environment}`, {
headers: { Authorization: `Bearer ${process.env.DEPLOY_API_KEY}` },
});
return res.json();
},
});
注意将 API Key 等敏感信息放在环境变量或 .env 文件中,不要硬编码在工具代码里。
如果 OpenCode 内置的某个工具不符合你的需求,可以用同名文件覆盖它。例如限制 bash 命令的执行范围:
import { tool } from "@opencode-ai/plugin";
export default tool({
description: "受限的 shell 命令执行",
args: {
command: tool.schema.string().describe("要执行的命令"),
},
async execute(args) {
const allowed = ["npm run", "ls", "cat", "pwd", "git status"];
if (!allowed.some((cmd) => args.command.startsWith(cmd))) {
return `不允许执行命令:${args.command}`;
}
// 执行逻辑...
return `已执行:${args.command}`;
},
});
如果你只想禁用而非覆盖某个内置工具,使用权限系统(Permissions)是更推荐的方式。
工具定义可以放在两个位置:
.opencode/tools/(推荐,随项目一起版本控制)~/.config/opencode/tools/(对所有项目生效)全局工具适合放一些通用性的功能,比如天气预报、时间转换、通用代码搜索等。项目级工具则适合放和当前项目强关联的逻辑,比如数据库查询、项目 API 调用、自定义构建命令等。
虽然工具定义必须是 TypeScript 文件,但具体的执行逻辑可以用任何语言实现。工具定义文件负责参数校验和调用,实际工作交给外部脚本:
import { tool } from "@opencode-ai/plugin";
import path from "path";
import { execSync } from "child_process";
export default tool({
description: "使用 Python 脚本分析代码复杂度",
args: {
filePath: tool.schema.string().describe("要分析的文件路径"),
},
async execute(args, context) {
const script = path.join(context.worktree, ".opencode/tools/analyze.py");
const result = execSync(
`python3 ${script} ${args.filePath}`,
{ encoding: "utf-8" }
);
return result.trim();
},
});
这样,你团队中已有的 Python、Go、Rust 等脚本都可以封装成 Custom Tools,让 AI 调用。
开发 Custom Tools 时,有几个实用建议:
description 要精确:AI 依靠描述判断何时调用工具,描述越精准,调用越准确
参数加 .describe():每个参数都添加语义描述,帮助 AI 生成正确的参数值
错误处理要完善:工具执行可能失败,返回清晰的错误信息有助于 AI 自我修正
避免耗时操作:工具执行时间过长会影响交互体验,建议设置超时
日志输出:在工具内添加日志,方便排查问题
安全第一:涉及敏感操作时做好权限校验,不要在工具中硬编码密钥
OpenCode 的 Custom Tools 机制为 AI 编程助手提供了强大的可扩展性。你只需要编写一个简单的 TypeScript 文件,就能让 AI 具备查询数据库、调用 API、运行自定义脚本等能力。结合项目级和全局级工具的划分,以及多语言脚本的支持,Custom Tools 可以灵活适应各种开发场景。
现在就为你的项目编写第一个自定义工具吧——从简单的信息查询开始,逐步扩展,让 OpenCode 成为真正懂你项目的 AI 编程伙伴。