writing-opencode-plugins

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Writing OpenCode Plugins

编写 OpenCode 插件

Use this skill to implement production-quality OpenCode plugins. Treat the repository's exported types and runtime as authoritative because plugin APIs are evolving and public docs may lag.
使用本技能可实现生产级别的 OpenCode 插件。由于插件 API 处于演进中,公开文档可能滞后,因此请以仓库导出的类型和运行时为准。

Start Here

入门指南

  1. Decide which runtime owns the feature.
  2. Read the relevant public type before writing code.
  3. Find one focused in-repository example using the same API.
  4. Implement the smallest target-specific module.
  5. Test loading, behavior, failure, and cleanup in the owning package.
NeedPlugin targetImportConfiguration
Hooks, tools, auth, providers, model parameters, shell environmentServer
@opencode-ai/plugin
opencode.json
or auto-discovered
.opencode/plugins/*.{ts,js}
Commands, keybindings, routes, dialogs, slots, themes, notificationsTUI
@opencode-ai/plugin/tui
Explicit
tui.json
plugin
entry
BothTwo target-only entrypointsBoth imports in separate filesPackage exports
./server
and
./tui
Never export
server
and
tui
from the same module. Do not use server event hooks as a substitute for interactive TUI APIs.
  1. 确定哪个运行时负责该功能。
  2. 编写代码前阅读相关的公开类型定义。
  3. 在仓库中找到一个使用相同 API 的针对性示例。
  4. 实现最小化的目标特定模块。
  5. 在所属包中测试加载、行为、故障处理和清理流程。
需求插件目标导入路径配置方式
钩子、工具、认证、提供者、模型参数、Shell 环境服务器端
@opencode-ai/plugin
opencode.json
或自动发现的
.opencode/plugins/*.{ts,js}
文件
命令、按键绑定、路由、对话框、插槽、主题、通知TUI
@opencode-ai/plugin/tui
tui.json
中显式的
plugin
条目
同时需要服务器端和TUI功能两个独立的目标入口点在不同文件中分别导入上述两个包包导出
./server
./tui
两个入口
切勿从同一模块导出
server
tui
。不要用服务器端事件钩子替代交互式 TUI API。

Verify The Current Contract

验证当前契约

Read these files before implementing unfamiliar behavior:
  • packages/plugin/src/index.ts
    : authoritative server plugin and hook types.
  • packages/plugin/src/tool.ts
    : custom tool schema, context, permission, metadata, attachments, and result types.
  • packages/plugin/src/tui.ts
    : authoritative TUI API and module types.
  • packages/opencode/specs/tui-plugins.md
    : TUI loading, packaging, lifecycle, and API semantics.
  • packages/opencode/src/plugin/shared.ts
    : target validation, IDs, and entrypoint resolution.
  • packages/opencode/src/plugin/loader.ts
    : install, compatibility, and import behavior.
If these disagree with examples or website docs, follow exported types and runtime behavior, then update stale documentation when appropriate.
实现不熟悉的功能前,请阅读以下文件:
  • packages/plugin/src/index.ts
    :权威的服务器端插件和钩子类型定义。
  • packages/plugin/src/tool.ts
    :自定义工具的 schema、上下文、权限、元数据、附件和结果类型。
  • packages/plugin/src/tui.ts
    :权威的 TUI API 和模块类型定义。
  • packages/opencode/specs/tui-plugins.md
    :TUI 加载、打包、生命周期和 API 语义说明。
  • packages/opencode/src/plugin/shared.ts
    :目标验证、ID 和入口点解析逻辑。
  • packages/opencode/src/plugin/loader.ts
    :安装、兼容性和导入行为逻辑。
如果这些文件与示例或网站文档存在冲突,请遵循导出的类型和运行时行为,并在适当的时候更新过时的文档。

Choose A Module Shape

选择模块形态

Prefer the explicit module object for new server plugins:
ts
import type { Plugin, PluginModule } from '@opencode-ai/plugin';

const server: Plugin = async ({ client, directory }, options) => ({
	dispose: async () => {},
});

export default {
	id: 'acme.example',
	server,
} satisfies PluginModule & { id: string };
Legacy server-only local plugins may export a plugin function directly. In a legacy module every distinct named export is interpreted as a plugin, so do not export unrelated constants. Prefer a default module object for new code.
TUI plugins always use a default module object:
tsx
/** @jsxImportSource @opentui/solid */
import type { TuiPlugin, TuiPluginModule } from '@opencode-ai/plugin/tui';

const tui: TuiPlugin = async (api) => {
	api.ui.toast({ message: 'Plugin loaded' });
};

export default {
	id: 'acme.example-tui',
	tui,
} satisfies TuiPluginModule & { id: string };
File plugins require a stable, non-empty
id
. npm plugins may derive the ID from the package name, but an explicit namespaced ID makes state, diagnostics, and collision handling clearer.
新的服务器端插件优先使用显式模块对象:
ts
import type { Plugin, PluginModule } from '@opencode-ai/plugin';

const server: Plugin = async ({ client, directory }, options) => ({
	dispose: async () => {},
});

export default {
	id: 'acme.example',
	server,
} satisfies PluginModule & { id: string };
旧版仅服务器端的本地插件可能直接导出插件函数。在旧版模块中,每个不同的命名导出都会被视为一个插件,因此不要导出无关的常量。新代码优先使用默认模块对象。
TUI 插件始终使用默认模块对象:
tsx
/** @jsxImportSource @opentui/solid */
import type { TuiPlugin, TuiPluginModule } from '@opencode-ai/plugin/tui';

