npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

opencode-go-router

v0.4.0

Published

opencode plugin that rotates between multiple opencode GO (Zen) API keys, with automatic failover on rate limit.

Readme

opencode-go-router

Local proxy that rotates multiple opencode GO API keys with automatic failover on rate limit.

npm version license node

If you have several opencode GO accounts and want opencode to keep working after one of them hits the rate limit, point it at this proxy. It rotates through your keys, marks the exhausted ones in a cooldown, and retries the current request with the next healthy key — transparently, before any tokens reach your terminal.


Why

The opencode GO plan issues static API keys (sk_...) that you can generate freely from your dashboard. Under real use, a single key eventually returns 429 rate_limit_exceeded, and your session stalls until it resets. Rotating manually is annoying; rotating from inside an opencode plugin doesn't work because the opencode-go provider uses a custom fetch path that never fires the chat.headers hook.

Solution: a tiny local HTTP proxy sits between opencode and the GO upstream. It owns the Authorization header and swaps the key on the fly.

opencode  ──►  http://localhost:8787  ──►  https://opencode.ai/zen/go/v1
                     │
                     ├── pool: [sk_1, sk_2, sk_3]
                     ├── sticky-until-fail (keeps using one key until it breaks)
                     ├── injects  Authorization: Bearer <active_key>
                     └── on 429/403 or "rate_limit" in the first SSE frame:
                         cools down the key and retries with the next one

Features

  • Automatic failover on 429, 403, and rate-limit payloads inside the first SSE frame.
  • Sticky-until-fail rotation — maximizes usage of one key before switching.
  • Persistent cooldowns in ~/.opencode-go-router/state.json; survives restarts.
  • Streams natively — after the initial handshake, requests are piped straight through with no buffering.
  • Zero opencode patches — works via opencode.json config only.
  • Respects Retry-After headers when the upstream provides them.
  • Debug header (x-opencode-router-key) on every response so you can see which key served it.

Requirements

  • Node.js ≥ 18
  • opencode installed
  • Two or more opencode GO API keys

Install

npm install -g opencode-go-router

Or without global install:

npx opencode-go-router serve

Quick start (plugin mode — recommended)

Configure once, globally — then every project just works.

opencode-go-router keys set "sk_1,sk_2,sk_3"   # persisted to ~/.opencode-go-router/keys.json
opencode-go-router init --global                # adds plugin entry to ~/.config/opencode/opencode.json
opencode                                        # in any project

This writes:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["opencode-go-router"]
}

opencode installs the plugin automatically (via bun, equivalent to npx) and caches it in ~/.cache/opencode/node_modules/. No per-project export, no per-project config.

Per-project instead of global

Drop --global to write ./opencode.json in the current directory:

opencode-go-router init

Alternative: proxy mode

If the plugin's chat.headers hook doesn't fire on your opencode build (custom fetch paths in some provider integrations bypass it), fall back to running the local HTTP proxy — see Proxy mode below.


CLI reference

opencode-go-router init [path] [--global]
    Configure opencode.json to load the plugin.
    --global writes ~/.config/opencode/opencode.json.

opencode-go-router keys
    List keys currently in use (masked) and their source (env or file).
opencode-go-router keys set "k1,k2,k3"
    Persist keys to ~/.opencode-go-router/keys.json.
opencode-go-router keys add <key>
    Append one key to the persisted file.
opencode-go-router keys remove <index|suffix>
    Remove by 1-based index or last-6 suffix.
opencode-go-router keys clear
    Delete the persisted keys file.

opencode-go-router status
    Show pool health and cooldown timers.
opencode-go-router force-cooldown
    Mark the currently active key as exhausted (useful for testing).
opencode-go-router reset
    Clear all cooldown state.

opencode-go-router serve [--port 8787] [--upstream URL]
    (Optional) run the standalone rotating proxy.

Key sources

Resolved in order — first match wins:

  1. env OPENCODE_ZEN_KEYS
  2. env OPENCODE_GO_KEYS
  3. ~/.opencode-go-router/keys.json (managed by keys set/add/remove/clear)

