模型: claude-opus-4-6 (anthropic/claude-opus-4-6) 生成日期: 2026-02-18
前三个实验中,我们从零搭建了 LLM CLI 对话工具、添加了 Tool Use 能力、构建了 MCP Server。这些都是独立项目——它们运行在 OpenCode 之外。本节我们转换视角,深入 OpenCode 内部——通过编写一个 Plugin,直接扩展 OpenCode 的核心能力。
在第 13 章我们详细分析了 Plugin 系统的架构:@opencode-ai/plugin 包定义了类型接口,plugin/index.ts 实现了加载机制,oh-my-opencode 展示了 Plugin 系统的全部威力。现在我们动手实现一个自己的 Plugin。
实现一个名为 opencode-plugin-guardian 的 Plugin,包含以下功能:
chat.message Hook:在每次用户发送消息时,自动注入项目规范(如代码风格要求)
tool.execute.before Hook:在工具执行前记录操作日志
tool.execute.after Hook:在工具执行后记录结果和耗时
自定义工具 project_stats:统计项目的文件数量、代码行数等基本信息
通过这个实验,你将亲手体验 Plugin 的完整开发流程——从类型定义到 Hook 编写,从工具注册到本地调试。
17.4.2 回顾 Plugin 接口
在开始编码之前,让我们回顾 @opencode-ai/plugin 定义的核心类型(详见第 13.1 节):
// 核心类型定义
type Plugin = (input: PluginInput) => Promise<Hooks>
type PluginInput = {
client: ReturnType<typeof createOpencodeClient> // OpenCode SDK 客户端
project: Project // 当前项目信息
directory: string // 当前工作目录
worktree: string // 项目根目录
serverUrl: URL // OpenCode Server URL
$: BunShell // Shell 执行接口
}
Plugin 的本质是一个异步函数:接收 PluginInput 上下文,返回一个 Hooks 对象。Hooks 中的每个字段对应一个生命周期钩子,OpenCode 会在合适的时机调用它们。
衍生解释——什么是生命周期钩子(Lifecycle Hook)?
生命周期钩子是一种软件设计模式,允许外部代码在框架的关键执行节点"挂载"自定义逻辑。就像 React 的 useEffect 在组件挂载/更新时执行,或者 Git 的 pre-commit Hook 在提交前执行一样,OpenCode 的 Plugin Hook 在消息发送、工具执行等关键节点被触发。
核心思想是控制反转(Inversion of Control):你不需要修改 OpenCode 的源码,只需在规定的接口上注册回调函数,框架会在合适的时机调用你的代码。
安装完成后,项目结构如下:
编辑 package.json,设置入口文件和构建配置:
步骤二:编写 Plugin 入口
创建 src/index.ts——这是 Plugin 的核心文件:
这段代码清晰地展示了 Plugin 的四大能力:
创建 src/logger.ts——一个简单的文件日志记录器:
创建 src/tools/project-stats.ts——实现 project_stats 工具:
让我们解析这个工具实现的关键点:
tool() 工厂函数:来自 @opencode-ai/plugin/tool,它接受 description、args、execute 三个字段。args 使用 tool.schema(实际上就是 Zod)来定义参数 Schema,OpenCode 会自动将 Schema 转换为 LLM 能理解的 Tool 定义。
ctx.$(BunShell):Plugin 通过 PluginInput 获得 Bun 的 Shell 执行能力。ctx.$\command`` 是模板字符串语法,类似在终端中执行命令。
context 参数:execute 函数的第二个参数提供了 sessionID、messageID、agent、abort 信号等上下文信息,可以用来做更精细的控制。
OpenCode 支持通过 file:// 协议加载本地 Plugin,无需发布到 npm。
编辑你的项目中的 opencode.json(或 ~/.config/opencode/opencode.json):
然后启动 OpenCode:
你应该能在启动日志中看到 Plugin 被加载的信息。试试以下操作来验证:
验证 chat.message Hook:在项目中创建 .guardian/guidelines.md 文件并写入一些代码规范,然后发送消息——观察 AI 是否遵循了你的规范。
验证工具注册:在对话中让 AI 使用 project_stats 工具,例如输入 "统计一下当前项目的代码量"。
验证日志 Hook:检查 .guardian/operations.log 文件,应该能看到每次工具调用的记录。
日志文件的输出类似这样:
17.4.4 深入理解 Plugin 加载机制
让我们对照源码,看看 OpenCode 是如何加载我们的 Plugin 的。回顾第 13.3 节的分析,packages/opencode/src/plugin/index.ts 中的加载流程:
关键点:
file:// 前缀判断是本地文件还是 npm 包。本地文件直接 import,npm 包先安装到 ~/.cache/opencode/node_modules/ 再导入。
OpenCode 会遍历模块的所有导出(Object.entries(mod)),找到类型为函数的导出并调用。这意味着一个模块可以导出多个 Plugin 函数。
Hook 触发时,所有 Plugin 返回的 Hooks 按注册顺序链式调用——先注册的 Plugin 先执行。这与中间件管道类似。
17.4.5 进阶:添加 event Hook
除了 chat.message 和 tool.execute.*,Plugin 还可以监听 OpenCode 的事件系统。让我们为 Guardian Plugin 添加会话事件监听:
event Hook 接收 OpenCode 事件总线(第 11 章)发布的所有事件。通过 event.type 字段可以区分事件类型,event.properties 包含事件的详细数据。
这个实验性 Hook 允许你修改发送给 LLM 的 System Prompt。例如,根据当前时间注入不同的行为指令:
output.system 是一个字符串数组,最终会被拼接为 LLM 的 System Prompt。你可以向其中 push 新的指令段落。
当 Plugin 开发完成后,你可以将其发布到 npm,这样其他 OpenCode 用户也可以使用:
发布后,用户只需在 opencode.json 中添加包名即可:
OpenCode 会自动通过 BunProc.install() 安装并加载。
通过这个实验,我们掌握了以下核心知识:
packages/plugin/src/index.ts
Plugin、PluginInput、Hooks 类型定义
packages/plugin/src/tool.ts
tool() 函数与 Zod Schema 参数定义
packages/opencode/src/plugin/index.ts
packages/opencode/src/plugin/index.ts
packages/opencode/src/bun/index.ts
packages/plugin/src/index.ts
chat.message 的 output.parts 结构
Plugin 开发的核心原则:
最小侵入:Plugin 通过 Hook 扩展行为,而不是修改 OpenCode 的核心代码。
容错优先:Hook 中的错误不应该导致 OpenCode 崩溃。始终使用 try-catch 包裹可能失败的操作。
上下文感知:通过 PluginInput 获取当前项目信息,而非硬编码路径。使用 ctx.directory 而非 process.cwd()。
按需注入:不要在每条消息中都注入大量内容——这会浪费 Token。根据 input.agent 等条件有选择地注入。
下一个实验中,我们将更进一步——模仿 oh-my-opencode 的核心架构,实现一个多 Agent 编排系统。