OpenCode 插件系统完全指南:用 Hooks 与自定义工具深度拓展 AI 编程终端

OpenCode 插件系统完全指南:用 Hooks 与自定义工具深度拓展 AI 编程终端

引言

在使用 OpenCode 进行日常开发时,你可能会遇到这样的场景:希望 AI 助手在写入文件后自动运行代码格式化;想让每次会话结束时收到系统通知;想要拦截某些敏感文件防止被读取;或者在 Shell 执行前自动注入项目所需的环境变量。这些需求看似琐碎,但每一项都能显著提升你的工作流体验。

OpenCode 的插件(Plugins)系统正是为这类深度定制而生。不同于 Skills(为 AI 注入领域知识)和 Custom Commands(快捷操作入口),插件系统直接接入 OpenCode 的内部生命周期——它允许你在工具执行前后、会话状态变化、Shell 环境准备等关键节点注入自定义逻辑,甚至可以为 AI 助手注册全新的工具函数。

本文将从插件的基本概念入手,结合官方文档和实战场景,带你全面掌握 OpenCode 插件开发。

插件基础:入口与上下文

OpenCode 插件是一个放在 .opencode/plugins/ 目录下的 JavaScript 或 TypeScript 文件。每个插件默认导出一个异步函数,该函数接收一个上下文对象并返回一个包含钩子(hooks)或工具(tools)的对象。

最基础的插件结构如下:

// .opencode/plugins/my-first-plugin.ts
import type { Plugin } from "@opencode-ai/plugin"

export const MyFirstPlugin: Plugin = async (ctx) => {
  return {
    // 在这里注册钩子和工具
  }
}

上下文对象 ctx 提供了几个关键能力:

  • client:与 OpenCode 内部通信的客户端,可用于结构化日志记录等操作
  • project:当前项目信息
  • $:执行 Shell 命令的工具函数
  • directory:当前工作目录路径
  • worktree:Git worktree 路径

插件的加载机制是"放置即生效"——将文件放入 .opencode/plugins/ 目录后,OpenCode 会自动发现并加载它,无需额外配置。

钩子系统:在关键时刻介入

钩子是插件的核心能力。OpenCode 通过钩子暴露出 AI 编程工作流的各个关键节点,让你能在恰到好处的时机执行自定义逻辑。

1. 工具执行钩子 tool.execute.before

这是最实用的钩子之一。它在 OpenCode 即将执行某个工具(如读取文件、运行命令、编辑文件等)之前触发,允许你检查甚至拦截该操作。

经典用例:防止 AI 助手读取 .env 文件,避免敏感信息泄露:

// .opencode/plugins/env-protection.js
export const EnvProtection = async ({ project, client, $, directory, worktree }) => {
  return {
    "tool.execute.before": async (input, output) => {
      if (input.tool === "read" && output.args.filePath.includes(".env")) {
        throw new Error("Do not read .env files")
      }
    },
  }
}

input 参数包含当前工具的名称和执行参数,output 是可修改的输出。通过 throw 抛出错误即可阻止工具执行。你可以据此扩展出更复杂的安全策略,比如:

  • 禁止修改 package-lock.json
  • 拦截对 .git/ 目录的写操作
  • 阻止在特定分支上执行危险命令

2. 会话事件钩子 event

通过 event 钩子,你可以监听 OpenCode 会话的状态变化。例如,下面这个插件在会话空闲时发送 macOS 系统通知:

// .opencode/plugins/notify.js
export const NotificationPlugin = async ({ project, client, $, directory, worktree }) => {
  return {
    event: async ({ event }) => {
      if (event.type === "session.idle") {
        await $`osascript -e 'display notification "Session completed!" with title "opencode"'`
      }
    },
  }
}

这在你让 AI 在后台处理一批任务时非常有用——不需要一直盯着屏幕,任务完成后自然会收到提醒。

3. Shell 环境注入钩子 shell.env

很多项目需要特定的环境变量才能正常运行。shell.env 钩子让你在每次 Shell 执行前自动注入变量,无论是 AI 调用的工具还是你手动打开的终端都能受益:

// .opencode/plugins/inject-env.js
export const InjectEnvPlugin = async () => {
  return {
    "shell.env": async (input, output) => {
      output.env.MY_API_KEY = "secret"
      output.env.PROJECT_ROOT = input.cwd
    },
  }
}

这个钩子特别适合管理那些不便提交到 .env 文件的 API 密钥——将它们放在插件中,利用 .opencode/ 目录的私密性来保护。

4. 会话压缩钩子 experimental.session.compacting

长对话会消耗大量 Token 和上下文窗口。OpenCode 的 /compact 命令会在对话过长时自动压缩历史。这个钩子让你自定义压缩策略:

// .opencode/plugins/compaction.ts
import type { Plugin } from "@opencode-ai/plugin"

export const CompactionPlugin: Plugin = async (ctx) => {
  return {
    "experimental.session.compacting": async (input, output) => {
      output.context.push(`
## Custom Context

Include any state that should persist across compaction:
- Current task status
- Important decisions made
- Files being actively worked on
`)
    },
  }
}

如果你需要完全替换默认的压缩提示词,可以直接设置 output.prompt

// .opencode/plugins/custom-compaction.ts
import type { Plugin } from "@opencode-ai/plugin"

