Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

dhaalves/opencode-swap

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

oswap — automatic API key rotation for opencode-go

npm: opencode-swap License: MIT Node.js ≥ 20 Tests: 17 passing Platform: Windows | macOS | Linux

oswap is a lightweight local proxy that rotates between multiple opencode-go API keys automatically when you hit rate limits (HTTP 429). If you use opencode with more than one account, oswap turns "rate limit reached, stop working" into a transparent, instant failover to the next key — no restarts, no manual account switching.

Zero dependencies, one Node.js runtime, works on Windows, macOS, and Linux.

The problem

opencode retries rate-limited (HTTP 429) requests with the same API key — there is no built-in multi-account support or key failover. If you have multiple opencode-go accounts, hitting the per-key usage limit means stopping work (or waiting for the cooldown) even though another key has quota left. Common searches that land here: opencode rate limit error, opencode multiple accounts, opencode-go switch account, opencode 429 too many requests.

The fix

oswap sits between opencode and the upstream API (https://opencode.ai/zen/go) as a local HTTP proxy and load balancer. It holds a pool of your keys and:

  • Round-robins requests across all ready keys
  • Rotates instantly on 429 — the throttled key goes into cooldown (honoring Retry-After / retry-after-ms), and the request is retried with the next key before your client ever sees an error
  • Cools down bad keys — 401/403 responses park a key for 1 hour; 5xx/network errors park it for 30s
  • Waits when everything is cooling — if all keys are rate-limited, oswap waits for the earliest key to recover (up to --max-wait-ms, default 30s) instead of failing
  • Streams SSE byte-for-byte — retries only ever happen before the first response byte, so streams are never half-duplicated
  • Persists state — cooldowns and per-key stats survive restarts in keys.json

Rotation is transport-layer only: model names, request bodies, and streaming behavior pass through untouched. /model switching, agent configs, and fallback chains in opencode keep working exactly as before.

How it works

opencode ──► http://127.0.0.1:8788/v1 ──► https://opencode.ai/zen/go/v1
                 │                            ▲
                 │  Authorization: key A ─429─┘
                 │  Authorization: key B ────► (retry, transparent)
                 ▼
            keys.json (pool: keys, cooldowns, stats)

The baseURL override is set on the provider, so every opencode-go/* model routes through the pool automatically.

Requirements

  • Node.js ≥ 20 (developed on v22; no npm dependencies)
  • One or more opencode-go API keys

Install

npm install -g opencode-swap   # puts the `oswap` command on your PATH

Or from source (for development):

git clone https://github.com/dhaalves/opencode-swap.git
cd opencode-swap
npm link

Quick start

oswap import        # pull existing opencode-go keys from auth.json + account.json
oswap add sk-...    # add more accounts (--label work)
oswap test          # verify every key against the live upstream
oswap install       # point opencode.json at the proxy (backs up first)
oswap serve         # run the proxy — keep it alive while using opencode

Then restart opencode. That's it — from now on, rate limits rotate keys instead of blocking you.

Watch rotation live:

oswap status
id        label  key           state          requests  rateLimits  failures
c1bda1c9  work   sk-3H...cxnH  cooling 42s    87        1           0
dab9c869  home   sk-PR...NWp6  ready          85        0           0

CLI reference

Command Description
oswap serve Run the rotation proxy (default 127.0.0.1:8788)
oswap status Live pool state from the running proxy (falls back to saved file)
oswap add <key> Add a key (--label name)
oswap remove <id> Remove a key by id or id prefix
oswap list List keys (masked) with stats
oswap reset [id] Clear cooldown on one key, or all
oswap import Import opencode-go keys from auth.json + account.json (--dry-run)
oswap test Check every key against the upstream /v1/models
oswap install Set provider.opencode-go.options.baseURL in opencode.json (makes .oswap-bak backup)
oswap uninstall Remove the baseURL override
oswap version Version of this CLI vs the running proxy — catches a serve still running older code
Option Default Description
--port <n> 8788 Proxy port
--host <h> 127.0.0.1 Bind host
--upstream <url> https://opencode.ai/zen/go Upstream base URL
--max-wait-ms <n> 30000 Max wait for a cooling key before returning 429
--keys <path> ~/.config/oswap/keys.json Pool file location
--provider <id> opencode-go Provider id for import

Rotation semantics

Upstream response What oswap does
429 Cools key for retry-after-ms / Retry-After (default 60s), retries with next key
401 / 403 Cools key 1 hour (probably dead, not throttled), retries with next key
5xx Cools key 30s, retries with next key
Network error Cools key 30s, retries with next key
All keys cooling Waits for earliest recovery, up to --max-wait-ms; else returns 429 with Retry-After
Other 4xx Passed through untouched (it's a client error, rotation won't help)

Monitoring endpoints

On the proxy port:

  • GET /oswap/health{ ok, upstream, version, pid, source }
  • GET /oswap/status{ upstream, version, keys } — per-key state, cooldowns, and counters (keys always masked)

version and source report the code the proxy actually loaded at boot, which is not necessarily what is on disk now. After upgrading, restart oswap serve and confirm with oswap version.

Configuration file

~/.config/oswap/keys.json (created by oswap import / oswap add) — contains secrets, keep it out of git. It lives in your user config dir, not the package dir, so npm update never wipes your pool:

{
  "version": 1,
  "keys": [
    {
      "id": "c1bda1c9",
      "key": "sk-...",
      "label": "work",
      "cooldownUntil": 0,
      "disabled": false,
      "stats": { "requests": 87, "rateLimits": 1, "failures": 0 }
    }
  ]
}

What oswap install changes

It adds this to your opencode.json (after copying the original to opencode.json.oswap-bak):

{
  "provider": {
    "opencode-go": {
      "options": { "baseURL": "http://127.0.0.1:8788/v1" }
    }
  }
}

The key in auth.json stays as-is — oswap replaces the Authorization header with pool keys, so the configured credential is ignored by the proxy but still keeps the provider enabled. oswap uninstall removes the override.

Keeping the proxy running

The proxy must be alive whenever you use opencode. Options:

  • pm2: pm2 start "oswap serve" --name oswap && pm2 save
  • Windows Task Scheduler: run oswap serve at logon
  • Or just leave a terminal open

Notes & limitations

  • No model fallback. oswap rotates keys, not models. If a model (not your key) is down, use opencode's own fallback config for that.
  • Other providers are untouched. If you route opencode-go traffic through another local proxy (e.g. a compression proxy on :8787), point that proxy's upstream at 127.0.0.1:8788/v1 to get rotation there too.
  • Single machine. The pool is a local JSON file — no distributed coordination.

Development

npm test          # node --test: pool unit tests + proxy integration tests (fake upstream)

Source layout:

src/pool.js   — key pool: round-robin, cooldowns, atomic JSON persistence
src/proxy.js  — HTTP proxy: auth substitution, retry/rotation loop, SSE pass-through
src/cli.js    — command line: serve/status/add/remove/import/install/test/...
test/         — node:test suites, zero network (fake in-process upstream)

FAQ

Does opencode support multiple accounts natively?

No. opencode stores one credential per provider in auth.json and retries rate-limited requests with the same key. Multi-account rotation is exactly what oswap adds, without modifying opencode itself.

How do I fix "rate limit reached" / HTTP 429 errors in opencode?

Run oswap with two or more keys. When the active key returns 429, oswap puts it in cooldown (honoring the server's Retry-After) and retries the same request with the next key — opencode never sees the error.

Will this break model switching or streaming?

No. oswap is transport-layer only: model names, request bodies, and SSE streams pass through byte-identical. Retries only happen before the first response byte, so a stream is never duplicated mid-flight.

Can I use it with other OpenAI-compatible providers?

The rotation logic is provider-agnostic — only the import command is opencode-go-specific. Point --upstream at any OpenAI-compatible base URL and add keys with oswap add.

Is oswap a VPN or does it send my keys anywhere?

No. It binds to 127.0.0.1, stores keys in a local keys.json, and only ever talks to the configured upstream. Keys are masked in all output.

How is this different from other opencode multi-account tools?

oswap is a standalone proxy, not an opencode plugin — plugin APIs can't intercept 429 responses, so plugin-based rotators rely on monkey-patching fetch. The proxy approach is stable across opencode updates and gives you persistent per-key stats and cooldowns across restarts.

License

MIT — see LICENSE.

About

Automatic API key rotation for opencode-go — local proxy that round-robins multiple accounts and fails over on rate limits (HTTP 429). Zero-dependency Node CLI with SSE streaming support.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.