Skip to article
Browse chapters

Providers and models

The providers bingo ships with, how a credential gets in, named instances for extra endpoints, and how a model is chosen.

What ships

Three provider plugins are compiled in.

pluginprovider idscredential
bingo-provider-anthropicanthropican API key
bingo-provider-openaiopenai, codexan API key; codex takes a ChatGPT subscription through OAuth
bingo-provider-fakefakenone — a scripted harness for tests

The fake provider registers only when BINGO_FAKE_SCRIPT names a script, so a packaged binary never lists it and never falls back to it.

--provider <id> picks one for a run; without it bingo takes the settings’ provider, and failing that the first one registered. --model <id> picks the model; without it the settings’ model, and failing that the provider’s default.

Signing in

bingo login codex        # opens a browser
bingo login codex --device
bingo login anthropic    # paste a key
bingo logout codex

Three flows, and a provider offers the ones it can honour (ADR-0012, ADR-0017):

  • Browser — PKCE over a loopback callback. bingo binds 127.0.0.1:1455, climbing up to twenty ports on a conflict, opens your browser, checks the state and exchanges the code. BINGO_NO_BROWSER stops it opening one, so you can paste the url somewhere else.
  • Device (--device) — a code to type into a browser on another machine. bingo polls for it, for at most fifteen minutes.
  • Paste (--paste) — a credential minted elsewhere, read from stdin. This is the flow every key-based provider uses; browser and device are refused for them, because there is no issuer to talk to.

Whatever the flow, the credential lands in ~/.bingo/data/auth.json at mode 0600, one entry per provider id. A settings file never holds a secret: the project layer is committed, and a committed key is a leaked key.

The same commands exist inside a session as /login <provider> [browser|device|paste] and /logout <provider>.

Where a key is looked for

For the built-in openai and anthropic providers, in this order:

  1. the auth.json entry a paste login wrote under that provider’s id;
  2. OPENAI_API_KEY / ANTHROPIC_API_KEY;
  3. the provider’s own apiKey setting.

auth.json comes first so that /login and /logout mean something in a shell that already exports a key. OPENAI_BASE_URL and ANTHROPIC_BASE_URL move the endpoint the same way the baseUrl setting does.

Tokens refresh themselves: a subscription’s access token is renewed 300 seconds before it expires, or on a 401, once, under one lock — so two turns running at the same time make one refresh. An issuer that says the refresh token is gone removes the entry rather than retrying, and the provider reads as expired until you sign in again.

Named instances

One settings key per wire shape holds one endpoint, which is not enough for a person with two proxies or two subscriptions. instances adds more, each registered under its own name (ADR-0017):

{
  "openai": {
    "instances": {
      "work": { "baseUrl": "https://proxy.internal/v1" }
    }
  },
  "codex": {
    "instances": {
      "personal": {}
    }
  }
}

A name is an identity: --provider work, /model work/gpt-5.4 and bingo login work all mean that instance, and its credential is keyed by that name in auth.json — so two ChatGPT subscriptions sit side by side. A name that collides with a built-in id, or with another instance, is refused at boot rather than half-registered.

Environment variables feed the default instances only. OPENAI_API_KEY belongs to openai and never to work: one ambient key must not silently feed every proxy.

bingo provider add writes the entry a person would otherwise edit in. It asks for a name, a wire shape (openai or anthropic), a base url and — hidden, and optional — a key; the instance goes into your user settings layer, the key into auth.json, and it closes by telling you the bingo --provider <name> that now works. Registration happens at boot, so the instance is live on the next run.

The model catalog

bingo carries a snapshot of models.dev, pruned to the facts it reads: context window, maximum output, whether the model reasons, and whether it takes images (ADR-0004). Those facts are resolved once per session — declared setting, then a window learned from an overflow, then the catalogue, then a default that fails closed on what a wrong guess would reject.

/model completes from that catalogue without a network call, and /model anthropic/claude-x names provider and model in one string. There is no /provider: a login names the provider, and /model names the model.

A model can read the catalog too. The ListModels tool renders every registered provider with its auth state and each provider’s models with their facts, so an orchestrator can pick a vision-capable or long-context model deliberately instead of guessing an id (ADR-0026).

Declaring what the catalogue got wrong

The kernel’s models settings key overrides a model’s facts by name:

{
  "models": {
    "openai/gpt-5.4": { "contextWindow": 400000, "maxOutput": 64000 }
  }
}

Every field is optional. A window bingo learns from a provider’s own overflow message is remembered in ~/.bingo/data/learned-windows.json and clamps the catalogue on the next session opened against that model.