fix(opencode): support OpenCode 2 plugin API (V1/V2 dual export) - #1171
Open
Scratchydisk wants to merge 1 commit into
Open
Scratchydisk wants to merge 1 commit into
Scratchydisk wants to merge 1 commit into
Conversation
OpenCode 2's plugin loader ignores the V1 `{ id, server }` shape entirely
(opencode.ai/v2/docs/migrate-v1: "V1 plugin implementations do not run in
V2") — it only recognizes a default export produced by
`Plugin.define({ id, setup })` from `@opencode/plugin`. Verified against a
real opencode@2.0.3 install: pointing `"plugins": ["context-mode"]` at the
currently-published package fails immediately with
`PluginModule.LoadError: Plugin must export a default definition with an
id and an effect or setup function.` The same repro against this fix's
build loads cleanly.
Adds a V2 `setup()` entrypoint alongside the untouched V1 `server()`,
following OpenCode's own documented "support V1 and V2 from one package"
pattern — both are spread into one default export, so OpenCode 1.x and
KiloCode keep calling `server()` unchanged while OpenCode 2 reads `id`/
`setup()` and ignores `server()`. Hook-for-hook mapping (per OpenCode's
migration guide, e.g. tool.execute.before -> ctx.tool.hook("execute.before"),
chat.message -> ctx.session.hook("prompt"), the tool map ->
ctx.tool.transform) is documented inline above `setupContextModePluginV2`.
`@opencode/plugin` is imported dynamically (not statically) so a stale
install missing that dependency degrades to V1-only instead of breaking
the whole module for existing users. Zero changes to V1 behavior — all
120 existing opencode-plugin/adapter tests still pass unchanged, plus 2
new tests locking in the dual-export shape.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #1169 (closed to allow further local testing — same branch, force-pushed since to rebase onto current
main, which is why GitHub wouldn't let me reopen it directly).Summary
OpenCode 2's plugin loader ignores the V1
{ id, server }shape this package'ssrc/adapters/opencode/plugin.tsexports — per OpenCode's own migration guide (opencode.ai/v2/docs/migrate-v1): "V1 plugin implementations do not run in V2." It only recognizes a default export produced byPlugin.define({ id, setup })from the new@opencode/pluginpackage.Repro against the currently-published package (verified against a real
opencode@2.0.3/2.0.4install)ctx_*tools never register, no hooks fire, andopencode plugin listsilently shows nothing for it — worse than a crash, since there's no indication anything is wrong until a user notices the tools are gone.Fix
Adds a V2
setup()entrypoint alongside the existing V1server(), following OpenCode's own documented "support V1 and V2 from one package" pattern (opencode.ai/v2/docs/build/plugins#support-v1): spreadPlugin.define({ id, setup })into the same default-export object that already carriesserver. OpenCode 1.x and KiloCode keep callingserver()— completely unchanged — while OpenCode 2 readsid/setup()off the same object and ignoresserver().The V2
setup()re-implements all five hooks using OpenCode 2's documented mapping (opencode.ai/v2/docs/build/plugins/migrate-v1):tool.execute.beforectx.tool.hook("execute.before", ...)tool.execute.afterctx.tool.hook("execute.after", ...)event(message.updated)ctx.event.subscribe()chat.messagectx.session.hook("prompt", ...)experimental.session.compactingctx.session.hook("compaction", ...)experimental.chat.system.transformctx.session.hook("context", ...)toolmapctx.tool.transform(editor => editor.add(...))The custom
ctx_*tools now declare JSON Schema (required by V2) instead of a Zod shape, reusing the existingzod3ShapeToV4conversion (added for #574) plus Zod v4's owntoJSONSchema().@opencode/pluginis imported dynamically (await import(...)inside a try/catch), not as a static top-level import, so a stale/partial install missing that dependency degrades to V1-only instead of breaking the whole module for existing OpenCode 1.x/KiloCode users.Verification
This branch has now been tested two ways:
opencode@2.0.3package cache and confirmed the unmodified published package fails with the exact error above while this branch's build loads with no error.~/.cache/opencode/.../node_modules/context-modeon a machine actually running this plugin day-to-day (realopencode.jsonwith"plugins": ["context-mode", ...], real provider credentials), restarted the OpenCode background service (which picked it up along with an unrelated OpenCode 2.0.3→2.0.4 self-update), and confirmed:"failed to load plugin"entries forcontext-modein the service log (previously, every single load attempt failed with the schema error above).ctx_statstool and got back a fully-formed real report pulled from the actual session database (event counts, per-tool capture stats, lifetime cost) — end-to-end proof thatPlugin.define/setup()→ctx.tool.transform→ tool execution → session DB all work correctly under a real OpenCode 2 session.All 122 pre-existing
opencode-plugin/adapters/opencode/parse-opencode-usagetests pass unchanged — zero behavior change to the V1/KiloCode path. Plus 2 new tests locking in the dual-export shape.npm run typecheckandnpm run buildboth clean.Known gap for maintainer review
The exact field names on OpenCode 2's
ToolExecuteBefore/ToolExecuteCompleted/ToolExecuteFailedevent types aren't spelled out in the public docs beyond short examples (event.tool,event.input,event.status,event.result,event.error). I inferredevent.sessionIDby analogy with every other hook interface OpenCode documents (SessionRequestHook,SessionRetryHook,PermissionEvaluation, etc. all expose it), and defensively no-op when it's absent. The routing-enforcement path (tool.execute.before/after→ session event capture) hasn't yet been exercised against a live agent turn that actually triggers a blocked/modified tool call — worth a maintainer smoke test before release, though thectx_*tool-execution path itself (registration, JSON Schema conversion, execute, result handling) is now confirmed working live.Test plan
npm run typechecknpm run buildnpx vitest run tests/opencode-plugin.test.ts tests/adapters/opencode.test.ts tests/session/parse-opencode-usage.test.ts(122 passed)ctx_statstool call succeeds end-to-end against a real session DBtool.execute.before/after(routing enforcement + session capture) against a live OpenCode 2 session with a real blocked/modified tool call