Skip to content

feat: opencode v2 plugin support + thinking-binding-controls beta for 5.x models - #37

Open
TomasRichtr wants to merge 2 commits into
dotCipher:mainfrom
TomasRichtr:v2-plugin-support
Open

TomasRichtr wants to merge 2 commits into
dotCipher:mainfrom
TomasRichtr:v2-plugin-support

Conversation

@TomasRichtr

Copy link
Copy Markdown

Summary

Two fixes for running this plugin on OpenCode 2.x, both hit in practice on opencode 2.0.30 with Claude Pro/Max OAuth:

  1. OpenCode v2 plugin support (closes OpenCode v2 support plans? #34) — v2 rejects the current entrypoint with:
    Plugin must export a default definition with an id and an effect or setup function
    
    V1 hooks never run under v2; moving the file or renaming the config entry is not enough.
  2. thinking-binding-controls-2026-08-01 beta flag (ref Beta header rewrite breaks Opus 5.5, Sonnet 5.5 and Fable 5.1 on OpenCode 1.18.x #33) — OpenCode v2 natively adds thinking.block_binding to requests for models flagged supportsThinkingBlockBinding (claude-haiku-5-5 and other 5.x models). The API only accepts that field under the new beta, so with the old flag set those requests fail with:
    thinking.adaptive.block_binding: Extra inputs are not permitted
    
    The flag exists in Claude Code ≥ 2.1.2xx (verified against the 2.1.296 install).

Changes

fix: commit (constants.ts)

  • Appends thinking-binding-controls-2026-08-01 to BETA_FLAGS, with a comment on how to re-derive the flag list from a Claude Code install (grep -aoE ... over the binary).

feat: commit (index.ts, tests, README, package.json)

  • Adds setup() next to server() on the default export ({ id, setup, server }). opencode ≥ 2.0 uses setup() and ignores server(); 1.18.x keeps using server(). No new runtime dependencies.
  • Maps the same behavior onto the v2 APIs:
    • auth.loader custom fetch → session.hook("http.request") (header/body/URL rewrite, provider-scoped to anthropic)
    • auth.loader response wrapping → session.hook("http.response") (SSE tool-name/argument translation)
    • experimental.chat.system.transform → session.hook("context")
    • auth.methods OAuth → integration.transform registering an oauth method override on the anthropic integration: authorize (Claude CLI keychain fast path, then browser PKCE via the existing local callback server) and a layered refresh callback whose result opencode persists
  • Extracts the V1 fetch's body/URL/response phases into shared helpers (transformClaudeRequestBody, normalizeAnthropicApiUrl, wrapAnthropicResponseWithSse) so v1 and v2 share one wire-format implementation; splits refreshAuth into a pure refreshTokensLayered (the v2 refresh callback must return tokens rather than write the auth store itself, since v2 has no client.auth.set equivalent — opencode persists what the callback returns).
  • Documented v2 divergences in the source: no model injection (the v2 models.dev catalog already lists Claude models), no inline 429 token rotation (Claude refresh tokens are single-use — rotating them anywhere but the persisted refresh callback invalidates the stored token), and rewrites apply to anthropic.com hosts only.
  • Tests: new plugin module shape case asserts the { id, setup, server } shape, that the method override targets anthropic/oauth with async authorize/refresh (a plain-object return crashes v2's credential resolver — that one cost a debugging session), and that the three provider-scoped hooks register.

Testing

  • npm run typecheck and npm test clean (123 tests).
  • Both changes verified end-to-end on opencode 2.0.30 / macOS against a real Claude Pro account: plugin loads without the v2 error, expired stored credentials auto-refresh through the keychain layer and the refreshed credential is persisted, and requests on both claude-haiku-4-5 and claude-haiku-5-5 succeed (the latter previously 400ing on block_binding).

Versioning

Bumped to 1.11.0 — main already requires the { id, server } entrypoint (opencode ≥ 1.18.29), so adding setup breaks nothing that currently works. If you'd rather ship the v2 support as 2.0.0 (e.g. to drop the V1 path eventually), happy to change it.

Richtr Tomáš added 2 commits October 16, 2026 09:22
Claude Code >= 2.1.2xx sends thinking-binding-controls-2026-08-01. OpenCode
v2 natively adds thinking.block_binding to requests for models flagged
supportsThinkingBlockBinding (claude-haiku-5-5 and other 5.x models). The
API only accepts that field under the new beta, so with the old flag set
those requests fail with:

  thinking.adaptive.block_binding: Extra inputs are not permitted

Ref: dotCipher#33
OpenCode 2.0 loads plugins through a new contract: the default export must
carry an id plus a setup() (Promise) or effect() (Effect) function, and V1
hooks never run there — the plugin fails with:

  Plugin must export a default definition with an id and an effect or
  setup function

This adds a v2 setup() next to the existing v1 server() (which opencode
1.18.x keeps using) and maps the same behavior onto the v2 APIs:

- auth.loader custom fetch        -> session.hook("http.request")
- auth.loader response wrapping   -> session.hook("http.response")
- experimental.chat.system.transform -> session.hook("context")
- auth.methods OAuth              -> integration.transform with an oauth
  method override on the anthropic integration: authorize (Claude CLI
  keychain fast path, then browser PKCE with the local callback server)
  and a layered refresh callback whose result opencode persists

Divergences under v2 (documented in the source): no model injection (the
v2 catalog already lists Claude models), no inline 429 token rotation
(Claude refresh tokens are single-use — only the persisted refresh
callback may rotate them), and rewrites apply to anthropic.com hosts
only.

The V1 fetch's body/URL/response phases are extracted into shared helpers
(transformClaudeRequestBody, normalizeAnthropicApiUrl,
wrapAnthropicResponseWithSse) so v1 and v2 share one wire-format
implementation; refreshAuth is split into a pure refreshTokensLayered so
the v2 refresh callback can return tokens for opencode to persist.

Closes dotCipher#34
Ref: dotCipher#33
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.

OpenCode v2 support plans?

1 participant