@ai-sdk/openai-compatible posts to {baseURL}/chat/completions, @ai-sdk/anthropic to {baseURL}/messages. Relay serves both off one base, https://your-relay/opencode/v1, so the same URL works whichever package you pick.
Relay translates between wire shapes, so the choice of package does not constrain the model: any model your relay key’s policy grants is reachable from either one.
Setup
Relay renders the models.dev catalog document OpenCode already knows how to read, so the provider block you would otherwise hand-write comes from the relay itself. Download it once, and point OpenCode at the file:relay, carrying its own base URL, its SDK package and env: ["RELAY_API_KEY"] — and a provider whose environment variable is set loads with no configuration block at all. Pick a model with opencode -m relay/<model>, or name a default in ~/.config/opencode/opencode.json:
OPENCODE_MODELS_PATH is a file path, not a URL: OpenCode reads it in place of its own cached catalog, and never fetches while it is set. The sibling OPENCODE_MODELS_URL does fetch {source}/api.json, but it sends no Authorization header and offers no way to attach one, so it cannot reach a policy-scoped endpoint — the file is the supported route. The document is a snapshot of what your key could route to at download time; re-run the curl after a policy or catalog change.
The models it lists carry the catalog’s context window, output cap, prices (including cache and context-tier rates), modalities and capability flags. A model the catalog declares no context window for is left out: OpenCode reads a missing limit.context as zero, and a zero silently disables its context-remaining display and its compaction trigger.
Writing the provider block yourself
If you would rather not carry a file, declare the provider inline. Supplylimit for every model — the defaults for a provider OpenCode has never heard of are a zero context window and a zero output cap:
Authorization: Bearer, which is how Relay expects it.
The Anthropic package
Swapnpm for @ai-sdk/anthropic and keep the same base URL — the package appends /messages to it, and Relay accepts the x-api-key header the package sends as a relay key. Prefer this variant when you want OpenCode’s Anthropic-side features on the wire as the vendor defines them: the thinking configuration it passes through options.thinking, and the prompt-cache breakpoints it injects into the request body, which survive as sent on a route to an Anthropic upstream. Note the /v1 in the base URL is yours to supply — the package only adds it for api.anthropic.com itself.
Per-agent models
Theagent block pins any agent to its own provider/model, so one relay serves a cheap title model and an expensive build model from a single config:
What you get
- Usage — every turn appears in
GET /logsand the/usage/*endpoints, attributed to your relay key, with token counts and timing. - Payloads — with payload logging enabled, the full request and response bodies are readable at
GET /logs/{request_id}. - Attribution — the client’s
x-session-idheader is recorded on the usage event asextras, alongsideclient=opencode. That is what lets you cost one session; OpenCode sends no agent or turn marker, so below the session the only distinction is the model.
Known limits
- Prompt-cache breakpoints do not cross from a Chat Completions body. OpenCode injects
cache_controlmarkers whenever the model id containsclaudeoranthropic— including on the openai-compatible path, where they land as a non-standard key on each message. Relay forwards them untouched to an upstream that speaks that shape, but does not yet map them into its canonical cache intent, so on a cross-shape route they are dropped and the turn is billed uncached. - Reasoning comes back as text unless the model declares where to find it. On the openai-compatible path OpenCode reads reasoning from whatever field
models.<id>.interleavednames (reasoning_contenton most upstreams that emit it at all) and re-sends it there on the next turn. Upstreams differ; setinterleavedto match yours, and expect no separate thinking block from one that returns none. - Attribution stops at the session. Unlike Claude Code and Codex, OpenCode sends no per-agent or per-turn header, so a sub-agent’s spend is separable only by the model it was pinned to.
- Model ids must not contain
/. OpenCode reads the first segment as the provider id; Relay’s ids are slugs and never do, but an alias you invent should not either. - Use
127.0.0.1, notlocalhost, in a local base URL.