Repository navigation
Description
Summary
Custom providers declared with the legacy V1 config shape (provider + npm + options) lose their compatibility block. Every one of their models reaches GET /api/model with compatibility absent, so a documented compatibility.reasoningField never applies. Declaring the same provider with the V2 shape (providers + package + settings) preserves the field.
Environment
- opencode version:
2.0.29(@opencode/cli2.0.29, npm global install) - OS: Windows 11 (NT 10.0.26200)
- Terminal: Web UI in Chrome,
TERM=xterm-256color - Shell:
cmd.exe - Install/channel: latest
- Active plugins: none found in config
Reproduction
- Declare a custom OpenAI-compatible provider using the legacy shape in
~/.config/opencode/opencode.json:
{
"provider": {
"acme": {
"npm": "@ai-sdk/openai-compatible",
"name": "Acme",
"options": { "baseURL": "http://127.0.0.1:8788/v1", "apiKey": "<redacted>" },
"compatibility": { "reasoningField": "reasoning_content" },
"models": {
"glm-5.2": {
"name": "GLM-5.2",
"limit": { "context": 1000000, "output": 64000 },
"capabilities": { "tools": true, "input": ["text", "image"], "output": ["text"] }
}
}
}
}
}-
Read the model catalog:
opencode api get /api/model. -
Every
acmemodel comes back withoutcompatibility:
{
"id": "glm-5.2",
"providerID": "acme",
"package": "@opencode/ai/providers/openai-compatible",
"capabilities": { "tools": true, "input": ["text", "image"], "output": ["text"] },
"limit": { "context": 1000000, "output": 64000 }
}- Rewrite the same provider in the V2 shape and change nothing else:
{
"providers": {
"acme": {
"name": "Acme",
"package": "@opencode/ai/providers/openai-compatible",
"settings": { "baseURL": "http://127.0.0.1:8788/v1", "apiKey": "<redacted>" },
"compatibility": { "reasoningField": "reasoning_content" },
"models": { "glm-5.2": { "name": "GLM-5.2", "limit": { "context": 1000000, "output": 64000 } } }
}
}
}- Now every model of that provider returns the field (37/37 in my setup), and it is picked up without restarting the server:
{ "id": "glm-5.2", "providerID": "acme", "compatibility": { "reasoningField": "reasoning_content" } }Expected Behavior
Provider-level compatibility applies to every model of the provider and should survive normalization under both config shapes, exactly like name, options/settings, and models do. V1 config files are documented as remaining compatible in V2.
Actual Behavior
Under the legacy provider shape, compatibility is silently dropped for the provider and for all of its models. No warning, no error, and /api/model simply omits the key. The setting is therefore a no-op for providers declared that way.
Additional Context
- Counts observed in one config: legacy shape →
compatibilityabsent on 37/37 custom models; V2 shape → present on 37/37. The only model still without it is a catalog model that genuinely has no reasoning metadata. - This is easy to hit when migrating: the V1 shape is what the older docs and existing configs use, and the V2 shape is required to get the documented behavior.
- Related: config: compatibility.supportsStore silently dropped from opencode.jsonc #52617 (other
compatibility.*keys silently ignored), desktop: model picker shows "No reasoning" for all V2 models — capabilities.reasoning is never set #50257 (the model tooltip always reports "No reasoning" — a separate hardcoded-false bug in the app/web bundle).
Activity
opencode-agent commentedon Oct 15, 2026
Thanks for the detailed report. I can confirm this on 2.0.29, and it still happens on the latest
dev(4397ee44).With the
acmeprovider in the oldprovider/npm/optionsshape and a provider-levelcompatibility: { reasoningField: "reasoning_content" },GET /api/modelreturnsacme/glm-5.2withoutcompatibility. The same provider in theproviders/package/settingsshape returnscompatibility: { reasoningField: "reasoning_content" }. The script below runs both configs againstopencode serveand prints the result for each.It looks like the config loader reads old-shape providers with the v1 provider schema. That schema has no
compatibilityfield, so the key is dropped with no warning. For now, writing the provider in theprovidersshape (as you did) works around it.Reproduction script (repro.sh)
Run it:
bash repro.shFinn763 commentedon Oct 15, 2026
Taking this one. The legacy (
provider/npm/options) shape is validated by the v1 provider schema, which has nocompatibilityfield, so the key is silently dropped. I'll extend the v1 schema to preserve it (same field definition as the v2 shape) and pin it with a config-load test.Finn763 commentedon Oct 15, 2026
PR is up: #55703 — the v1 provider schema now accepts the optional
compatibilityblock (sameProviderCompatibilityschema the v2 shape uses) andmigrateStandardProvidercarries it through, with the legacysetCacheKeyderivation merged underneath an explicit block. Two normalization cases added; both are red on the base (the block was dropped) and green after.opencode-agent commentedon Oct 16, 2026
Closing this as not planned. The config mixes the two shapes:
compatibilityis a V2providersfield and has never been part of the V1providerschema, so it isn't recognized inside a V1providerentry.Use one shape or the other:
providers+package+settings+compatibility, as in your second example.interleaved, which is migrated tocompatibility.reasoningField: