OpenCode 提供了一套基于 TypeScript 的插件系统,允许开发者为 AI 编码助手注入自定义工具。本文通过一个实际的 Benchmark 插件,拆解 OpenCode 插件的开发原理、核心 API 和最佳实践。
引言:为什么要给 AI 编码助手写插件
AI 编码工具已经成为日常开发的一部分。但每个团队的工作流都有差异——有人需要对接内部服务,有人需要自定义代码检查,有人需要测量不同模型的响应速度。通用的 AI 助手不可能覆盖所有场景。
OpenCode 的解决方案是开放插件系统:你可以用 TypeScript 编写插件,注册自定义工具(Tool),这些工具直接暴露给 AI Agent 调用。当你在对话中说”帮我跑一下模型基准测试”,AI 就会调用你写的 benchmark_models 工具,而不是猜测或拒绝。
这篇文章分两部分:先讲 OpenCode 插件的运行原理,再用一个 Benchmark 插件的完整实现说明怎么从零开始写一个插件。
背景:OpenCode 的扩展体系
OpenCode 是一个终端内运行的 AI 编码助手,支持多种 LLM 提供商(OpenAI、Anthropic、Google 等)。它的扩展体系有两个核心概念:
- Plugin(插件):TypeScript 代码,运行在 OpenCode 进程中,注册自定义工具和生命周期钩子。AI Agent 可以像调用内置工具一样调用插件注册的工具。
- Skill(技能):纯 Markdown 文件(SKILL.md),不包含可执行代码,通过自然语言指令影响 AI Agent 的行为模式。技能告诉 AI”在什么场景下使用什么工具、如何使用”。
两者配合使用:Plugin 提供能力(Tool),Skill 提供策略(When & How)。
第一部分:OpenCode 插件原理
1.1 插件的发现与加载
OpenCode 从多个来源发现和加载插件,按优先级排列:
| 来源 | 路径 | 适用范围 |
|---|---|---|
| 项目插件目录 | .opencode/plugins/ |
当前项目 |
| 全局插件目录 | ~/.config/opencode/plugins/ |
所有项目 |
| 项目配置 | opencode.json 中的 plugin 字段 |
当前项目 |
| 全局配置 | ~/.config/opencode/opencode.json |
所有项目 |
对于目录中的插件,OpenCode 启动时会自动扫描 .ts 文件,用 Bun 编译并加载——放进去就生效,不需要额外注册。
对于 npm 包形式的插件,在 opencode.json 中声明即可:
1 | { |
一个典型的项目级插件目录结构:
1 | 项目根目录/ |
1.2 核心 API:Plugin 接口
一个 OpenCode 插件本质上是一个异步函数,接收上下文,返回钩子集合:
1 | import type { Plugin } from "@opencode-ai/plugin" |
PluginInput 上下文对象 是插件的核心入口,提供了与 OpenCode 运行时交互的全部能力:
| 属性 | 类型 | 说明 |
|---|---|---|
client |
OpencodeClient |
SDK 客户端,可操作 session、provider、config 等 |
project |
Project |
当前项目元数据 |
directory |
string |
当前项目目录 |
worktree |
string |
Git worktree 根路径 |
serverUrl |
URL |
OpenCode 本地服务地址 |
$ |
BunShell |
Bun 原生 shell,可执行系统命令 |
1.3 Tool 定义:让 AI 能调用的工具
插件最常见的用途是注册自定义工具。OpenCode 用 tool() 辅助函数定义工具:
1 | import { tool } from "@opencode-ai/plugin" |
关键设计:
description:这是 AI Agent 决定是否调用这个工具的唯一依据。写得越清晰,AI 调用得越准确。args:基于 Zod schema 定义参数,自动生成 JSON Schema 供 LLM 理解。execute:异步执行函数,返回字符串。AI 会将返回值作为工具输出呈现给用户。context:执行上下文,包含当前 session、message、agent 信息,以及abort信号用于取消长时间运行的任务。
1.4 Hooks:生命周期钩子
除了注册工具,插件还可以通过钩子介入 OpenCode 的各个生命周期阶段:
| 钩子 | 触发时机 | 典型用途 |
|---|---|---|
tool |
注册自定义工具 | 扩展 AI 能力 |
event |
接收系统事件 | 日志、监控、统计 |
config |
配置加载时 | 动态修改配置 |
auth |
认证流程 | 接入自定义 OAuth/API Key |
chat.message |
新消息到达 | 消息预处理、路由 |
chat.params |
LLM 调用前 | 调整 temperature、topP 等参数 |
chat.headers |
HTTP 请求前 | 注入自定义 Header |
permission.ask |
权限请求时 | 自动授权或拒绝 |
tool.execute.before |
工具执行前 | 参数拦截、修改 |
tool.execute.after |
工具执行后 | 结果后处理 |
shell.env |
Shell 执行前 | 注入环境变量 |
这套钩子系统覆盖了从消息接收到工具调用到结果返回的完整链路,理论上可以对 OpenCode 的任何行为进行定制。
值得一提的是 OpenCode 的工具覆盖机制:如果插件注册的工具与内置工具同名,插件版本会覆盖内置版本。这让插件可以拦截并修改内置工具的行为——比如有人用这个机制包装 shell 工具,在执行前后注入自定义逻辑。
1.5 Plugin 与 Skill 的协作模式
理解 Plugin 和 Skill 的关系是写好插件的关键:
1 | Plugin (代码层) Skill (提示层) |
没有 Skill 的 Plugin 也能工作——AI 会根据 description 自行决定何时调用。但配合 Skill 可以让调用时机更精确,输出格式更可控。
第二部分:Benchmark 插件的实现与使用
2.1 需求场景
在使用 OpenCode 时,通常会配置多个 LLM 提供商和模型。不同模型的响应延迟差异很大——有些模型 1 秒内返回,有些可能需要 20 秒。在选择模型时,我们需要一个快速的基准测试来量化各模型的实际响应速度。
这就是 Benchmark 插件要解决的问题:向所有已配置的模型发送一条简单消息,测量每个模型的端到端响应延迟,按速度排序输出。
2.2 插件实现详解
完整的插件代码约 240 行,我们逐段拆解。
2.2.1 插件骨架
1 | import type { Plugin } from "@opencode-ai/plugin" |
几个要点:
- 插件只注册了一个工具
benchmark_models - 两个可选参数:
provider用于过滤特定提供商,timeout设置超时 description写得足够清晰,AI 能准确判断何时调用
2.2.2 模型发现:收集所有测试目标
1 | type Target = { providerID: string; modelID: string; name: string } |
这里体现了 ctx.client 的核心价值——插件可以直接调用 OpenCode 的内部 API 获取运行时信息。provider.list() 返回所有已配置的提供商及其模型列表,connected 数组标记了哪些提供商处于已连接状态。
代码还实现了降级策略:如果 provider.list() 失败(比如旧版本 API 不支持),会 fallback 到 config.get() 读取配置文件,再 fallback 到 config.providers() 获取默认配置。三层降级确保在不同 OpenCode 版本下都能工作。
2.2.3 并行基准测试
1 | const results = await Promise.all( |
核心设计:
- 并行执行:所有模型同时测试(
Promise.all),而不是串行等待。30 个模型只需等最慢的那个,不是所有时间之和。 - 独立 Session:每个测试创建独立的临时 session,测试完立即删除。不会污染用户的对话历史。
- 超时控制:
Promise.race实现单模型超时,默认 30 秒。避免某个模型无响应时阻塞整个测试。 - 错误分类:catch 中根据错误信息分类为
timeout(超时)、auth(认证问题)、parse(响应解析错误)和通用error。
2.2.4 结果格式化输出
1 | // 成功的按延迟排序,失败的单独分组 |
输出示例:
1 | Model Latency Benchmark |
2.3 配套 Skill 文件
仅有 Plugin 就够用了,但配合 Skill 可以优化 AI 的调用体验。SKILL.md 内容非常简洁:
1 | --- |
这段 Skill 做了三件事:
- 触发条件(frontmatter 的
description):告诉 AI 在用户问到”模型速度”、”哪个模型最快”、”比较延迟”等场景时激活这个技能。 - 行为指令:直接调用工具,不要多余的解释。
- 输出约束:必须完整输出每一行,禁止 AI 偷懒合并行。这一条非常实用——大模型有时会”贴心地”把结果压缩为”剩余 22 个模型 4.5s-27.3s”,Skill 约束可以阻止这种行为。
2.4 依赖管理
插件的依赖声明在 .opencode/package.json 中:
1 | { |
@opencode-ai/plugin 包含了:
Plugin、Hooks等类型定义tool()辅助函数tool.schema(即 Zod)用于参数校验@opencode-ai/sdk作为传递依赖,提供ctx.client的类型
安装依赖后(bun install 或 npm install),OpenCode 启动时会自动编译并加载插件。
2.5 使用方式
插件安装后,有两种使用方式:
方式一:自然语言触发
在 OpenCode 对话中直接说:
1 | 帮我测一下所有模型的响应速度 |
AI 会识别到 benchmark-models 技能,自动调用 benchmark_models 工具。
方式二:斜杠命令触发
1 | /benchmark-models |
技能自动注册为斜杠命令,效果相同。
方式三:指定 provider 过滤
1 | 只测试 openai 的模型速度 |
AI 会传入 provider: "openai" 参数,只测试该提供商下的模型。
开发体验小结
写完这个插件后,有几点体会:
上手门槛低。Plugin 接口很直觉——接收上下文、返回钩子。tool() 的 Zod schema 设计让参数定义和校验一体化。不需要学习额外的框架概念。
ctx.client 是核心。插件的能力上限取决于 SDK 暴露了多少 API。Benchmark 插件用到了 provider.list()、session.create()、session.prompt()、session.delete() 和 config.get()——这些 API 让插件能深度操作 OpenCode 运行时。
Plugin + Skill 的分离设计合理。代码逻辑和 AI 行为策略分开管理,各自有清晰的职责边界。修改 AI 的调用时机或输出格式不需要改代码,编辑 SKILL.md 即可。
约定优于配置。插件放进 .opencode/plugins/ 自动加载,技能放进 .opencode/skills/ 自动注册为命令,不需要额外的配置文件声明。这降低了不少心智负担。
如果你也在用 OpenCode,不妨试试写一个自己的插件——可以是接入内部 API 的工具、自定义的代码检查器、或者团队特有的工作流自动化。门槛比你想象的低。