Skip to content

feat: Opencode v2 support - #274

Open
heymaaz wants to merge 4 commits into
griffinmartin:mainfrom
heymaaz:opencode-v2-support
Open

feat: Opencode v2 support#274
heymaaz wants to merge 4 commits into
griffinmartin:mainfrom
heymaaz:opencode-v2-support

Conversation

@heymaaz

@heymaaz heymaaz commented Aug 18, 2026

Copy link
Copy Markdown

Summary

Adds support for opencode v2

Related issue

Closes #245

Testing

I have been maintaining a fork and other people have been using that plugin: https://www.npmjs.com/package/opencode-claude-auth-v2 , also can see that I fixed issues with the v2 plugin a couple time in #245

Checklist

  • PR title follows Conventional Commits (feat:, fix:, docs:, chore:, etc.)
  • make all passes locally (runs lint, build, and test)
  • Tests added or updated where applicable
  • README or docs updated where applicable

Everything below is AI generated:

OpenCode 2 replaced the plugin auth API this project was built on (auth.loader + a custom fetch override) with an Integration/Credential/Provider registration, and moved the Anthropic provider off a builtin fetch hook onto an AI-SDK-compatible package. This PR reworks the plugin's entrypoint around that new API, using @opencode-ai/plugin's plain Promise-based registration (Plugin.define({ setup: async (ctx) => ... })) rather than its parallel Effect-based one — this plugin has no other use for Effect, and depending on it directly means tracking its exact version against whatever OpenCode 2 itself bundles; the Promise API avoids that dependency entirely.

This targets OpenCode 2 only and drops OpenCode 1 support — see "Rollout" below before merging.

Rollout — needs a decision before merging

This repo's commit convention (feat!:) plus its release-please-style automation (chore(main): release X) means merging this will auto-bump a major version. Existing OpenCode 1 users are pinned to opencode-claude-auth@latest per the current README/installation.md — if the major version publishes under the latest npm tag, every OpenCode 1 user's @latest plugin breaks silently on their next OpenCode restart, since this version won't load under OpenCode 1's plugin API at all.

Worth deciding explicitly how to roll this out — e.g. gate the initial release behind a next tag until OpenCode 1 usage has migrated, or publish with clear release notes calling out that @latest now requires OpenCode 2.

OpenCode 2 replaces the auth.loader/fetch-override plugin API with an
Effect-based Integration/Credential/Provider registration, and moves the
Anthropic provider to an AI-SDK-compatible package instead of a builtin
fetch hook. Rework the plugin's entrypoint around that API:

- Register a "claude-code" OAuth method on the built-in Anthropic
  integration via ctx.integration.transform, surfaced as "Import Claude
  Code subscription" under /connect
