An opencode plugin that wires
BitRouter in as a provider. It
declares the provider for you, makes bitrouter/auto your default model,
discovers the available models from your BitRouter instance instead of shipping
a hard-coded list, and adds a BitRouter Cloud device login to /connect.
BitRouter can run two ways:
- Local daemon (
http://127.0.0.1:4356) — BYOK, your keys, your machine. - BitRouter Cloud (
https://api.bitrouter.ai/v1) — managed proxy, one bill.
By default the plugin picks for you: if a local daemon is serving models it uses
that (zero-login dev flow), otherwise it falls back to cloud. Set
BITROUTER_TARGET to force one.
Add the package to the plugin array in opencode.json — opencode fetches and
caches it automatically:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@bitrouter/opencode"]
}That is the whole configuration. The plugin contributes the provider.bitrouter
block itself, and sets model and small_model to bitrouter/auto, so you do
not need to write either one.
A model you set yourself always wins — the plugin only fills in what your
config leaves out.
Then authenticate:
opencode auth loginPick BitRouter and choose either BitRouter Cloud (device login) — a
browser device-code flow, new accounts get free credits — or API key to
paste a brvk_ key.
Drop a checkout into a plugin directory opencode loads at startup —
.opencode/plugins/ for one project, ~/.config/opencode/plugins/ globally.
The plugin returns three hooks, one per stage of the provider's life:
| Hook | What it does |
|---|---|
config |
Declares the bitrouter provider (@ai-sdk/openai-compatible, the resolved base URL) seeded with whatever catalog is reachable at load time, and names bitrouter/auto as model and small_model. A provider.bitrouter block — or a model — you wrote yourself always wins; the hook only fills in what you left out. |
auth |
Offers the device login and the API-key method, and turns whichever credential is stored into provider options. An expired OAuth grant is refreshed per request and written back through client.auth.set. |
provider |
Re-discovers the live catalog via GET ${baseUrl}/models once a credential exists, so the model list reflects your account rather than the seed. The auto route leads the refreshed list too. If discovery fails it keeps the current list rather than blanking it. |
bitrouter/auto hands model choice back to BitRouter: the request carries
bitrouter/auto as its model and the gateway's routing policy picks the model
per request. bitrouter/ is a namespace BitRouter reserves for itself, so the
vendor segment names the router being addressed rather than the token
destination. It leads every catalog the plugin produces, and it is the default
model and small_model.
The rest of the catalog is still there. bitrouter/auto is the default, not
the only option — pin bitrouter/anthropic/claude-opus-5 (or anything else
BitRouter serves) with /models or in opencode.json whenever you want one
specific model, and switch back whenever you do not.
Before you authenticate on cloud there is no token to discover with, so the
provider is seeded with the auto route alone. That is deliberate: without at
least one model the provider would not be selectable and you could not reach
/connect at all. The rest of the catalog fills in on first use.
Until BitRouter's own catalog lists bitrouter/auto, the plugin synthesizes the entry
with deliberately conservative capacities (128K context, 16K output). They are
the floor rather than the ceiling on purpose — auto may land on any model in
the ladder, and under-claiming compacts a session early where over-claiming
fails a request outright, mid-turn. A gateway that ever serves an entry under
this id supersedes the placeholder, though none does today: the namespace is
resolved before any provider lookup and BitRouter's registry validator
refuses catalog models under bitrouter/, so the entry has to come from
here.
| Variable | Default | Description |
|---|---|---|
BITROUTER_TARGET |
(auto) | local → daemon at http://127.0.0.1:4356/v1; cloud → https://api.bitrouter.ai/v1. Unset means: use local if it answers /models, else cloud. |
BITROUTER_BASE_URL |
(derived from target) | Override the base URL for either mode. Takes precedence over the target default. |
BITROUTER_API_KEY |
(unset) | Use this token directly and skip /connect entirely. Handy for CI. |
BITROUTER_OAUTH_AS |
https://api.bitrouter.ai |
Authorization-server origin for device login. |
BITROUTER_OAUTH_CLIENT_ID |
bitrouter-cli |
Public OAuth client id. |
BITROUTER_OAUTH_SCOPE |
(CLI default set) | Space-separated scope string. |
The local daemon defaults to skip_auth: true, so loopback requests are
admitted without a key — the plugin supplies a filler key so opencode surfaces
the provider anyway. To enable key-based auth, mint a virtual key:
bitrouter key sign --user <id>Then paste the resulting brvk_... into opencode auth login → BitRouter →
API key, or export it as BITROUTER_API_KEY.
If you would rather not run a plugin, examples/opencode.json
declares the same provider statically. You lose dynamic model discovery and the
device login, and you maintain the model list yourself.
The model list only shows bitrouter/auto
The catalog has not been fetched yet. Run opencode auth login and connect
BitRouter; the real list appears on the next request. bitrouter/auto itself
still works meanwhile — routing is the gateway's job, not the plugin's.
model refresh failed at .../models: HTTP 401
The stored credential is rejected. Re-run opencode auth login, or check that
BITROUTER_API_KEY holds a valid brvk_ key for a local target.
Nothing reaches BitRouter at all
Confirm the daemon is up with bitrouter status (start it with
bitrouter start), and that BITROUTER_BASE_URL, if set, points at a reachable
instance. The plugin logs to opencode's structured log under the bitrouter
service.
npm installnpm run buildnpm testLogic lives in src/ as pure, dependency-injected modules — target
resolution, catalog discovery, model mapping, the OAuth device flow — with
src/index.ts as the thin opencode-facing composition layer.
BitRouter's cloud endpoints are in src/constants.ts.
Apache-2.0