pi-provider-opencode-console: live OpenCode Go models without waiting for pi releases

Posted on Sat, 24 Oct 2026 in Tools

For my personal work I use the OpenCode Go subscription as the model provider in pi. And there was one detail that annoyed me every week: when OpenCode changed the subscription model catalog, pi kept showing me the old list until a new release shipped.

pi-provider-opencode-console repository

The problem: a model list frozen into the binary

pi does not discover models at runtime for its built-in providers. The catalog lives in pi-ai, in a file generated at build time:

// This file is auto-generated by scripts/generate-models.ts
// Do not edit manually - run 'npm run generate-models' to update

For a provider with a public API and a stable catalog that's fine. For a subscription that adds and retires models every few weeks, it's constant friction: you see new names on the OpenCode site that don't exist in /model, and retired models that are still there taking up space.

The alternative was hand-editing models.json, which is worse: you maintain it, it drifts, and model overrides don't always behave the same way with extension providers.

The fix: device-code OAuth and a live catalog

pi-provider-opencode-console is a pi extension that registers two new providers — opencode-console and opencode-go-console — with OAuth 2.0 device authorization grant login (RFC 8628) and model discovery against OpenCode's real catalog.

pi install npm:pi-provider-opencode-console

The two providers are deliberately distinct from the ones pi ships with:

Provider Auth Model catalog
opencode / opencode-go (built-in) API key Static, compiled into pi-ai
opencode-console / opencode-go-console (extension) OAuth device code Live, from the server

That leaves the built-in providers untouched and lets you pick whichever matches your setup: API key or subscription. No name collisions, no credential conflicts.

Login

The flow is the usual one: you request the login, pi gives you a code and a URL, you approve in the browser, done.

/login opencode-go-console
# pi prints a user code and the verification URL
# you approve in the browser and the credential lands in ~/.pi/agent/auth.json

For the full console (the one that absorbed Zen) it's the same flow:

/login opencode-console

Both share the device-code session, so authenticating one leaves the other ready. The credential is stored as oauth in auth.json, and the access token renews itself: with less than five minutes to expiry the extension refreshes before the next request, single-flight so two concurrent calls don't request two tokens, and mirrors the rotated token into the sibling entry.

One important thing: the extension is OAuth-only. It does not offer pi's generic "Enter API key" login. If you want API-key access with a static list, that's exactly why the built-in providers exist.

Where the models come from

Each provider has its own source:

  • opencode-go-console pulls the public GET https://opencode.ai/inference/go/v1/models catalog, the lite list, and enriches it with models.dev data while dropping disabled or deprecated ids.
  • opencode-console pulls the org-scoped GET {server}/api/config endpoint with Authorization: Bearer and x-org-id, so each organization sees the models it actually has.

To refresh without logging in again:

/opencode-go-console refresh

And to see what's behind it:

/opencode-go-console status    # auth method, account, org, token expiry
/opencode-go-console logout    # clears the Go credential, keeps Console's

The console provider also has switch-org to change organization when the session isn't already scoped to one from the browser. If the grant requested org scoping, switch-org doesn't apply: you log in again.

The detail that makes this work well

OpenCode does not expose every model over the same protocol. Some models speak anthropic-messages, others openai-completions, others openai-responses, and the Gemini ones go through google-generative-ai. The extension routes each request to the right wire API for the model you picked, and normalizes the base URL per API kind before letting the SDK compose its own:

API kind Normalized base
anthropic-messages /inference[/go]/anthropic
openai-completions /inference[/go]/openai/v1
openai-responses /inference[/go]/openai/v1
google-generative-ai /inference[/go]/google/v1beta

Streaming and parsing are delegated to pi-ai's implementations instead of being reimplemented. That means a new model speaking an already-supported protocol works without touching the extension, and streaming bugs get fixed where they belong: in pi-ai.

It also injects per-request identity headers (x-opencode-org-id, x-opencode-client, x-opencode-request), which is what makes the session recognizable as yours on OpenCode's side.

Configuration

Just two environment variables, and usually none:

Variable Default What for
OPENCODE_CONSOLE_SERVER https://opencode.ai/console Self-hosted consoles. Persisted to auth.json on first sign-in.
PI_CODING_AGENT_DIR ~/.pi/agent pi's agent directory. Useful for testing in an isolated setup.

Migrating from older versions

2.0 broke compatibility on purpose: service-key auth (sk-…) was removed. If you still have one of those entries in auth.json, it's inert and pi reports "Provider is not configured". Delete it and do the device-code sign-in.

# coming from 1.x? check and clean up
cat ~/.pi/agent/auth.json

How I use it

I run pi on two machines with two different budgets: the OpenCode Go subscription for personal work and the Claude subscription for work (that's pi-claude-bridge). On the personal machine, this extension removed an entire class of friction: there's no longer a "let's see if this week's release brings the new model" moment.

The catalog you see in /model is the one you're paying for right now. If OpenCode adds a model in the morning, I have it in pi after a refresh, without updating anything.

And if you want to know how each of those models performs before picking one, aa-opencode crosses the catalog with Artificial Analysis benchmarks.

Resources