拓冰建站拓冰建站
首页 / 资讯中心 / 正文

OpenCode V2 Effect 插件 API 详解:hook、transform 与 reload 的完整实践指南

OpenCode V2 Effect 插件 API 详解hook、transform 与 reload 的完整实践指南【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencode本文基于 OpenCode 仓库中的 V2 Effect 插件 API 文档packages/plugin/src/v2/effect/README.md展开系统讲解如何通过opencode-ai/plugin/v2/effect中的define定义插件、利用 transform hook 参与 agent/catalog/command 等有状态域的构建、通过 runtime hook 拦截运行时操作以及使用reload重建域状态。读完后你能够独立编写一个具备 hook 注册、作用域生命周期管理和域重载能力的 V2 插件并理解其底层的 Scope 语义与重建流程。一、API 定位两个进程内能力Effect 插件 API 为插件提供两个进程内in-process能力hook在 OpenCode 的扩展点extension point上安装行为reload针对某个有状态域stateful domain重新执行其全部 transform hook。文档同时明确了一个边界公共的 server client 将单独暴露目前有意地不包含在PluginContext中。也就是说V2 Effect 插件当前只处理进程内的扩展点而不是直接操作远程服务端。整个 API 的入口与导出定义在 index.tsexport type { PluginContext } from ./context.js export { define } from ./plugin.js export type { Plugin } from ./plugin.js即对外只有三个公共符号define、PluginContext类型和Plugin类型。该子路径的导出配置见 package.json./v2/effect: ./src/v2/effect/index.ts因此插件的导入方式固定为import { define } from opencode-ai/plugin/v2/effect二、定义一个插件文档给出的最小插件示例如下import { define } from opencode-ai/plugin/v2/effect import { Effect } from effect export const Plugin define({ id: example, effect: Effect.fn(function* (ctx) { yield* ctx.catalog.transform((catalog) { catalog.provider.update(example, (provider) { provider.name Example }) }) }), })从源码结构看plugin.tsdefine就是一个恒等函数真正约束插件形状的是Plugin接口export interface PluginR Scope.Scope { readonly id: string readonly effect: (context: PluginContext) Effect.Effectvoid, never, R }其中几个要点值得注意id: string是插件的唯一标识后续ctx.plugin.remove(id)依赖它来卸载插件effect接收PluginContext返回一个 Effect 流程错误类型固定为never需求类型默认是Scope.Scope——这直接对应下面第四节的生命周期语义插件 setup命令式地注册 hook不返回任何 hook 对象。文档特别强调了这一点“Plugin setup registers hooks imperatively. It does not return a hook object.”插件还提供PluginDomainplugin.ts允许运行时动态管理插件集合export interface PluginDomain { readonly add: (plugin: Plugin) Effect.Effectvoid readonly remove: (id: string) Effect.Effectvoid }对应ctx.plugin.add(plugin)/ctx.plugin.remove(id)两个操作。三、PluginContext插件能拿到什么PluginContext的完整形状定义在 context.tsexport interface PluginContext { readonly options: PluginOptions readonly agent: AgentHooks Reload readonly aisdk: AISDKHooks readonly catalog: CatalogHooks Reload readonly command: CommandHooks Reload readonly integration: IntegrationHooks Reload readonly plugin: PluginDomain readonly reference: ReferenceHooks Reload readonly skill: SkillHooks Reload }按用途可以分成三类成员能力说明options读取配置提供给插件的配置类型见下文agent/catalog/command/integration/reference/skilltransform reload六个有状态域均支持 transform hook 与reload()aisdkruntime hook拦截 AI SDK 的sdk/language生成过程plugin插件域管理add/remove动态装卸插件其中ctx.options的类型很简单就是一个只读键值映射options.tsexport type PluginOptions ReadonlyRecordstring, any也就是说OpenCode 配置中写在插件名下的 options 会原样透传给effect由插件自行解释字段含义。四、注册生命周期Scope 与 dispose理解 hook 机制的关键在 registration.tsexport interface Registration { readonly dispose: Effect.Effectvoid } export interface Reload { readonly reload: () Effect.Effectvoid } export type HooksSpec { readonly [Name in keyof Spec]: ( callback: (input: Spec[Name]) Effect.Effectvoid | void, ) Effect.EffectRegistration, never, Scope.Scope }这解释了文档中的三句描述注册是带作用域的每个 hook 注册调用返回EffectRegistration, never, Scope.Scope即注册结果绑定到一个 EffectScope。这就是为什么 README 说“Registrations are owned by the plugin scope”——插件effect流程所在的作用域持有所有注册项作用域关闭时它们被自动撤销可以提前撤销Registration自带dispose: Effectvoid因此一个注册可以在作用域关闭前手动移除“a registration may also be removed early throughdispose”callback 可以同步也可以 Effect 化(input) Effectvoid | void两种签名都被接受简单场景直接写同步回调即可。HooksSpec是一个映射类型Spec里列出的每个键都变成一个 hook 注册函数。以 agent 域为例agent.tsexport type AgentHooks Hooks{ transform: AgentDraft }所以ctx.agent.transform(callback)就是注册一个接收AgentDraft的转换回调。五、Transform Hooks参与有状态域的构建Transform hook 用于向有状态域贡献状态。文档示例yield * ctx.agent.transform((agent) { agent.update(reviewer, (item) { item.description Reviews code for regressions item.mode subagent }) })其重建语义由文档明确给出OpenCode 在任一 transform 被注册或撤销时重建该域重建从全新的域状态出发按注册顺序依次执行所有活跃的 transform。这是一个“全量重建”rebuild-from-scratch模型而不是增量修补——每个 transform 都应当假设自己面对的是初始状态。可用的 transform hook 按域命名空间组织共六个ctx.agent.transform ctx.catalog.transform ctx.command.transform ctx.integration.transform ctx.reference.transform ctx.skill.transform从源码结构看每个域对应一个源文件agent.ts、catalog.ts、command.ts、integration.ts、reference.ts、skill.tshook 的 draft 参数形状定义在其中。agent 域AgentDraftAgentDraftagent.ts暴露五个方法操作对象是 SDK 类型AgentV2Infoexport interface AgentDraft { list(): readonly AgentV2Info[] get(id: string): AgentV2Info | undefined default(id: string | undefined): void update(id: string, update: (agent: AgentV2Info) void): void remove(id: string): void }list()/get(id)枚举与查询当前域中的 agentdefault(id?)设置或清空默认 agentupdate(id, fn)就地修改某个 agent如示例中的item.description、item.mode subagentremove(id)从域中删除。catalog 域CatalogDraftcatalog 域结构最丰富分为 provider 与 model 两个子面catalog.tsexport interface CatalogDraft { readonly provider: { list(): readonly CatalogProviderRecord[] get(providerID: string): CatalogProviderRecord | undefined update(providerID: string, update: (provider: ProviderV2Info) void): void remove(providerID: string): void } readonly model: { get(providerID: string, modelID: string): ModelV2Info | undefined update(providerID: string, modelID: string, update: (model: ModelV2Info) void): void remove(providerID: string, modelID: string): void readonly default: { get(): { providerID: string; modelID: string } | undefined set(providerID: string, modelID: string): void } } }注意provider.list()返回的CatalogProviderRecord同时携带provider: ProviderV2Info和models: ReadonlyMapstring, ModelV2Info方便 transform 一次性查看某 provider 下全部模型。model.default是一个特殊的子对象支持get()/set()读写默认 provider/model 组合——这是 README 开头示例中catalog.provider.update(example, ...)之外另一个常见的定制点。六、Runtime Hooks拦截实时操作与 transform 不同runtime hook 不重建域状态而是拦截“活着的”操作。文档给出的示例是替换 AI SDK 的 provider 实例并接管 language model 生成yield * ctx.aisdk.sdk( Effect.fn(function* (event) { if (event.package ! ai-sdk/xai) return const mod yield* Effect.promise(() import(ai-sdk/xai)) event.sdk mod.createXai(event.options) }), ) yield * ctx.aisdk.language((event) { if (event.model.providerID ! xai) return event.language event.sdk.responses(event.model.api.id) })两个 hook 的 event 形状在 aisdk.ts 中有精确定义export type AISDKHooks Hooks{ sdk: { readonly model: ModelV2Info readonly package: string readonly options: Recordstring, any sdk?: any } language: { readonly model: ModelV2Info readonly sdk: any readonly options: Recordstring, any language?: LanguageModelV3 } }从字段设计可以看出流水线关系sdkhook 的 event 中sdk字段是可选输出sdk?: anyplugin 按event.package判断是否要接管然后动态import对应 AI SDK 包并赋给event.sdklanguagehook 的 event 里sdk变为必填输入sdk: any说明它一定运行在sdk解析之后language是可选输出类型为ai-sdk/provider的LanguageModelV3若插件不设置language系统走默认解析路径设置后则以插件结果为准。文档还规定了执行顺序hooks 按注册顺序串行执行后面的 hook 能观察到前面 hook 所做的变更“Hooks run sequentially in registration order. Later hooks observe mutations made by earlier hooks.”。示例中先注册sdk、再注册language正是利用了这一点——第二个 hook 才能拿到第一个 hook 写入的event.sdk。七、Reload域重载的正确姿势当 transform 捕获的外部数据发生变化时需要重载受影响域。文档示例let data yield * loadCatalog() yield * ctx.catalog.transform((catalog) { applyCatalog(data, catalog) }) data yield * loadCatalog() yield * ctx.catalog.reload()可用的 reload 操作与 transform 一一对应ctx.agent.reload() ctx.catalog.reload() ctx.command.reload() ctx.integration.reload() ctx.reference.reload() ctx.skill.reload()这里有一个容易误解的点文档专门澄清了reload 属于域而不是某一次注册。ctx.catalog.reload()会重新执行当前全部活跃的 catalog transform 并发布重建后的 catalog——即使没有任何新注册或 dispose 发生。这为“transform 闭包捕获了可变数据、数据变了但注册没变”的场景提供了手动触发重建的手段。Reload接口的定义也很简单registration.tsexport interface Reload { readonly reload: () Effect.Effectvoid }每个域类型都以 Reload交叉出这个成员见 context.ts所以六个域的transform与reload总是成对出现。八、实践要点小结与源码索引综合文档与源码编写一个 V2 Effect 插件时可以遵循以下要点用define({ id, effect })定义插件effect内用Effect.fn组织流程ctx.options读取配置需要修改域内容agent、catalog、command、integration、reference、skill时注册transform并保持“每次重建都从全新状态执行”的幂等假设需要干预 AI SDK 实例或 language model 生成时使用ctx.aisdk.sdk/ctx.aisdk.language并注意注册顺序决定数据流注册项生命周期由插件作用域托管作用域关闭自动清理也可通过返回的Registration提前dispose外部数据变化后调用所属域的ctx.domain.reload()触发全量重建与发布。相关源码与文档索引API 文档本文主体README.md插件定义与 PluginDomainplugin.ts生命周期与 Hooks 映射类型registration.ts上下文结构context.ts各域 draft 类型agent.ts、catalog.ts、aisdk.ts配置类型options.ts导出路径配置package.json同目录还有一份实现计划文档 PLAN.md以及 Promise 风格的姊妹 API导出子路径./v2/promise见 packages/plugin/src/v2/promise/README.md两者可对照阅读。适用前提说明以上结论均基于当前仓库中opencode-ai/pluginv2 子包的实际源码effect与opencode-ai/sdk均以 workspace 依赖方式引入effect依赖以 catalog 版本锁定package.json具体版本以仓库根目录锁文件为准。【免费下载链接】opencodeThe open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/openc/opencode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门