The env var takes precedence so you can override the persisted set for a single shell without deleting the file. While an env var is set, keys add/keys remove refuse to write (they'd be shadowed anyway).

Environment variables

| Variable | Default | Description | |---|---|---| | OPENCODE_ZEN_KEYS | (unset) | Comma-separated GO API keys — takes precedence over the persisted file | | OPENCODE_ROUTER_UPSTREAM | https://opencode.ai/zen/go/v1 | Upstream base URL (proxy mode) | | OPENCODE_ROUTER_PORT | 8787 | Default port for serve | | OPENCODE_ROUTER_DEBUG | (off) | Set to 1 for per-request logs on stderr | | OPENCODE_ROUTER_STATE_DIR | ~/.opencode-go-router | Where cooldown state and keys.json live |


Proxy mode (fallback)

If the plugin hook can't intercept requests on your setup, run the standalone proxy and point opencode's opencode-go provider at it.

opencode-go-router keys set "sk_1,sk_2,sk_3"   # or export OPENCODE_ZEN_KEYS=...
opencode-go-router serve                        # in a dedicated terminal

Then edit opencode.json (per project) to add:

{
  "provider": { "opencode-go": { "options": { "baseURL": "http://localhost:8787" } } }
}

Verifying it works

With the proxy running, expect logs like:

[go-router] listening on :8787, upstream=https://opencode.ai/zen/go/v1, keys=3
[go-router] → attempt 1/3 key=...abc123 POST https://opencode.ai/zen/go/v1/chat/completions
[go-router] ✓ streamed via key=...abc123

Force a rotation to confirm failover works end-to-end:

opencode-go-router force-cooldown

Then send any prompt in opencode — you should see the proxy pick a different key:

[go-router] → attempt 1/3 key=...def456 POST https://opencode.ai/zen/go/v1/chat/completions
[go-router] ✓ streamed via key=...def456

Inspect the pool at any time:

opencode-go-router status
{
  "activeKey": "...def456",
  "keys": [
    { "key": "...abc123", "healthy": false, "cooldownMsRemaining": 587234 },
    { "key": "...def456", "healthy": true,  "cooldownMsRemaining": 0 },
    { "key": "...ghi789", "healthy": true,  "cooldownMsRemaining": 0 }
  ]
}

How rotation works

  1. On every request, the proxy calls pickKey() which returns the current sticky key (or the next healthy one if it's cooling down).
  2. The request is forwarded upstream with Authorization: Bearer <key>.
  3. The response is inspected:
    • 429 / 403 → the key is put into cooldown (respecting Retry-After if present; default 5 minutes), and the request is retried with the next key. Up to pool.size attempts.
    • 200 with SSE → the first frame is sniffed (up to 800 ms) for rate_limit_exceeded / quota_exceeded payloads. If found, cooldown + retry. Otherwise, headers are flushed and the stream is piped through untouched.
    • All other responses → passed through verbatim.
  4. If every key is in cooldown, the proxy responds with 429 and x-opencode-router: all-exhausted so opencode surfaces a clear error rather than hanging.

Limitations

  • No mid-stream failover. If the upstream starts streaming and only later emits an error, the current turn is lost. The next turn will use a fresh key.
  • SSE error detection is heuristic. Payloads matching rate.?limit|quota|too many|insufficient.?quota are treated as exhaustion. If the GO backend introduces a new error shape, this regex may need updating (see src/proxy.js).
  • Single opencode config target. The init command only configures the opencode-go provider. Other providers are untouched.
  • Terms of Service. Rotating between your own accounts is generally fine, but you should confirm this is acceptable under the opencode GO ToS before running it in production. This project is a routing layer, not an evasion technique.

FAQ

Why not implement this as an opencode plugin? The opencode-go provider uses a custom fetch path that bypasses the chat.headers plugin hook, so a plugin can't reliably swap the auth header. A local proxy sits outside opencode entirely and works regardless of internal changes.

Can I use this with providers other than opencode-go? The proxy itself is provider-agnostic — it forwards to whatever --upstream you set. But init only writes config for opencode-go. For other providers, edit opencode.json manually.

What happens when all keys are exhausted? The proxy returns 429 with header x-opencode-router: all-exhausted. Opencode will surface this as a normal rate-limit error. opencode-go-router status shows the remaining cooldown for each key.

Is this an MCP server? No. MCP is a protocol for tools/context, not for authenticating provider requests. Key rotation has to happen where the request is issued, which is inside opencode — so we intercept at the HTTP layer instead.


Development

git clone https://github.com/YOUR_USERNAME/opencode-go-router
cd opencode-go-router
npm install
npm link                                      # expose CLI globally

cd /path/to/some/opencode/project
npm link opencode-go-router
opencode-go-router keys set "sk_1,sk_2,sk_3"
opencode-go-router init --global
OPENCODE_ROUTER_DEBUG=1 opencode

Project layout:

opencode-go-router/
├── src/
│   ├── proxy.js       # HTTP proxy with rotation + retry logic
│   ├── pool.js        # KeyPool: pickKey, markExhausted, status
│   └── index.js       # legacy opencode plugin (kept for other providers)
├── bin/cli.js         # command-line interface
├── package.json
└── README.md

License

MIT © Your Name


Contributing

Issues and PRs welcome. If you hit an edge case — a new upstream error shape, a different provider path, a bug in the SSE sniffer — please open an issue with the redacted debug log (OPENCODE_ROUTER_DEBUG=1).