Skip to content

Support OpenCode 2 plugin API (dual-shape default export) - #47

Closed
San4es wants to merge 1 commit into
Mte90:masterfrom
San4es:feat/opencode-v2-support
Closed

San4es wants to merge 1 commit into
Mte90:masterfrom
San4es:feat/opencode-v2-support

Conversation

@San4es

@San4es San4es commented Oct 21, 2026

Copy link
Copy Markdown

Summary

Adds support for the OpenCode 2 plugin API while keeping OpenCode 1 compatibility, via a dual-shape default export — the same pattern used by other plugins that migrated (e.g. type-inject).

The problem: OpenCode 2 rejects V1 plugin factories with Plugin must export a default definition with an id and an effect or setup function, so the plugin fails to load with a LoadError on OpenCode 2.

The fix: export default { id, server, setup }

  • OpenCode 1 (>= 1.3.4) keeps loading the existing plugin through the server field — AutoResumePlugin itself is unchanged (the named export stays too, so nothing changes for direct imports).
  • OpenCode 2 calls the new setup entrypoint (src/v2.ts), which:
    • instantiates the V1 plugin with a synthesized OpenCodeClient (the V2 context has no client field, so one is created lazily via @opencode/client with a graceful fallback),
    • bridges the returned V1 hooks onto V2 APIs:
      • tool.execute.before / tool.execute.after → ctx.tool.hook(...)
      • event + chat.message → ctx.event.subscribe() pump
      • tool.task_complete → ctx.tool.transform(...) (registers the task_complete tool)
    • skips V1-only hooks (config, command.execute.before) since OpenCode 2 has no equivalent for them.

Verification

The V2 setup is a port of a shim that has been running live on OpenCode 2.1.2: the plugin loads without errors and registers the task_complete tool in real sessions.

  • bun run build OK
  • bun test: 599 pass / 0 fail across 37 files
  • src/index.exports-guard.test.ts updated for the new default-export shape (still guards against non-Plugin exports crashing the V1 host)

Notes

  • New dependency: @opencode/client (used by the V2 setup to synthesize the client the V1 plugin expects). Existing V1 users get it transitively but the V1 code path is untouched.
  • Version bumped to 1.2.0 (minor — V1 behaviour preserved via server).

OpenCode 2 rejects V1 plugin factories ("must export a default
definition with an id and a setup function"). Ship a dual-shape
default export: { id, server, setup }.

- OpenCode 1 (>= 1.3.4) keeps loading the existing plugin through
  the "server" field (AutoResumePlugin unchanged, named export too)
- OpenCode 2 calls the new "setup" entrypoint (src/v2.ts), which
  instantiates the V1 plugin with a synthesized OpenCodeClient and
  bridges the returned V1 hooks onto V2 APIs:
  tool.execute.before/after -> ctx.tool.hook,
  event + chat.message -> ctx.event.subscribe(),
  tool.task_complete -> ctx.tool.transform
- V1-only hooks (config, command.execute.before) are skipped, as
  OpenCode 2 has no equivalent
- add @opencode/client dependency (used by the V2 setup to
  synthesize the client the V1 plugin expects)
- update the exports-guard test for the new default-export shape

The V2 setup is a port of a shim that has been verified live on
OpenCode 2.1.2: plugin loads without errors and registers the
task_complete tool.
@San4es

San4es commented Oct 21, 2026

Copy link
Copy Markdown
Author

Thanks for considering this PR! A quick note on how it relates to the other v2 PRs in the tracker (#20, #37, #41, #44) — I see #37 is the chosen direction, and we're happy to close this one if that stands.

The one thing #47 adds to the discussion is an adapter variant that avoids the "silently stubbed surfaces" concern raised about #44:

  • Real client, not stubs. The v2 setup (src/v2.ts) synthesizes an actual OpenCodeClient via @opencode/client (self-discovering, lazy, graceful fallback) and hands it to the unchanged v1 AutoResumePlugin. So session.list discovery, app.log, todo reads, etc. all keep working on v2 hosts — the v1 engine runs on the same APIs it expects, with no stubbed returns.
  • Dual-shape default export { id, server, setup } in dist/index.js: v1 hosts (>= 1.3.4) load via server, v2 hosts call setup — no exports map change and no TS entrypoint loading needed, and the v1 code path stays byte-identical.
  • Live-verified: an equivalent of this bridge has been running in a real OpenCode 2.1.2 setup — the plugin loads with no LoadError and registers the task_complete tool.

That said, we fully understand if the full port in #37 is the preferred long-term answer (it covers things a bridge can't express as cleanly, like v2's tool-lifecycle events, plus todo state from the session log). If #37 is landing soon, we're glad to close this — just say the word. Until then, leaving it up in case the thinner single-engine option is useful as a comparison point.

@Mte90

Mte90 commented Oct 26, 2026

Copy link
Copy Markdown
Owner

there is already #37 so anything that is wrong in that PR needs to be documented in that PR otherwise following everything for me that I still don't use opencode v2 is crazy.

@Mte90 Mte90 closed this Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants