从插件架构设计到源码解析,带你掌握 OpenCode 扩展开发的核心原理
为什么需要插件? 如果你用 OpenCode 做 AI 开发,可能会好奇:能不能扩展它的能力? 当然可以。OpenCode 提供了插件系统,今天我们就来深入剖析它。 本文基于真实插件 opencode-plugin-langfuse 的源码,带你理解插件的架构设计与实战开发。 |
一、插件系统概述
OpenCode 插件是一个导出 Plugin 函数的 ES 模块。插件本质上是一个函数,接收输入,返回一组钩子(Hooks),从而与 OpenCode 深度集成。
核心概念
概念 | 说明 |
Plugin | 插件入口函数类型 |
PluginInput | OpenCode 传递给插件的上下文 |
Hooks | 插件返回的钩子集合,定义插件行为 |
二、Plugin 接口详解
类型签名
type Plugin = (input: PluginInput) => Promise
PluginInput 上下文 — 当 OpenCode 加载你的插件时,会传递一个丰富的上下文:
type PluginInput = { client: ReturnType
Hooks 返回结构 — 插件通过返回不同的 Hook 来响应 OpenCode 的生命周期事件:
type Hooks = { config?: (input: Config) => Promise
三、Hooks 系统详解
3.1 config 钩子 — 配置验证:在 OpenCode 读取配置文件后调用。
config: async (config) => { if (!config.experimental?.openTelemetry) { client.app.log({ body: { service: "langfuse-otel", level: "warn", message: "OpenTelemetry is disabled" } }); } }
3.2 event 钩子 — 事件响应:监听 OpenCode 内部事件。
event: async ({ event }) => { switch (event.type) { case "session.idle": await processor.forceFlush(); break; case "server.instance.disposed": await sdk.shutdown(); break; } }
3.3 tool 钩子 — 注册自定义工具(需要额外导入 zod):
import { tool } from "@opencode-ai/plugin"; import { z } from "zod"; return { tool: { myTool: tool({ description: "执行自定义操作", args: { name: z.string().describe("名称"), count: z.number().optional().describe("数量"), }, async execute(args, context) { return `执行了 ${args.name}`; }, }), }, };
四、Langfuse 插件实战解析
完整源码解析 — 以下是 opencode-plugin-langfuse 的核心实现:
import { LangfuseSpanProcessor } from "@langfuse/otel"; import type { Plugin } from "@opencode-ai/plugin"; import { NodeSDK } from "@opentelemetry/sdk-node"; export const LangfusePlugin: Plugin = async ({ client }) => { // 1. 读取环境变量 const publicKey = process.env.LANGFUSE_PUBLIC_KEY; const secretKey = process.env.LANGFUSE_SECRET_KEY; const baseUrl = process.env.LANGFUSE_BASEURL ?? "https://cloud.langfuse.com"; const log = (level: "info" | "warn" | "error", message: string) => { client.app.log({ body: { service: "langfuse-otel", level, message } }); }; // 2. 凭证检查 if (!publicKey || !secretKey) { log("warn", "Missing Langfuse credentials"); return {}; } // 3. 初始化 OpenTelemetry const processor = new LangfuseSpanProcessor({ publicKey, secretKey, baseUrl }); const sdk = new NodeSDK({ spanProcessors: [processor] }); sdk.start(); log("info", "OpenTelemetry tracing initialized"); // 4. 返回钩子 return { config: async (config) => { if (!config.experimental?.openTelemetry) { log("warn", "tracing disabled in opencode config"); } }, event: async ({ event }) => { if (event.type === "session.idle") await processor.forceFlush(); if (event.type === "server.instance.disposed") await sdk.shutdown(); }, }; };
工作流程
1 | 启动阶段 读取环境变量 → 创建 processor → 启动 SDK → 返回 { config, event } 钩子 |
2 | 运行时 — config 钩子 检查 experimental.openTelemetry 配置 |
3 | 运行时 — event 钩子 响应 session.idle (flush) / server.disposed (shutdown) |
五、开发自己的插件
5.1 最小插件模板
import type { Plugin } from "@opencode-ai/plugin"; export const MyPlugin: Plugin = async ({ client }) => { const log = (msg: string) => { client.app.log({ body: { service: "my-plugin", level: "info", message: msg } }); }; return { config: async (config) => { log("Plugin loaded!"); }, }; };
5.2 带工具的插件 — 注册自定义工具供 OpenCode 调用:
import { tool } from "@opencode-ai/plugin"; import { z } from "zod"; export const MyPlugin: Plugin = async ({ client }) => { return { tool: { greet: tool({ description: "打招呼", args: { name: z.string().describe("姓名") }, async execute(args, context) { return `你好,${args.name}!`; }, }), }, }; };
5.3 拦截消息 — 监听 chat.message 和 chat.params 钩子:
export const MyPlugin: Plugin = async () => { return { "chat.message": async (input, output) => { console.log("用户消息:", input.message.content); }, "chat.params": async (input, output) => { // 可以修改 LLM 参数 output.params.temperature = 0.7; }, }; };
六、最佳实践
6.1 环境变量检查 — 缺少凭证时返回空 Hooks,插件静默失效:
if (!publicKey || !secretKey) { log("warn", "Missing credentials, plugin disabled"); return {};// 返回空 Hooks,插件静默失效 }
6.2 资源清理 — server.instance.disposed 时必须清理资源,防止数据丢失:
event: async ({ event }) => { if (event.type === "server.instance.disposed") { await sdk.shutdown();// 必须!防止数据丢失 } }
6.3 配置验证 — 提醒用户开启必要的配置项:
config: async (config) => { if (config.experimental?.featureX !== true) { log("warn", "Feature X is not enabled"); } }
七、总结
要点 | 说明 |
入口 | 导出 Plugin 函数的 ES 模块 |
输入 | PluginInput 提供 client、$、目录等上下文 |
输出 | Hooks 定义插件行为(config、event、tool 等) |
关键 | 记得在 server.disposed 时清理资源 |
掌握这些,你就能为 OpenCode 开发各种扩展——从 LLM 监控到自定义工具,无所不能。
相关资源
资源 | 地址 | 备注 |
opencode-plugin-langfuse 源码 | github.com/omercnet/opencode-plugin-langfuse | 真实插件参考 |
OpenCode 官方文档 | opencode.ai | 官方文档 |
@opencode-ai/plugin npm | npmjs.com/package/@opencode-ai/plugin | 插件开发包 |
本文基于 opencode-plugin-langfuse 源码整理 如需动手实践,请参考 GitHub 官方仓库
夜雨聆风