readProviderConfig read only options.baseURL and options.apiKey, and the
OpenAI-compatible dispatch hardcoded a bearer token, so provider
options.headers never reached the request. OpenCode sends those headers on
every chat turn, which left the small model authenticating differently from
the request path against the same URL.
Providers behind a gateway that authenticates on its own header, such as the
Ocp-Apim-Subscription-Key default of Azure API Management, answered 401 for
walkthroughs, session goal audits, titles and commit summaries while the same
model worked in chat.
Read options.headers alongside the API key, resolve {env:...} and {file:...}
in the values with the existing resolveConfigApiKey, and merge them into the
request after the bearer default so a gateway whose header is the credential
can override it.
Closes#3213
Match @ai-sdk/anthropic exactly: baseURL is the full API prefix, so
/messages is appended as-is instead of unconditionally inserting /v1,
which broke configs where the baseURL already ends in /v1 (the common
form OpenCode itself passes to @ai-sdk/anthropic).
Addresses review feedback from @btriapitsyn on PR #3000.
The Anthropic small-model dispatch (session-assist recap/suggestion,
walkthrough, etc.) hardcoded https://api.anthropic.com/v1/messages,
ignoring provider.anthropic.options.baseURL — the same override every
other provider branch already respects. Any setup routing the anthropic
provider through a local proxy (e.g. Meridian for Claude Max / Vertex)
had the small-model call bypass the proxy and hit the real Anthropic API
with whatever placeholder credential the proxy config uses, failing with
401 invalid x-api-key on every call.
Plugin providers are registered from a plugin's `config` hook and credentialed
from its `auth` loader, both inside the running OpenCode process. Nothing about
them reaches `opencode.json` or `auth.json`, so resolution that only reads files
could not see them: selecting such a model failed with "has no known API base
URL" while the same model worked in chat (#2666).
`GET /provider` is where that state is visible. A new `runtime-providers`
module keeps one cached snapshot of it and reports, per provider, the
credential and endpoint OpenCode itself resolved. Credential resolution becomes
config -> runtime -> auth.json, and endpoint resolution config -> openai default
-> runtime -> models.dev catalog.
Providers with a dedicated wire format (Copilot, ChatGPT-plan OpenAI, Anthropic,
Google) are excluded from the runtime credential: for them OpenCode reports an
OAuth access token that their real transport does not accept.
opencode zen is excluded when the user has no zen login. OpenCode then reports
the sentinel `apiKey: "public"` and trims its catalog to free models that run on
its own infrastructure; the sentinel is never read as a credential.
Claude Code stays refused for background actions even when a plugin publishes an
OpenAI-compatible endpoint for it, because that endpoint is a facade over the
Claude Agent SDK and spawns the CLI per request.
No capability probe. Asking `GET /models` does identify a plugin whose protocol
lives in its own `fetch`, but measured across the 166 providers with an `api`
URL in the models.dev catalog it also denies six that work and simply have no
`/models` route. A provider that vanishes from the picker explains nothing,
while one that fails on use says why, so availability stops at credential and
endpoint.
The same list drives the Small Model and Changes Walkthrough pickers.
Validated against a real OpenCode with four plugin providers loaded: offered
providers went from 3 to 7, zen and Claude Code stayed out, and a generation
through a plugin-backed model that previously failed now returns.
When the walkthrough small model resolves to a provider with no usable
login, readiness was still ready and generate returned a raw 500 message.
Refuse up front with no-provider-login and surface a blocker instead.
Closesopenchamber/openchamber#2607
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
A diff is ordered by file path, which is almost never the order in which a
change makes sense. This adds a Walkthrough surface that reorders it: the model
groups related hunks into stops, explains what each group changes about
behavior, and orders the stops so each builds on the last. It explains and
orders; judging code stays with the existing Review action.
Reviews uncommitted work (all, staged, unstaged), a branch against its base, or
a pull request. Generation is always user-initiated — nothing runs on a timer,
on a file change, or as a side effect of opening a panel.
Invariants worth preserving:
- Hunk identity is derived on the server and only there. Ids are content
hashes, so an anchor that no longer resolves is proof the code it described
changed, and staleness needs no heuristics. The client matches ids to ids and
never recomputes them; two implementations would have to agree forever.
- The digest is never truncated. A diff that does not fit the model's context
is refused with an actionable reason, because a walkthrough written against
half a diff reads as confident and is wrong.
- Nothing disappears. Lockfiles and other generated output are excluded from
the model's input by name — never by size — and everything no stop covers is
listed at the end, so "have I seen all of it" stays answerable.
- Cost is explicit. Results are content-addressed, so returning the working
tree to an earlier state costs nothing; generation outlives its request, so a
refresh detaches the client rather than discarding paid-for work, and only an
explicit cancel stops it.
Supporting changes to shared modules:
- git: expose the existing getRangeDiff as GET /api/git
listUntrackedPaths and getUntrackedDiffs. The latter resolve the repository
once for a batch instead of per file, taking a panel
~340ms on an 80-file working tree.
- small-model: structured output across four wire forma
and abort signal, and an onOverflow policy so an oversized prompt fails
loudly instead of being silently clipped. A provider
remembered so the prompt-side fallback goes first next time.
- models.dev metadata: surface structured_output as tri
false blocks a model, a missing field does not, because the catalog omits it
for roughly half of all models.
Desktop and tablet only: VS Code serves Git through its
these routes, and the mobile shell does not consume the surface registry.
Docs: packages/docs walkthrough page in English and all eight locales.
Supports {env:NAME} and {file:path} apiKey substitutions in provider config.
Keeps resolved credentials and file contents server-side.
Adds coverage for env and file-based credential resolution.
Uses thinkingLevel for Gemini 3 Flash models
Keeps older Gemini Flash models on thinkingBudget: 0
Updates docs and tests for the new Google request payload
* fix: add support for custom provider base URLs from config
* feat: read custom provider apiKey from config and use it as primary credential when it exists
* doc: update small-model documentation