Skip to content

Route OpenCode explicit model overrides through the AI Gateway#170

Merged
molebox merged 1 commit into
mainfrom
rh/opencode-gateway-model-prefix
Jul 6, 2026
Merged

Route OpenCode explicit model overrides through the AI Gateway#170
molebox merged 1 commit into
mainfrom
rh/opencode-gateway-model-prefix

Conversation

@molebox

@molebox molebox commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Problem

An explicit model override like anthropic/claude-sonnet-5 was passed to the OpenCode CLI verbatim (--model anthropic/claude-sonnet-5). OpenCode reads the first segment as its provider id, and the generated opencode.json only configures the vercel (AI Gateway) provider — so every explicit-model run using a canonical gateway id died at session start:

{"type":"error","error":{"name":"UnknownError","data":{"message":"Unexpected server error. Check server logs for details."}}}

with observedModel: null on every task. The form OpenCode actually routes through the gateway is vercel/anthropic/claude-sonnet-5 — consistent with the package's own defaultModel shape and the README's OpenCode section. claude-code and codex were unaffected because their adapters route via gateway env/config (ANTHROPIC_BASE_URL, model_provider = \"vercel\").

Fix

Mirrors codex's host-computed model handling (openai/ prefixing):

  • resolveOpenCodeModel (host-side, opencode/agent.ts) — prefixes vercel/ unless the caller already targets vercel/... or a key configured via agentOptions.extraProviders (the unreleased-models escape hatch). Host-side because only the host knows extraProviders.
  • runnerExtra() — threads the resolved id to the sandbox runner as input.extra.cliModel. The judge path (eval-helper.mjs) ships extra but not agentOptions, so the resolution must happen host-side. The runner deliberately has no fallback to input.model — verbatim pass-through is the exact mis-route this fixes, and both runner entry points always ship extra.
  • normalizeObservedModel (runner-side, opencode/run.mjs) — un-applies exactly the prefix the host added, so observedModel === requestedModel holds for canonical gateway ids, and a gateway substitution still surfaces as a clean gateway id (anthropic/claude-haiku-4, not vercel/anthropic/...).

Compatibility

  • The README-documented vercel/{provider}/{model} form passes through verbatim — zero change for existing consumers following the docs.
  • The auto-prefix path only activates on inputs that previously failed with provider-not-found, so no working consumer can regress.
  • Native-default runs are untouched: CLI args are byte-identical and observations (e.g. vercel/google/gemini-3-pro-preview) pass through unchanged — no published-data drift.
  • extraProviders requests are passed verbatim, preserving that workflow.
  • No public API surface change (resolveOpenCodeModel has the same internal/test-only visibility as generateOpenCodeConfig) → patch changeset.
  • README's OpenCode model format section updated — it previously stated the unprefixed form fails.

Testing

  • 8 new unit tests covering: prefixing, idempotence on vercel/... ids, extraProviders verbatim + gateway ids still prefixed alongside them, bare ids (no /), no-verbatim-fallback in arg building, native-default arg parity, observed-model round-trip, gateway-substitution reporting, and native-default observation pass-through.
  • Full suite: 259 passed, tsc + eslint clean, copy-runners build verified.

…teway

An explicit --model like anthropic/claude-sonnet-5 was passed to the
OpenCode CLI verbatim, which reads the first segment as its provider id.
The generated opencode.json only configures the vercel (AI Gateway)
provider, so every explicit-model run using a canonical gateway id died
at session start with "Unexpected server error" and a null
observedModel.

- resolveOpenCodeModel (host-side): prefix vercel/ unless the caller
  already targets vercel/... or a configured extraProviders key.
- Threaded to the runner via runnerExtra -> input.extra.cliModel,
  mirroring codex's host-computed model (the judge path ships extra but
  not agentOptions, so the resolution must happen host-side).
- normalizeObservedModel (runner-side): un-apply exactly that prefix on
  the observation, so observedModel === requestedModel holds for
  canonical gateway ids and a gateway substitution still surfaces as a
  clean gateway id. Native-default observations are untouched, and the
  documented vercel/-prefixed form still passes through verbatim.
- README: the OpenCode model format section now documents both forms
  (it previously stated unprefixed ids fail with provider-not-found).
@molebox
molebox requested a review from gaojude July 6, 2026 10:18
@molebox
molebox merged commit 4358799 into main Jul 6, 2026
4 checks passed
@molebox
molebox deleted the rh/opencode-gateway-model-prefix branch July 6, 2026 16:40
@github-actions github-actions Bot mentioned this pull request Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants