學習目標
- 理解外掛載入機制與結構
- 學會實作外掛事件監聽
- 掌握自訂工具的定義與 Zod 參數驗證
- 能用 Python 或 JavaScript 編寫自訂工具
外掛系統概述
opencode 的外掛系統讓你可以在不修改核心程式碼的情況下,擴充或修改工具的行為。外掛可以監聽事件、注入環境變數、新增自訂工具,以及保護特定檔案不被意外修改。
外掛的載入位置
外掛可以來自兩種來源:
| 來源 | 設定方式 | 範例 |
|---|---|---|
| 本地檔案 | 指定 .js 或 .mjs 檔案的路徑 | "./plugins/my-plugin.js" |
| npm 套件 | 安裝 npm 套件後直接引用 | "opencode-plugin-env" |
外掛設定在 opencode.json 的 plugins 陣列中:
{
"plugins": [
"./plugins/notifier.js",
"opencode-plugin-env"
]
}
外掛也可以是物件形式,以便傳遞自訂選項:
{
"plugins": [
{
"name": "./plugins/notifier.js",
"options": {
"sound": "chime",
"notifyOn": ["tool:start", "tool:end"]
}
}
]
}
外掛基本結構
一個外掛是一個匯出一個或多個鉤子函式的 JavaScript 或 TypeScript 模組:
// plugins/notifier.js
export const name = "notifier";
export const hooks = {
// 在工具執行前觸發
"tool:start"({ tool, args }) {
console.log(`[Plugin] 工具 ${tool} 開始執行`);
},
// 在工具執行完成後觸發
"tool:end"({ tool, args, result }) {
console.log(`[Plugin] 工具 ${tool} 執行完畢`);
},
// 在代理回覆前觸發
"before:reply"({ message }) {
console.log(`[Plugin] 代理準備回覆`);
}
};
事件分類
外掛可監聽以下事件分類:
| 事件前綴 | 說明 |
|---|---|
tool:start | 工具開始執行時 |
tool:end | 工具執行結束時 |
tool:error | 工具執行發生錯誤時 |
before:reply | 代理產生回覆前 |
after:reply | 代理回覆完成後 |
agent:start | 代理開始處理時 |
agent:end | 代理處理完成時 |
實用外掛範例
桌面通知外掛
當代理執行特定操作時,發送桌面通知:
// plugins/desktop-notifier.js
export const name = "desktop-notifier";
export const hooks = {
"tool:start"({ tool, args }) {
if (tool === "bash" || tool === "write") {
// 使用作業系統的通知機制
// 例如 Notification API(需在特定環境)
}
}
};
.env 檔案保護
防止代理意外修改敏感的環境變數檔:
// plugins/env-protector.js
export const name = "env-protector";
export const hooks = {
"tool:start"({ tool, args }) {
if (tool === "write" || tool === "edit") {
const filePath = args.filePath || "";
if (filePath.includes(".env") && !filePath.includes(".env.example")) {
throw new Error("不允許直接修改 .env 檔案");
}
}
}
};
運作原理:當鉤子函式拋出錯誤時,該工具執行會被中斷,代理會收到錯誤訊息。這個機制讓外掛能有效地控制工具的行為。
注入環境變數
可透過 npm 套件 opencode-plugin-env 自動從檔案注入環境變數:
{
"plugins": [
{
"name": "opencode-plugin-env",
"options": {
"files": [".env.local", ".env.development"]
}
}
]
}
自訂工具外掛
外掛也可以註冊全新的工具:
// plugins/weather-tool.js
export const name = "weather";
export const tools = [
{
name: "get_weather",
description: "取得指定城市的目前天氣",
parameters: {
type: "object",
properties: {
city: {
type: "string",
description: "城市名稱"
}
},
required: ["city"]
},
async execute({ city }) {
const res = await fetch(`https://api.weather.com/${city}`);
return res.json();
}
}
];
自訂工具
除了透過外掛註冊工具,你也可以直接在專案中定義自訂工具,讓代理在該專案中可以使用。
位置與結構
自訂工具放在專案根目錄的 .opencode/tools/ 目錄下,每個工具是一個獨立的 .js 或 .mjs 檔案:
.opencode/
└── tools/
├── analyze-log.js
└── deploy.js
opencode 會自動發現並載入此目錄下的所有工具檔案。
使用 tool() 輔助函式
opencode 提供 tool() 輔助函式,讓工具定義更簡潔:
// .opencode/tools/analyze-log.js
import { tool } from "opencode/tool";
export default tool({
name: "analyze-log",
description: "分析日誌檔案中的錯誤模式",
parameters: {
filePath: { type: "string", description: "日誌檔案路徑" },
pattern: { type: "string", description: "搜尋模式(選填)", optional: true }
},
async execute({ filePath, pattern }) {
const content = await readFile(filePath, "utf-8");
const lines = content.split("\n");
const errors = lines.filter(l => l.includes("ERROR"));
return `找到 ${errors.length} 筆錯誤`;
}
});
注意:工具檔案必須使用 ESM 格式(export default),並以 .js 或 .mjs 為副檔名。不支援 CommonJS(module.exports)。
參數定義(Zod Schema)
自訂工具也支援使用 Zod 來定義參數結構,以獲得更好的型別檢查與驗證:
// .opencode/tools/deploy.js
import { tool } from "opencode/tool";
import { z } from "zod";
export default tool({
name: "deploy",
description: "部署專案到指定環境",
parameters: z.object({
environment: z
.enum(["staging", "production"])
.describe("部署目標環境"),
branch: z
.string()
.default("main")
.describe("要部署的分支名稱"),
force: z
.boolean()
.optional()
.describe("強制部署(跳過檢查)")
}),
async execute({ environment, branch, force }) {
// 執行部署邏輯
return `部署 ${branch} 到 ${environment} 成功`;
}
});
上下文資訊
工具的 execute 函式可以接收第二個參數,包含工具執行時期的上下文資訊:
export default tool({
name: "config-env",
description: "讀取環境設定",
parameters: { key: { type: "string" } },
async execute({ key }, context) {
// context 包含:
// - context.projectDir: 專案根目錄
// - context.config: opencode 設定內容
// - context.args: 用戶輸入的原始參數
// - context.signal: AbortSignal
return process.env[key] || "未設定";
}
});
用 Python 編寫自訂工具
opencode 也支援使用 Python 編寫工具。你需要安裝 opencode-sdk 套件,並使用 @opencode_tool 裝飾器:
# .opencode/tools/docker-status.py
from opencode_sdk import opencode_tool
import subprocess
import json
@opencode_tool(
name="docker-status",
description="檢查 Docker 容器狀態",
parameters={
"container_name": {
"type": "string",
"description": "容器名稱(選填,省略則列出全部)",
"optional": True
}
}
)
def docker_status(container_name=None):
cmd = ["docker", "ps", "--format", "{{.Names}}\t{{.Status}}"]
if container_name:
cmd.extend(["--filter", f"name={container_name}"])
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
return f"錯誤: {result.stderr}"
if not result.stdout.strip():
return "無執行中的容器"
lines = [line.split("\t") for line in result.stdout.strip().split("\n")]
return json.dumps(
[{"name": n, "status": s} for n, s in lines],
ensure_ascii=False, indent=2
)
Python SDK 安裝:pip install opencode-sdk。支援 Python 3.10 以上版本。SDK 會自動處理工具的通訊協定,你只需要專注在工具邏輯本身。
實戰練習
練習 1:建立保護外掛
- 建立
plugins/protector.js外掛檔案 - 監聽
tool:start事件 - 當工具為
write且目標檔案是package.json時拋出錯誤 - 在
opencode.json中註冊此外掛 - 啟動 opencode 並嘗試要求代理修改 package.json,觀察保護機制
練習 2:建立自訂工具
- 在
.opencode/tools/目錄下建立count-lines.js - 工具接受
filePath參數,回傳該檔案的總行數 - 使用
tool()輔助函式和 Zod schema - 啟動 opencode 並測試此工具是否被正確載入與執行
練習 3:Python 工具整合
- 安裝 Python SDK:
pip install opencode-sdk - 建立
.opencode/tools/file-stats.py工具 - 實作功能:計算目錄中的檔案數量、總大小、最大檔案
- 確認 opencode 能自動載入並使用此 Python 工具