writing-opencode-plugins
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWriting 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
入门指南
- Decide which runtime owns the feature.
- Read the relevant public type before writing code.
- Find one focused in-repository example using the same API.
- Implement the smallest target-specific module.
- Test loading, behavior, failure, and cleanup in the owning package.
| Need | Plugin target | Import | Configuration |
|---|---|---|---|
| Hooks, tools, auth, providers, model parameters, shell environment | Server | | |
| Commands, keybindings, routes, dialogs, slots, themes, notifications | TUI | | Explicit |
| Both | Two target-only entrypoints | Both imports in separate files | Package exports |
Never export and from the same module. Do not use server event hooks as a substitute for interactive TUI APIs.
servertui- 确定哪个运行时负责该功能。
- 编写代码前阅读相关的公开类型定义。
- 在仓库中找到一个使用相同 API 的针对性示例。
- 实现最小化的目标特定模块。
- 在所属包中测试加载、行为、故障处理和清理流程。
| 需求 | 插件目标 | 导入路径 | 配置方式 |
|---|---|---|---|
| 钩子、工具、认证、提供者、模型参数、Shell 环境 | 服务器端 | | |
| 命令、按键绑定、路由、对话框、插槽、主题、通知 | TUI | | |
| 同时需要服务器端和TUI功能 | 两个独立的目标入口点 | 在不同文件中分别导入上述两个包 | 包导出 |
切勿从同一模块导出 和 。不要用服务器端事件钩子替代交互式 TUI API。
servertuiVerify The Current Contract
验证当前契约
Read these files before implementing unfamiliar behavior:
- : authoritative server plugin and hook types.
packages/plugin/src/index.ts - : custom tool schema, context, permission, metadata, attachments, and result types.
packages/plugin/src/tool.ts - : authoritative TUI API and module types.
packages/plugin/src/tui.ts - : TUI loading, packaging, lifecycle, and API semantics.
packages/opencode/specs/tui-plugins.md - : target validation, IDs, and entrypoint resolution.
packages/opencode/src/plugin/shared.ts - : install, compatibility, and import behavior.
packages/opencode/src/plugin/loader.ts
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 - :自定义工具的 schema、上下文、权限、元数据、附件和结果类型。
packages/plugin/src/tool.ts - :权威的 TUI API 和模块类型定义。
packages/plugin/src/tui.ts - :TUI 加载、打包、生命周期和 API 语义说明。
packages/opencode/specs/tui-plugins.md - :目标验证、ID 和入口点解析逻辑。
packages/opencode/src/plugin/shared.ts - :安装、兼容性和导入行为逻辑。
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 . npm plugins may derive the ID from the package name, but an explicit namespaced ID makes state, diagnostics, and collision handling clearer.
id新的服务器端插件优先使用显式模块对象:
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 };文件插件需要一个稳定且非空的 。npm 插件可以从包名派生 ID,但显式的命名空间 ID 能让状态、诊断和冲突处理更清晰。
idEngineering Rules
工程规则
- Use TypeScript and against the public plugin type.
satisfies - Parse and validate ; they arrive as unvalidated
options.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 for long-running or cancellable work.
AbortSignal - Use for structured server logging instead of
client.app.log().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 or the closest owning subsystem. TUI runtime tests belong under ; component-level TUI tests may belong in .
packages/opencode/test/plugin/packages/opencode/test/cli/tui/packages/tuiTest 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 from the owning package for type checking.
bun typecheck服务器端插件测试应放在 或最接近的所属子系统目录下。TUI 运行时测试应放在 ;组件级别的 TUI 测试可放在 目录下。
packages/opencode/test/plugin/packages/opencode/test/cli/tui/packages/tui至少测试以下内容:
- 有效的加载和目标/入口点选择;
- 配置选项和格式错误的选项;
- 可观察的行为,而非重复实现逻辑;
- 中止、故障和部分初始化行为;
- 清理或销毁操作;
- 相关的重复 ID 或注册情况;
- 发布时的本地文件和 npm 打包行为。
从包目录运行测试,切勿从仓库根目录运行。使用所属包中的 进行类型检查。
bun typecheckReview 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, , and config target are correct.
engines.opencode - 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 插件:键盘映射、路由、对话框、插槽、状态和生命周期。
- 打包与测试:配置、包导出、兼容性和测试位置。