export const CustomCompactionPlugin: Plugin = async (ctx) => {
  return {
    "experimental.session.compacting": async (input, output) => {
      output.prompt = `
You are generating a continuation prompt for a multi-agent swarm session.

Summarize:
1. The current task and its status
2. Which files are being modified and by whom
3. Any blockers or dependencies between agents
4. The next steps to complete the work

Format as a structured prompt that a new agent can use to resume work.
`
    },
  }
}

output.prompt 被设置后,output.context 会被忽略——你获得了对话摘要的完全控制权。这在多 Agent 协作场景中尤其重要,可以确保切换 Agent 时不会丢失上下文。

自定义工具:赋予 AI 新能力

除了拦截和修改现有行为,插件还可以为 AI 助手注册全新的工具函数。当 OpenCode 内置的工具集不足以满足你的需求时,这个能力就显得至关重要。

// .opencode/plugins/custom-tools.ts
import { type Plugin, tool } from "@opencode-ai/plugin"

export const CustomToolsPlugin: Plugin = async (ctx) => {
  return {
    tool: {
      mytool: tool({
        description: "This is a custom tool",
        args: {
          foo: tool.schema.string(),
        },
        async execute(args, context) {
          const { directory, worktree } = context
          return `Hello ${args.foo} from ${directory} (worktree: ${worktree})`
        },
      }),
    },
  }
}

tool 辅助函数提供了三个核心配置:

  • description:工具的功能描述,AI 会根据它判断何时调用
  • args:使用 Zod schema 定义工具的入参
  • execute:工具被调用时执行的函数,可以访问当前目录和 worktree 等上下文

一个需要注意的细节:如果插件注册的工具名与 OpenCode 内置工具重名,插件工具会覆盖内置工具——这给了你极大的灵活性,但也需要谨慎命名。

结合前面的钩子能力,你可以构建出非常强大的组合。比如,注册一个 deploy 工具,再通过 tool.execute.before 在部署前做安全检查:

import { type Plugin, tool } from "@opencode-ai/plugin"

export const DeployPlugin: Plugin = async (ctx) => {
  return {
    tool: {
      deploy: tool({
        description: "Deploy the current project to staging environment",
        args: {
          environment: tool.schema.enum(["staging", "production"]),
          confirm: tool.schema.boolean(),
        },
        async execute(args) {
          if (!args.confirm) return "Deployment cancelled - confirmation required"
          const result = await ctx.$`./deploy.sh ${args.environment}`
          return result.stdout
        },
      }),
    },
    "tool.execute.before": async (input, output) => {
      if (input.tool === "deploy" && input.args.environment === "production") {
        // 生产环境部署前检查当前分支是否为主分支
        const branch = await ctx.$`git branch --show-current`
        if (branch.stdout.trim() !== "main") {
          throw new Error("Production deployment only allowed from main branch")
        }
      }
    },
  }
}

这个例子展示了插件系统的真正威力:先通过 tool.execute.before 在部署执行前进行分支检查,确认安全后再由 deploy 工具执行实际的部署脚本。整个过程对 AI 来说是透明的——它只需要调用 deploy 工具即可。

结构化日志

调试插件时,避免使用 console.log。OpenCode 提供了 client.app.log() 方法用于结构化日志记录:

export const MyPlugin = async ({ client }) => {
  await client.app.log({
    body: {
      service: "my-plugin",
      level: "info",
      message: "Plugin initialized",
      extra: { foo: "bar" },
    },
  })
}

支持的日志级别:debuginfowarnerror。使用结构化日志可以获得更好的搜索和过滤体验,特别是在同时启用多个插件时。

实战场景总览

为了让你更直观地理解插件系统的价值,这里总结几个典型应用场景及其实现方式:

| 场景 | 使用的钩子/能力 | 核心思路 |
|------|-----------------|----------|
| 敏感文件保护 | tool.execute.before | 拦截 .env 等敏感文件的读取操作 |
| 环境变量注入 | shell.env | 在 Shell 执行前自动设置项目所需变量 |
| 会话完成通知 | event | 监听 session.idle 事件触发系统通知 |
| 对话压缩定制 | experimental.session.compacting | 注入自定义上下文或替换压缩提示词 |
| 自定义部署工具 | tool + tool.execute.before | 注册新工具并在执行前做安全校验 |
| 代码规范强制 | tool.execute.before | 拦截文件写入,检查是否符合编码规范 |

总结

OpenCode 的插件系统是其可扩展性的核心支柱。它与 Agent Skills、Custom Tools、Hooks 等机制形成了分层的能力阶梯:Skills 注入知识、Custom Commands 提供快捷入口、Plugins 则深入到引擎层面进行行为定制。

本文介绍的钩子和自定义工具能力覆盖了绝大多数插件开发场景。值得注意的一点是,插件的 tool.execute.before 钩子与 OpenCode 独立的 Hooks 系统在概念上是互补的——Hooks 系统更偏向工作流层面的自动化(如在 commit 前运行测试),而插件钩子更侧重内部行为的细粒度控制。两者的配合使用能让你打造出真正独一无二的 AI 编程环境。

最后,如果你要分发编写的插件,OpenCode 提供了 Ecosystem 机制支持插件的共享和安装。但即使不对外分发,.opencode/plugins/ 目录中那几个小小的 JS 文件,已经足以让你的日常开发效率再上一个台阶。