Repository navigation
fix: OpenCode v2 compatibility and 401 failover handling - #5
abolfazl-moeini wants to merge 10 commits into
Conversation
…lover bypass
- Export default plugin schema { id, setup } for both server and TUI
- Register keychain and failover commands via input.command.transform
- Support OpenCode v2 keymap.layer callback syntax and slash command definitions
- Add fallback dialog compatibility for OpenCode v2 alert/select
- Fix early return in fetch-patch to disable invalid/exhausted keys on 401/402/403 and rotate
- Add integration tests for 429 rotation and 401 key disable/failover
… in o' TypeError
- OpenCode v2 tool dispatcher evaluates ('output' in o) expecting an object
- Returning a string causes JavaScriptCore to throw: TypeError: o is not an Object. (evaluating '"output"in o')
- If 'output' property is present without an outputSchema, OpenCode v2 rejects with schema error
- Normalizing tool results to { content: string } guarantees compliance with OpenCode v2 and Code Mode
- Added tests verifying { content } format and error safety
…status - Call loadAllEnv() at server startup across project, config dir, and home .env files - Trigger res.config() hook inside plugin.setup() for OpenCode v2 compatibility - Dynamically re-scan environment and discover newly added providers inside keychain-status - Automatically import and sync providers from ~/.local/share/opencode/auth.json
…etry turn on quota exceeded
|
Thanks a lot for the PR and the comprehensive test coverage, @abolfazl-moeini! Looks like a massive improvement for OpenCode v2 compatibility. I haven't reviewed the changes in detail yet, but it's on my radar. I'll approve the CI workflow and take a closer look over the next few days. |
…ead of permanent disable
…tched fetch for config lookup
…ader parsing, and intelligent backoff
…d handle full pool exhaustion
bulutmuf
left a comment
There was a problem hiding this comment.
Thanks — this fixes the actual v2 blocker, and the native input.session.hook + retry-override approach is the right one. I pulled the branch locally: bun test is 71/71 green and bun run typecheck is clean. I also checked the v2.1.4 binary: the TUI loader validates the default export as exactly { id: string, setup: function }, so that part of the fix is correct. No key exfiltration in the diff — the only new outbound call is opencode.ai/console/api/v2/config (org id lookup for the provider's own key).
A few changes before merge.
Blocking
-
src/lib/hooks.ts:296-299— theretryhook setsevent.decision = { retry: true, delay: 100 }whenever any key is active, without checking the error class or the attempt count. A non-retryable error (400, invalid request, context length) gets retried for as long as the pool is non-empty. Please gate the override on the classification (Rotate/Overload only) and/or anevent.attemptcap, and otherwise keep the original decision. -
src/lib/hooks.ts:169andsrc/lib/hooks.ts:288-293—writeAuthKey()(rewritesauth.json) andsyncCredentialDb()(opensopencode.db) run on every model request. On this machineopencode.dbis 4.2 GB; a synchronous open+update per request is a latency andSQLITE_BUSYproblem. Please write only when the active key actually changed (track the last-written key per provider). -
src/lib/config.ts:202-221—loadAllEnv()copies every entry from~/.env,~/.config/opencode/.envand<project>/.envintoBun.envunconditionally, includingPATHandNODE_OPTIONS. Please restrict it to provider/plugin keys (e.g.*_API_KEYS,OPENCODE_FAILOVER_*) and do not overwrite variables that are already set.
Non-blocking
-
src/lib/hooks.ts:60-78—resolveProviderFromUrluseshost.includes("openai.com"), soevil-openai.comalso matches. Usehost === d || host.endsWith("." + d). -
src/lib/auth.ts:40— when no credential row matches the new key, the existing row'svalueis overwritten, so the user's original key is lost fromopencode.dbafter the first rotation. It may be intended (same behaviour asauth.json), but it needs a comment and a test, or it should insert a new row instead.
Compatibility
src/index.ts:413— changing the default export from theserverfunction to{ id, setup }drops OpenCode 1.x support, while the README still advertises "OpenCode 1.17+". If 1.x is still supported this needs a function-compatible default, otherwise a major version bump plus README/package.jsonpeerDependency update (@opencode-ai/plugin: ^1.0.0).
Note: CI has never run on this PR (CI is action_required because it comes from a fork), so the green result above is from my local run only. I have approved the workflow run so CI can verify the branch.
Hey! Ran into a few issues trying to run this on OpenCode v2 and put together a fix:
• v2 schema compatibility: Updated default exports for both server and TUI to
{ id, setup }, and adapted keymap/dialog calls to match v2's API so it doesn't throwInvalid V2 TUI plugin module.• Tool execution return format: Wrapped tool returns into
{ content: string }. In v2/CodeMode, returning raw strings was throwing a TypeError (o is not an Object (evaluating '"output" in o')).• 401/402/403 failover: When an invalid or exhausted key hits 401/402, it now immediately disables the bad key and rotates to the next one instead of bailing out early.
• Native v2 session hooks & auto-retry: Registered
http.request,http.response, andretryhooks oninput.session.hook. OpenCode v2 routes model calls via Effect's HTTP client which bypassedglobalThis.fetch. Native hooks directly intercept requests, rotate keys on 429 /FreeUsageLimitError, sync the SQLitecredentialtable inopencode.db, and override the retry decision so OpenCode automatically re-attempts failed steps with the new key.• Environment & startup sync: Added
loadAllEnvat server startup to load keys from project,~/.config/opencode/.env, and~/.env. Merged env-defined keys (<PROVIDER>_API_KEYS) withkeychain.jsonso existing keychain keys don't shadow env variables.• Registered commands: Added
/keychainand/failovercommands viainput.command.transform.• Tests: Added comprehensive unit & integration tests (
bun testpasses cleanly, 52/52).Tested locally with OpenCode v2 on multiple providers and working smoothly.