const tui: TuiPlugin = async (api) => {
	api.ui.toast({ message: 'Plugin loaded' });
};

export default {
	id: 'acme.example-tui',
	tui,
} satisfies TuiPluginModule & { id: string };
文件插件需要一个稳定且非空的
id
。npm 插件可以从包名派生 ID,但显式的命名空间 ID 能让状态、诊断和冲突处理更清晰。

Engineering Rules

工程规则

  • Use TypeScript and
    satisfies
    against the public plugin type.
  • Parse and validate
    options
    ; they arrive as unvalidated
    Record<string, unknown>
    .
  • Namespace plugin IDs, command IDs, route names, modes, slot names, and shared KV keys.
  • Use the directory supplied by the plugin or tool context, not
    process.cwd()
    .
  • Honor
    AbortSignal
    for long-running or cancellable work.
  • Use
    client.app.log()
    for structured server logging instead of
    console.log
    .
  • Request permission before sensitive or consequential custom-tool work.
  • Keep notifications privacy-safe; do not expose prompts, secrets, paths, commands, or raw errors.
  • Register only needed hooks and UI resources. Avoid broad event subscriptions when a specific hook exists.
  • Make cleanup bounded, idempotent, and safe after partial initialization.
  • Do not depend on undocumented load order to resolve ownership conflicts.
  • 使用 TypeScript,并针对公开插件类型使用
    satisfies
    进行类型校验。
  • 解析并验证
    options
    ;它们是以未验证的
    Record<string, unknown>
    形式传入的。
  • 为插件 ID、命令 ID、路由名称、模式、插槽名称和共享 KV 键添加命名空间。
  • 使用插件或工具上下文提供的目录,而非
    process.cwd()
  • 对于长时间运行或可取消的任务,要尊重
    AbortSignal
  • 使用
    client.app.log()
    进行结构化服务器端日志记录,而非
    console.log
  • 在执行敏感或有重大影响的自定义工具操作前,请求权限。
  • 确保通知符合隐私安全要求;不要暴露提示词、密钥、路径、命令或原始错误信息。
  • 仅注册所需的钩子和 UI 资源。当存在特定钩子时,避免使用宽泛的事件订阅。
  • 确保清理操作是有界、幂等的,且在部分初始化后也能安全执行。
  • 不要依赖未记录的加载顺序来解决所有权冲突。

Testing Workflow

测试工作流

Server plugin tests belong under
packages/opencode/test/plugin/
or the closest owning subsystem. TUI runtime tests belong under
packages/opencode/test/cli/tui/
; component-level TUI tests may belong in
packages/tui
.
Test at least:
  • valid loading and target/entrypoint selection;
  • configured options and malformed options;
  • the observable behavior, not a duplicate of implementation logic;
  • abort, failure, and partial-initialization behavior;
  • cleanup or disposal;
  • duplicate IDs or registrations when relevant;
  • local file and npm packaging behavior when publishing.
Run tests from the package directory, never the repository root. Use
bun typecheck
from the owning package for type checking.
服务器端插件测试应放在
packages/opencode/test/plugin/
或最接近的所属子系统目录下。TUI 运行时测试应放在
packages/opencode/test/cli/tui/
;组件级别的 TUI 测试可放在
packages/tui
目录下。
至少测试以下内容:
  • 有效的加载和目标/入口点选择;
  • 配置选项和格式错误的选项;
  • 可观察的行为,而非重复实现逻辑;
  • 中止、故障和部分初始化行为;
  • 清理或销毁操作;
  • 相关的重复 ID 或注册情况;
  • 发布时的本地文件和 npm 打包行为。
从包目录运行测试,切勿从仓库根目录运行。使用所属包中的
bun typecheck
进行类型检查。

Review Checklist

评审清单

  • The feature is in the correct server or TUI runtime.
  • Module shape and import path match the target.
  • Server and TUI entrypoints are separate.
  • IDs and persistent keys are stable and namespaced.
  • Options and external data are validated.
  • Hook output mutation preserves other plugins' changes.
  • Tools use context directory, permission, metadata, and abort correctly.
  • TUI keybindings are mode-gated unless intentionally global.
  • TUI resources and custom side effects are disposed.
  • Package exports,
    engines.opencode
    , and config target are correct.
  • Tests cover behavior and lifecycle.
  • 功能位于正确的服务器端或 TUI 运行时中。
  • 模块形态和导入路径与目标匹配。
  • 服务器端和 TUI 入口点相互独立。
  • ID 和持久化键稳定且带有命名空间。
  • 选项和外部数据已验证。
  • 钩子输出的变更不会破坏其他插件的修改。
  • 工具正确使用上下文目录、权限、元数据和中止信号。
  • TUI 按键绑定除非是全局意图,否则应受模式限制。
  • TUI 资源和自定义副作用已被销毁。
  • 包导出、
    engines.opencode
    和配置目标正确。
  • 测试覆盖了行为和生命周期。

References

参考资料

  • Server plugins: hooks, custom tools, lifecycle, and examples.
  • TUI plugins: keymaps, routes, dialogs, slots, state, and lifecycle.
  • Packaging and testing: config, package exports, compatibility, and test locations.
  • 服务器端插件:钩子、自定义工具、生命周期和示例。
  • TUI 插件:键盘映射、路由、对话框、插槽、状态和生命周期。
  • 打包与测试:配置、包导出、兼容性和测试位置。