本系列文章皆基于开源库Vibecoding 工具 opencode 经典插件 oh-my-opencode 源码进行详细拆解。
源码链接:github.com/code-yeongy…
写在前面
大家好!今天来聊一个很有意思的话题:OpenCode 的插件机制。
用过 Claude Code 或 OpenCode 的同学都知道,原生的 AI 编程助手功能相对单一。但当你装上 oh-my-opencode 这个插件后,突然就有了多智能体协作、后台并行任务、LSP 代码重构等一堆黑科技...
问题来了:它是怎么在不修改 OpenCode 源码的情况下,实现这么多功能升级的?
答案就藏在 Hooks(钩子)机制 里!看完这篇文章,你会彻底明白:
- OpenCode 提供了哪些钩子接口?
- 插件如何利用这些钩子"注入"新功能?
- 完整的执行流程是怎样的?
准备好了吗?我们开始!
一、OpenCode 的钩子接口
1.1 核心机制:一切皆 Hook
OpenCode 的设计很聪明——它把核心功能固定下来,然后在不同的时机抛出钩子,让插件来接管。
用户输入消息
↓
【Hook】chat.message ← 插件可以在这里干预消息
↓
【Hook】chat.params ← 插件可以调整模型参数
↓
发送到 LLM
↓
LLM 返回工具调用
↓
【Hook】tool.execute.before ← 插件可以检查/修改工具参数
↓
执行工具
↓
【Hook】tool.execute.after ← 插件可以处理工具结果
↓
【Hook】event ← 插件可以监听会话事件
↓
返回给用户
核心思想:OpenCode 只负责"搭舞台",插件通过钩子"上台表演"。
1.2 8 个官方 Hooks
| Hook | 触发时机 | 典型用途 |
|---|---|---|
| config | 配置加载时 | 注入自定义配置 |
| tool | 系统启动时 | 注册新工具 |
| chat.message | 收到用户消息时 | 首消息处理、关键词检测 |
| chat.params | 发送请求前 | 调整模型参数 |
| chat.headers | 发送 HTTP 请求前 | 注入请求头 |
| event | 会话状态变化时 | 监听创建/删除/空闲事件 |
| tool.execute.before | 执行工具前 | 权限检查、规则注入 |
| tool.execute.after | 执行工具后 | 输出处理、元数据存储 |
代码示例:插件如何注册钩子
// 插件入口文件 src/index.ts
import type { Plugin } from "@opencode-ai/plugin"
const OhMyOpenCodePlugin: Plugin = async (ctx) => {
return {
// 注册 26 个自定义工具
tool: {
task: createDelegateTask(...),
background_output: createBackgroundOutput(...),
ast_grep_search: createAstGrepTools(...),
// ... 更多工具
},
// 消息处理钩子
"chat.message": async (input, output) => {
if (input.message.includes("ultrawork")) {
setUltraworkMode(input.sessionID)
}
},
// 工具前钩子
"tool.execute.before": async (input, output) => {
checkPermissions(input.tool, input.args)
injectRules(input)
},
// 工具后钩子
"tool.execute.after": async (input, output) => {
truncateOutputIfNeeded(output)
}
}
}
export default OhMyOpenCodePlugin
二、oh-my-opencode 如何通过 Hooks 升级功能
2.1 功能升级对比
未安装插件时的 OpenCode:
用户:帮我搜索代码
OpenCode:我只能用 bash 执行 grep,功能有限...
安装 oh-my-opencode 后:
用户:帮我搜索代码
OpenCode:调用 task 工具 → 启动 Explore 智能体 →
使用 AST-Grep 精准搜索 → 后台并行执行 →
返回结构化结果
怎么做到的? 看下面的钩子映射表:
2.2 功能 → Hook 映射
| oh-my-opencode 功能 | 通过哪个 Hook 实现 | 具体实现方式 |
|---|---|---|
| 26 个新工具 | tool Hook | 注册 task、background_output、ast_grep 等工具 |
| 多智能体调度 | tool Hook | task 工具内部根据 category 路由到不同智能体 |
| 后台并行任务 | tool + event | background_output 工具 + 会话状态监听 |
| LSP 代码重构 | tool Hook | 注册 6 个 lsp_* 工具 |
| AST 代码搜索 | tool Hook | 注册 ast_grep_search/replace 工具 |
| Ultrawork 模式 | chat.message | 检测关键词切换智能体和模型 |
| 文件守卫 | tool.execute.before | 拦截 write/edit 操作进行权限检查 |
| 输出截断 | tool.execute.after | 处理过长的工具输出 |
2.3 核心原理:钩子链式调用
当用户输入 "用 ultrawork 模式重构代码" 时,完整的钩子调用链:
// Hook 1: chat.message - 检测关键词
async chatMessageHook(input, output) {
if (input.message.includes('ultrawork')) {
setSessionAgent(input.sessionID, 'sisyphus') // 切换到主智能体
enableUltraworkMode(input.sessionID) // 开启 ultrawork 模式
}
}
// Hook 2: chat.params - 调整参数
async chatParamsHook(input, output) {
output.effort = 'max' // 最大 effort
output.thinking = true // 启用思考模式
}
// Hook 3: tool.execute.before - 权限检查
async toolBeforeHook(input, output) {
if (input.tool === 'edit') {
guardExistingFile(input.args.filePath) // 防止误修改
}
}
// Hook 4: tool.execute.after - 结果处理
async toolAfterHook(input, output) {
if (output.result.length > 10000) {
output.result = truncate(output.result, 10000) // 截断过长输出
}
}
三、完整执行流程
3.1 插件生命周期
flowchart TD
A[OpenCode 启动] --> B[读取配置中的 plugin 数组]
B --> C[安装/加载插件]
C --> D[调用 Plugin 函数]
D --> E[插件返回 Hooks 对象]
E --> F[注册 Hooks 到 OpenCode]
F --> G[OpenCode TUI 启动]
G --> H[等待用户输入]
H --> I{用户输入}
I --> J[触发 chat.message]
I --> K[触发 chat.params]
J --> L[发送到 LLM]
K --> L
L --> M{需要工具?}
M -->|是| N[触发 tool.execute.before]
N --> O[执行工具]
O --> P[触发 tool.execute.after]
P --> Q[返回结果]
M -->|否| Q
Q --> R[触发 event]
R --> S[显示给用户]
3.2 关键步骤详解
步骤 1-5:插件加载
// OpenCode 内部(伪代码)
async function loadPlugins() {
const config = readConfig('~/.config/opencode/opencode.json')
const pluginNames = config.plugin || []
for (const name of pluginNames) {
// 安装到 ~/.cache/opencode/node_modules/
const pluginPath = await ensurePluginInstalled(name)
// 动态导入
const pluginModule = await import(pluginPath)
const plugin = pluginModule.default
// 创建上下文
const ctx = {
client: { session, tui, ... },
directory: process.cwd(),
project: loadProject(),
$: createShell()
}
// 调用插件,获取 Hooks
const hooks = await plugin(ctx)
// 注册到系统
registerHooks(hooks)
}
}
步骤 6-17:运行时钩子调用
// 用户输入处理(伪代码)
async function handleMessage(sessionID, message) {
// Hook: chat.message
for (const hook of hooks['chat.message']) {
await hook({ sessionID, message }, { message: modifiedMsg })
}
// Hook: chat.params
const params = { effort: 'medium', ... }
for (const hook of hooks['chat.params']) {
await hook({ model: currentModel }, params)
}
// 发送到 LLM
const response = await llm.send({ message: modifiedMsg, params, tools })
// 处理工具调用
if (response.toolCalls) {
for (const call of response.toolCalls) {
// Hook: tool.execute.before
for (const hook of hooks['tool.execute.before']) {
await hook(call, context)
}
// 执行工具
const result = await tools[call.tool](call.args)
// Hook: tool.execute.after
for (const hook of hooks['tool.execute.after']) {
await hook({ ...call, result }, context)
}
}
}
}
四、内部 Hook 系统(进阶)
oh-my-opencode 不光用了 OpenCode 的 8 个 Hooks,内部还实现了 46 个自己的 Hooks!
OpenCode Hooks (8个)
↓
oh-my-opencode 内部 Hooks (46个)
├─ Core Hooks (37)
│ ├─ Session Hooks (23) - 会话管理、模型回退
│ ├─ Tool-Guard Hooks (10) - 权限检查、文件守卫
│ └─ Transform Hooks (4) - 消息转换、思考块验证
├─ Continuation Hooks (7) - Todo 连续性、Ralph Loop
└─ Skill Hooks (2) - 技能系统
为什么这么设计?
因为 OpenCode 的钩子粒度比较粗,oh-my-opencode 需要更细粒度的控制。比如 tool.execute.before 一个钩子要处理很多事情,所以内部再拆分出 10 个 Tool-Guard Hooks。
五、总结
看完这篇文章,你应该明白了:
- OpenCode 提供了 8 个标准 Hooks,让插件可以在不同时机介入
- oh-my-opencode 通过 Hooks 注册了新工具,扩展了功能边界
- 多智能体、后台任务、代码重构等功能都是通过
toolHook 实现的 - 权限检查、输出处理等增强功能通过
tool.execute.before/after实现 - 插件本质上是 Hooks 的集合,OpenCode 负责触发,插件负责实现
一句话概括:OpenCode 是舞台,Hooks 是上台的入口,oh-my-opencode 通过 8 个入口,表演了一场多智能体编排的大戏!
写在最后
咱们这个 oh-my-opencode 插件是如何工作的今天就先讲到这里,然后看看兄弟们是不是还有疑问或者有想看的其他解析,可以留言告诉我哈!!!
欢迎在评论区留言讨论!也请点赞、收藏 + 关注,咱们下期再见!!!