ARTICLE · 1350972
OpenCode 自定义插件开发与安装指南
OpenCode 自定义插件开发与安装指南
适用版本:OpenCode V2 官方文档:https://opencode.ai/v2/docs/build/plugins 整理日期:2026-11-08
1. 插件是什么
OpenCode 的插件是一个 npm 包(也可以是本地目录或单文件),用于修改 OpenCode 的行为、增加新能力。
一个插件包最多包含四个部分,每个部分都是包的独立导出:
../server | |||
./tui | |||
./gui-experimental | |||
./rpc |
大多数场景只需要 Server 部分。所有部分的 SDK 都来自同一个依赖:@opencode/plugin。
OpenCode 采用客户端-服务端架构:TUI 连接后台服务,插件运行在服务端,因此插件可以拦截会话、工具调用、模型请求等核心流程。
2. 快速开始:最小插件
2.1 方式一:单文件插件(最快)
在项目根目录创建:
.opencode/plugins/hello.ts
内容:
import { Plugin } from "@opencode/plugin"
export default Plugin.define({
id: "hello",
async setup(ctx) {
console.log(`插件已加载,OpenCode 版本:${ctx.app.version}`)
// 注册一个自定义工具
await ctx.tool.transform((editor) => {
editor.add({
name: "hello",
description: "向指定的人打招呼",
input: {
type: "object",
properties: {
name: { type: "string" },
},
required: ["name"],
additionalProperties: false,
},
execute: async (input) => {
const { name } = input as { name: string }
return { content: `Hello ${name}!` }
},
})
})
},
})
.opencode/plugins/ 目录下的 .ts / .js 文件会被自动发现并加载,无需修改任何配置。
2.2 方式二:完整插件目录(推荐)
.opencode/plugins/my-plugin/
├── package.json
└── src/
├── index.ts # server 部分
└── tui.tsx # 可选:TUI 部分
package.json:
{
"name": "opencode-acme-plugin",
"version": "1.0.0",
"type": "module",
"exports": {
".": "./src/index.ts"
},
"files": ["src"],
"dependencies": {
"@opencode/plugin": "latest"
}
}
2.3 目录的加载规则
如果目录中有 package.json的name和exports,按exports加载各部分。如果没有 exports,OpenCode 会在目录根查找:server(找不到则用index)tuirpc扩展名支持 .ts、.tsx、.jsGUI 部分为 gui-experimental.cjs(或.js)
3. 插件核心概念
3.1 Plugin.define
import { Plugin } from "@opencode/plugin"
export default Plugin.define({
id: "example",
async setup(ctx) {
// 初始化逻辑
},
})
id:插件唯一标识。setup(ctx):插件加载时执行,可以返回一个清理函数,在插件卸载时调用。
setup(ctx) {
const timer = setInterval(() => {
// 周期任务
}, 60_000)
return () => clearInterval(timer)
}
3.2 插件上下文 ctx
ctx 本质上是一个带插件扩展能力的 OpenCode 服务端客户端。常用能力:
ctx.app.version | |
ctx.location | directory、workspaceID、project) |
ctx.options | |
ctx.storage | |
ctx.event | |
ctx.tool | |
ctx.session | |
ctx.command | |
ctx.agent | |
ctx.providerctx.model | |
ctx.skill | |
ctx.mcp | |
ctx.reference | |
ctx.permission | |
ctx.shell | |
ctx.vcsctx.worktree | |
ctx.websearch |
3.3 Transforms(转换)
Transforms 是对 OpenCode 领域状态的同步编辑,例如注册工具、命令、Provider、模型等。
const registration = await ctx.tool.transform((editor) => {
editor.add({ /* 工具定义 */ })
})
// 不再需要时注销
await registration.dispose()
重要规则:
Transform 回调必须是同步的;外部数据要在回调外先加载好。 捕获的外部数据变化后,调用对应的 reload()重新应用,例如:ctx.tool.reload()ctx.model.reload()ctx.provider.reload()ctx.command.reload()ctx.agent.reload()ctx.mcp.reload()ctx.skill.reload()注册顺序有意义,后注册的 transform 可以看到前面注册的结果。 每个模型请求会捕获一份稳定的工具快照,后续的 transform 只影响未来的请求。
3.4 Hooks(钩子)
Hooks 用于拦截正在发生的操作,返回的注册对象可调用 dispose() 注销。
常用钩子:
ctx.session.hook("prompt", ...) | |
ctx.session.hook("context", ...) | |
ctx.session.hook("compaction", ...) | |
ctx.session.hook("title", ...) | |
ctx.session.hook("http.request", ...) | |
ctx.session.hook("http.response", ...) | |
ctx.session.hook("retry", ...) | |
ctx.tool.hook("execute.before", ...) | |
ctx.tool.hook("execute.after", ...) | |
ctx.permission.hook("evaluate", ...) | |
ctx.shell.hook("create.before", ...) |
示例:拦截所有用户提示词中的敏感信息:
await ctx.session.hook("prompt", (event) => {
event.prompt.text = event.prompt.text.replaceAll("company-secret", "[redacted]")
event.metadata = { ...event.metadata, source: "company-policy" }
})
示例:给每次模型调用追加系统指令并降低温度:
await ctx.session.hook("context", (event) => {
event.system.push({ type: "text", text: "回答必须简洁,并给出依据。" })
event.options.temperature = 0.2
})
3.5 工具定义
await ctx.tool.transform((editor) => {
// 可选:给工具加命名空间
editor.namespace({
name: "acme",
description: "Acme 客户账户工具",
})
editor.add({
name: "greeting",
description: "创建一条问候语",
input: {
type: "object",
properties: {
name: { type: "string" },
},
required: ["name"],
additionalProperties: false,
},
options: { namespace: "acme", codemode: true },
execute: async (input, context) => {
// 向用户报告进度
await context.progress({ status: "greeting" })
// context.signal 可用于取消可中止操作
const response = await fetch("https://example.com/api", {
signal: context.signal,
})
return { content: await response.text() }
},
})
})
更新和删除工具(使用带命名空间的最终名称):
await ctx.tool.transform((editor) => {
editor.update("acme_greeting", (tool) => {
tool.description = "按名字问候用户"
})
editor.remove("acme_obsolete")
})
3.6 命令
await ctx.command.transform((editor) => {
editor.add({
name: "security-review",
description: "审查当前变更的安全问题",
execute: async ({ sessionID, prompt, delivery }) => {
await ctx.session.prompt({
...prompt,
sessionID,
text: `请从安全角度审查以下变更:\n\n${prompt.text}`,
delivery,
})
},
})
})
3.7 持久化存储
await ctx.storage.set("settings", { strict: true })
const settings = await ctx.storage.get("settings")
const page = await ctx.storage.scan({ prefix: "cache/", limit: 100 })
await ctx.storage.remove("settings")
3.8 读取插件选项
配置:
{
"$schema": "https://opencode.ai/config.json",
"plugins": [
{
"package": "./plugins/company",
"options": {
"strict": true,
"agent": "reviewer"
}
}
]
}
插件中:
setup(ctx) {
const strict = ctx.options.strict === true
const agent = String(ctx.options.agent ?? "build")
}
3.9 订阅事件
export default Plugin.define({
id: "events-example",
async setup(ctx) {
const controller = new AbortController()
void (async () => {
for await (const event of ctx.event.subscribe({ signal: controller.signal })) {
console.log("事件类型:", event.type)
}
})()
return () => controller.abort()
},
})
只订阅指定事件:
for await (const event of ctx.event.subscribe({ types: ["config.updated"] })) {
// ...
}
4. 完整实战:一个可用的插件
目标:创建一个插件,提供 project_info 工具,并在每次模型请求前注入团队规范。
4.1 目录结构
project-root/
└── .opencode/
└── plugins/
└── team-kit/
├── package.json
└── src/
└── index.ts
4.2 package.json
{
"name": "opencode-team-kit",
"version": "0.1.0",
"type": "module",
"exports": {
".": "./src/index.ts"
},
"files": ["src"],
"dependencies": {
"@opencode/plugin": "latest"
}
}
4.3 src/index.ts
import { Plugin } from "@opencode/plugin"
import { readFile, stat } from "node:fs/promises"
import path from "node:path"
export default Plugin.define({
id: "team-kit",
async setup(ctx) {
const strict = ctx.options.strict === true
// 1. 注册工具
await ctx.tool.transform((editor) => {
editor.add({
name: "project_info",
description: "读取当前项目的 package.json 基本信息",
input: {
type: "object",
properties: {},
additionalProperties: false,
},
execute: async () => {
const file = path.join(ctx.location.directory, "package.json")
const info = JSON.parse(await readFile(file, "utf8"))
return {
content: JSON.stringify(
{
name: info.name,
version: info.version,
scripts: Object.keys(info.scripts ?? {}),
},
null,
2,
),
}
},
})
})
// 2. 注册命令
await ctx.command.transform((editor) => {
editor.add({
name: "team-check",
description: "按团队规范检查当前项目",
execute: async ({ sessionID, prompt, delivery }) => {
await ctx.session.prompt({
...prompt,
sessionID,
text: "请按团队规范检查当前项目,并列出问题清单。",
delivery,
})
},
})
})
// 3. 注入团队规范
let rules = ""
try {
rules = await readFile(
path.join(ctx.location.directory, ".opencode/team-rules.md"),
"utf8",
)
} catch {
rules = "未找到团队规范文件。"
}
await ctx.session.hook("context", (event) => {
event.system.push({
type: "text",
text: `## 团队规范\n\n${rules}`,
})
if (strict) {
event.options.temperature = 0.2
}
})
},
})
4.4 验证
保存文件后 OpenCode 会自动重载(也可以执行 /reload)。在 TUI 中让 Agent 使用 project_info工具。使用 /team-check命令触发自定义命令。观察模型请求的上下文是否包含团队规范。
5. 安装插件
5.1 自动发现(本地开发最方便)
以下位置会被自动加载:
<项目>/.opencode/plugins/ # 项目级
~/.config/opencode/plugins/ # 全局级
Windows 上全局目录通常是:
C:\Users\<用户名>\.config\opencode\plugins\
支持直接放 .ts / .js 文件,或放带 package.json 的插件目录。
5.2 配置 plugins 数组
在 opencode.jsonc 中添加:
{
"$schema": "https://opencode.ai/config.json",
"plugins": [
"opencode-acme-plugin",
"opencode-acme-plugin@1.2.0",
"@acme/opencode-plugin",
"./plugins/local",
"../shared/plugin",
"/absolute/path/plugin",
"file:///home/me/plugins/local",
{
"package": "@acme/opencode-plugin",
"options": {
"agent": "reviewer",
"strict": true
}
}
]
}
注意事项:
相对路径从包含该条目的配置文件所在目录解析。 通过配置指定的本地路径必须是目录,指向单个文件会被跳过。 插件数组按配置优先级从低到高合并。 支持禁用语法: -前缀禁用某个插件*匹配所有插件.*匹配某个 ID 前缀后面的 ID 可以重新启用
{
"plugins": ["*", "-opencode.provider.*", "opencode.provider.openai", "-acme.reviewer"]
}
5.3 CLI 管理(npm/Git 包)
安装全局包插件:
opencode plugin add opencode-acme-plugin@1.2.0
opencode plugin add @acme/opencode-plugin@latest
opencode plugin add github:acme/opencode-plugin
opencode plugin add git+ssh://git@github.com/acme/opencode-plugin.git#main
opencode plugin add 'github:acme/plugins#main::path:packages/opencode-plugin'
查看与管理:
opencode plugin list
opencode plugin list --builtin
opencode plugin check
opencode plugin update
opencode plugin update opencode-acme-plugin
opencode plugin remove opencode-acme-plugin@1.2.0
说明:
支持 npm 名称、版本、tag、范围,以及 npm 兼容的 Git 规格。 支持分支、tag、完整 commit hash,以及 ::path:子目录选择器。不支持 tarball 和 npm alias。 精确版本和完整 commit hash 不会被自动更新。
5.4 TUI-only 插件
只包含 TUI 部分的插件配置在 cli.json:
{
"plugins": ["opencode-acme-cli"]
}
全局 cli.json 通常位于:
~/.config/opencode/cli.json
5.5 桌面端插件
导出 ./gui-experimental 的插件会出现在 OpenCode Desktop 中。桌面端只从全局opencode.json(c) 加载:
{
"plugins": ["opencode-acme-plugin"]
}
可以在 Settings > Extensions > Plugins 中查看和重新加载。
6. 重载与调试
配置文件被监听,保存后自动重载插件,无需重启。 可用 touch触发本地插件重载:
touch .opencode/plugins/concise/index.ts
未监听的本地依赖变化,可能需要重启后台服务:
opencode service restart
opencode service status
TUI 中也可以执行 /reload强制完整重载。查看日志:
~/.local/share/opencode/log/opencode.log
需要更多日志时:
OPENCODE_LOG_LEVEL=DEBUG
7. 发布插件
发布就是一个普通 npm 包:
npm publish
建议:
在 package.json中导出./rpc,让其他插件和客户端可以只导入类型契约。GUI 部分先构建产物,并通过 files包含进去。使用与目标 OpenCode 版本兼容的 @opencode/plugin版本。测试真实安装后的包,而不仅是 workspace 链接的副本。
8. 兼容 V1 与 V2
如果同一个包需要同时支持 V1 和 V2,可以同时导出 setup() 和 server():
import { Plugin } from "@opencode/plugin"
export default {
...Plugin.define({
id: "example",
async setup(ctx) {
await ctx.tool.hook("execute.before", () => {
console.log("工具即将执行")
})
},
}),
async server() {
return {
"tool.execute.before": async () => {
console.log("工具即将执行")
},
}
},
}
V1 调用 server()并使用其返回的钩子。V2 读取 id和setup()(Effect 插件用effect()),忽略server()。两套实现彼此独立,V1 钩子不会自动转换为 V2 钩子。
9. 常见问题
Q1:插件放了但没加载?
确认文件在 .opencode/plugins/下,或在opencode.jsonc的plugins中配置。通过配置指定的本地路径必须是目录,不能是单个文件。 检查 opencode plugin list是否列出。查看日志 ~/.local/share/opencode/log/opencode.log。
Q2:修改代码后没有生效?
试着 touch文件或执行/reload。如果依赖了未监听的外部文件/包,执行 opencode service restart。
Q3:Transform 里的外部数据不更新?
Transform 回调是同步的,外部数据在加载时就已捕获。 数据变化后调用对应域的 reload(),例如ctx.tool.reload()。
Q4:工具名和预期不一致?
命名空间中的点号和不支持字符会被替换为 _。更新/删除工具时使用带命名空间的最终名称,例如 acme_greeting。
Q5:本地插件 vs npm 插件怎么选?
开发和团队内部使用:本地目录,放 .opencode/plugins/或配置相对路径。需要跨项目分发、版本管理:发布 npm 包,用 opencode plugin add安装。
10. 参考文档
插件总览:https://opencode.ai/v2/docs/build/plugins Server 插件:https://opencode.ai/v2/docs/build/plugins/server 插件安装与配置:https://opencode.ai/v2/docs/plugins TUI 插件:https://opencode.ai/v2/docs/build/plugins/cli GUI 插件:https://opencode.ai/v2/docs/build/plugins/gui RPC 契约:https://opencode.ai/v2/docs/build/plugins/rpc V1 插件迁移:https://opencode.ai/v2/docs/build/plugins/migrate-v1 配置参考:https://opencode.ai/v2/docs/config 故障排查:https://opencode.ai/v2/docs/troubleshooting















































































