! 本篇文章將會介紹 custom tools,用幾十行 TypeScript 幫 AI 打造專屬工具,甚至直接內建 Python :D
TL;DR: https://dev.benben.me/slides/s/ironman-16-custom-tools
讀完這篇你會學到:
tool() helper 定義 AI 可呼叫的工具Day 15 的 skill 是「教 AI 怎麼想」,custom tool 則是「給 AI 新的能做到」。查公司資料庫、呼叫內部 API、跑自訂腳本——這些 bash 湊不出來或湊得很醜的事,寫成一個 tool 之後,AI 像用內建工具一樣自然地呼叫它。
.opencode/tools/
~/.config/opencode/tools/
檔名就是工具名:database.ts 會註冊一個叫 database 的工具。
// .opencode/tools/database.ts
import {tool} from '@opencode-ai/plugin'
export default tool({
description: 'Query the project database',
args: {
query: tool.schema.string().describe('SQL query to execute'),
},
async execute(args) {
// 你的資料庫邏輯
return `Executed query: ${args.query}`
},
})
三個成分:
description:AI 判斷「何時該用這工具」的依據,跟 skill 的 description 一樣要寫清楚args:參數的 Zod schema(tool.schema 就是 Zod)。.describe() 會成為參數說明,AI 看著它填參數execute:真正執行的函式,回傳字串(或物件)給 AI存檔、重啟 opencode,AI 的工具箱就多了一個 database。
execute 的第二個參數帶著 session 資訊:
async execute(args, context) {
const { agent, sessionID, directory, worktree } = context
// ...
}
context.worktree 是 git worktree 根目錄,context.directory 是工作目錄——路徑處理靠這兩個,不要寫死。
一個檔案 export 多個 tool,會註冊成 檔名_匯出名:
// .opencode/tools/math.ts
export const add = tool({
/* ... */
})
export const multiply = tool({
/* ... */
})
註冊出 math_add 與 math_multiply 兩個工具。
custom tool 跟內建工具撞名時,custom 優先。例如寫一個 bash.ts 擋掉危險指令——所有 bash 呼叫都會繞進你的 wrapper。官方提醒:想「停用」內建工具而不是替換的話,用 permissions 就好,別為了關燈蓋一棟房子。
tool 定義是 TypeScript,但定義裡可以呼叫任何語言的腳本:
// .opencode/tools/python-add.ts
import {tool} from '@opencode-ai/plugin'
import path from 'path'
export default tool({
description: 'Add two numbers using Python',
args: {
a: tool.schema.number(),
b: tool.schema.number(),
},
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.$ shell API 叫 Python 跑 add.py。團隊既有腳本資產不用重寫,包一層就能上。
在 .opencode/package.json 宣告 dependency(例如 shell 轉義庫 shescape),opencode 啟動時自動 bun install,tool 裡直接 import。
Custom tool 就是 tool,Day 11 的 permission 系統一體適用:
{
"permission": {
"database": "ask"
}
}
敏感工具設 ask——AI 每次呼叫前都要你點頭。工具描述裡也建議寫清楚副作用(「會寫入生產資料庫」),AI 會更謹慎地用。
注意!像 database 這種吃整句 SQL 的 tool 要再多想一層:query 是 AI 生成的,prepared statement 擋不了 prompt injection——AI 讀到惡意檔案可能被慫恿下 DROP TABLE。保險做法是讓工具天然做不了壞事:連線用唯讀帳號,或把 execute 限制成只放行 SELECT。
| Custom tool | MCP server | |
|---|---|---|
| 形式 | 一個 TS 檔 | 獨立 process / 服務 |
| 共用範圍 | 這個專案 / 這台機器 | 任何支援 MCP 的工具都能連 |
| 開發成本 | 幾十行 | 包協定、起 server |
口訣:自己專案用 → custom tool;跨工具、跨團隊共用 → MCP。
Q:tool 報錯 AI 看得到嗎?
A:execute 拋出的錯誤會回給 AI,它通常會回報或換個方式重試。回傳「Error: ...」字串也行,總之把失敗資訊講清楚,AI 的自我修正能力會讓你驚訝。
Q:tool 會吃到我的 API key 嗎?
A:需要 key 時用環境變數(process.env),別寫進檔案。搭配 Day 08 的 {env:變數名} 觀念,機密永遠不落地。
Q:修改 tool 之後要重啟嗎?
A:保險做法是重啟 session 讓工具重新註冊。
.opencode/tools/*.ts,tool() + Zod schema,檔名即工具名context.worktree 處理路徑ask / deny
寫這篇的當下,opencode 已經悄悄推出
2.0.x了!,筆者這邊也會先試試水溫,試結束之後再多個幾篇。
Day 17:Plugins 與 SDK——事件 hook 加程式化客戶端,把 opencode 變成真正的平台。
有任何疑問但沒有 iT 邦幫忙帳號,或是想匿名提問?
歡迎到 https://dev.benben.me/q/P3C5U6 提問或加油打氣,沒意外的話會在完賽之後一起回答 :D