- Point the Anthropic provider's model package at a new AI-SDK-compatible
  provider (src/provider.ts, built on @ai-sdk/anthropic's createAnthropic)
  wired in via ctx.catalog.transform, reusing the existing tool-name
  translation, SSE buffering, beta-flag selection, and billing-header logic
- Inject the Claude Code system identity via ctx.session.hook("context")
  instead of experimental.chat.system.transform
- Re-sync from the keychain before ever attempting a network refresh: since
  OpenCode 2 persists the OAuth credential itself and calls the plugin's
  refresh hook on its own schedule, a refresh token that's gone stale
  because `claude` rotated credentials independently of this connection
  (an interactive re-login, its own periodic refresh, ...) would otherwise
  fail with invalid_grant forever. The keychain is the real source of
  truth, so check it first and adopt whatever is there when it disagrees
  with what OpenCode handed back

This targets OpenCode 2 only and is not backwards compatible with OpenCode
1's plugin API — see the updated README/installation.md for the
`plugin` -> `plugins` config key change and migration notes.

Credential source reading (keychain.ts, credentials.ts), tool-name
translation, SSE handling, beta-flag selection, and billing-header signing
(transforms.ts, signing.ts, betas.ts) are all unchanged.
- Fix the config key: OpenCode 2 uses "plugins" (plural), not OpenCode 1's
  "plugin" (singular) — the previous instructions would silently no-op on
  OpenCode 2
- Document the /connect flow (Anthropic -> Import Claude Code subscription)
  that replaces OpenCode 1's fully-automatic auth
- Rework "How it works" and "How it works (technical)" for the new
  Integration/Credential/Provider registration and the keychain-resync
  behavior in the refresh hook; drop the auth.json fallback sync and
  5-minute background timer, which no longer exist under OpenCode 2's own
  credential persistence
- Add a troubleshooting entry for OpenCode 2 replaying a stale stored
  refresh token after an out-of-band `claude` re-login
Copilot AI lite review requested due to automatic review settings August 18, 2026 07:19

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the plugin to support OpenCode v2’s Effect-based plugin/auth APIs and introduces an AI-SDK-compatible Anthropic provider wrapper to use Claude Code subscription credentials.

Changes:

  • Replaces the OpenCode v1 plugin/auth implementation with an OpenCode v2 Plugin.define integration + OAuth credential method.
  • Adds an Anthropic provider wrapper (src/provider.ts) that injects Bearer auth, required headers, request/stream transforms, and retry behavior.
  • Updates docs/tests/scripts for OpenCode 2 usage and dependency set (AI SDK + Effect + OpenCode beta plugin API).

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/provider.ts New AI-SDK Anthropic provider wrapper + Claude subscription transport logic.
src/provider.test.ts Unit tests for provider header/body/stream transformations and failure behavior.
src/index.ts New OpenCode v2 plugin entrypoint using Effect integration/catalog/session hooks and OAuth method registration.
src/index.test.ts Removes legacy v1-focused tests.
scripts/test-headless.ts Updates headless E2E runner to use OpenCode 2 command/config keys and tighter assertions.
README.md Updates documentation for OpenCode 2 setup and /connect flow.
pnpm-lock.yaml Locks new dependencies required for OpenCode 2 + AI SDK integration.
package.json Moves to OpenCode 2 dependency set; updates package description.
opencode-claude-auth.js Adjusts entrypoint exports to re-export dist.
installation.md Updates installation instructions for OpenCode 2 and new connect flow.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/provider.ts
Comment on lines +209 to +218
export function createClaudeSubscription(options: Record<string, unknown>) {
const accessToken = typeof options.apiKey === "string" ? options.apiKey : ""
const upstream =
typeof options.fetch === "function" ? (options.fetch as Fetch) : undefined
return createAnthropic({
...options,
apiKey: "",
fetch: claudeSubscriptionFetch(accessToken, upstream),
})
}
Comment thread package.json
Comment on lines +66 to +70
"dependencies": {
"@ai-sdk/anthropic": "4.0.39",
"@opencode-ai/plugin": "0.0.0-beta-17519",
"effect": "4.0.0-beta.101"
},
Comment thread installation.md
Comment on lines 11 to +15
```bash
opencode --version
```

You should see a version number (e.g., `1.2.28`).
You need an OpenCode 2 build. See the [OpenCode 2 migration guide](https://opencode.ai/v2/docs/migrate-v1) if you're still on an OpenCode 1 build (`1.x`).
Comment thread installation.md
Comment on lines +90 to +92
```bash
opencode plugin list
```
OpenCode 2 beta-17595 bundles effect@4.0.0-beta.107. With our plugin still
pinned to effect@4.0.0-beta.101, the plugin failed to load entirely:

  "failed to load plugin" cause="Cause([Die(TypeError: undefined is not
  an object (evaluating 'n.base.get'))])"

Two mismatched effect runtime instances interacting produced that crash.
OpenCode then fell through to an unauthenticated Anthropic connection,
surfacing as "API key is invalid" on every request rather than anything
that pointed at the plugin. Bumping effect to match, plus @opencode-ai/plugin
to the beta-17595 pin, fixes it - verified with a real opencode2 run against
the live Anthropic API, not just the unit suite.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

…ugin API

@opencode-ai/plugin publishes two parallel plugin registration APIs: an
Effect-based one (/effect, what we used) and a plain Promise-based one
(the default export). We only ever used Effect as a thin wrapper around
already-async logic, and Effect is pre-1.0 with no cross-version
compatibility guarantees - our plugin's own effect dependency had to match
whatever OpenCode 2 bundled to the exact beta patch, or the plugin crashed
at load time (as it just did against beta-17595). Moving to the Promise
API removes that dependency entirely, leaving @opencode-ai/plugin as the
only OpenCode-coupled dependency to track, pinned to the "beta" dist-tag
so a fresh install picks up whatever OpenCode 2 currently ships without
needing a manual version bump each time.

- Extract all OAuth logic (build credential, authorize, refresh-with-
  keychain-resync, label, method descriptor) into src/oauth-method.ts: a
  framework-agnostic core with dependency injection (OAuthDeps), shared by
  both plugin variants and covered by 29 new unit tests using plain fakes
- Rewrite src/index.ts against @opencode-ai/plugin's Promise API (Plugin
  + setup) instead of @opencode-ai/plugin/effect (Plugin + effect)
- Verified with two separate real `opencode2 run` end-to-end passes
  against the live Anthropic API (once before the cutover with the
  Promise variant temporarily loaded, once after as the permanent
  src/index.ts), not just the unit suite

Also: dropping effect as a direct dependency made TypeScript's
declaration emission stricter, surfacing a pre-existing, unrelated bug -
the multi-account picker (`prompts: [{type: "select", ...}]`) doesn't
match the current Form.Fields schema (form?: Form.Fields, whose field
union has no "select" variant), so multi-account selection during
/connect is likely non-functional against current OpenCode 2 builds. It
only ever type-checked because of a TS excess-property-check loophole.
Documented in place (src/oauth-method.ts) rather than silently fixed here
since it's out of scope for this change; single/no-account paths (what
the e2e tests exercise) are unaffected.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

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

2 participants