[Bug] v3.12.1 custom openai-compatible provider (OpenCode Go): reasoning enabled builds Anthropic-format request with thinking field → 400 invalid_request
Nobody has claimed this yet.
- Dominant language
- No language data
- Stars
- 22
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
[Bug] v3.12.1 custom openai-compatible provider (OpenCode Go): reasoning enabled → request built as Anthropic endpointType with thinking field, rejected 400 by OpenCode Go
Reporter: jojuega
ZCode: 3.12.1 (Electron app, macOS)
OS: macOS 26.5.2 (Apple Silicon)
Summary
Since the v3.12.1 update, a provider configured in the settings UI with API format = Chat Completions (/chat/completions) gets its request built in the Anthropic format whenever the model has reasoning.enabled: true. The engine appends the Anthropic suffix /v1/messages on top of the provider's own /v1 base URL (yielding .../v1/v1/messages), injects the Anthropic-only field thinking: {type: "enabled", budget_tokens: ...}, and OpenCode Go rejects the body:
reason=invalid_request status=400 retryable=false
Error from provider (Console Go): Upstream request failed: [invalid_request_error] invalid request body: json: unknown field "thinking"
TraceID: e7a70180-f974-4fbe-84c1-c6f631aac2d2
Evidence from ZCode's own debug log
Provider registered via the settings UI as openai-compatible, baseURL https://opencode.ai/zen/go/v1, model glm-5.3-flash, API format shown/selected: Chat Completions. With reasoning.enabled=true, the connectivity-test log shows:
{
"endpointType": "anthropic",
"requestUrl": "https://opencode.ai/zen/go/v1/v1/messages",
"body": {
"model": "glm-5.3-flash",
"max_tokens": 32001,
"output_config": { "effort": "max" },
"thinking": { "type": "enabled", "budget_tokens": 32000 },
"messages": [ ... ]
}
}
Two concrete defects are visible:
- Wrong adapter: URL becomes
.../v1/v1/messages(double/v1) — the Anthropic transport is used despite the UI config selecting chat-completions. For reference, the same provider id works whenreasoning.enabled=false, showing the format flip is triggered by the reasoning config, not the endpoint choice. - Anthropic-only field injected:
thinkingis not a valid field of the OpenAI chat-completions schema and OpenCode Go (Baseten-backed) strictly rejects unknown fields.
OpenCode Go accepts reasoning levels — via the correct format
Verified directly against OpenCode Go with a valid session header (key redacted):
POST https://opencode.ai/zen/go/v1/chat/completions
{"model":"glm-5.3-flash","max_tokens":8000,"reasoning_effort":"high", ...} → 200 OK
{"model":"glm-5.3-flash","max_tokens":8000,"reasoning_effort":"medium", ...} → 200
{"model":"glm-5.3-flash","max_tokens":8000,"reasoning_effort":"low", ...} → 200
{"model":"glm-5.3-flash","max_tokens":8000,"thinking":{"type":"enabled","budget_tokens":32000}, ...} → 400
[invalid_request_error] json: unknown field "thinking" ← exactly the field ZCode sends
So the model's Low/High/Max selector could work end-to-end: OpenCode Go accepts reasoning_effort on the chat-completions endpoint. ZCode just sends the reasoning budget in the Anthropic dialect on an endpoint (and via an endpointType) that can't take it.
Steps to Reproduce
- ZCode v3.12.1 → Settings → Providers → add custom provider: baseURL
https://opencode.ai/zen/go/v1, API format Chat Completions, OpenCode Go API key, modelglm-5.3-flashwith reasoning enabled (variants low/max/high). - Start a conversation → every turn fails with
invalid_request: json: unknown field "thinking". - Inspect
~/.zcode/v2/logs/<date>.log→ the request showsendpointType: "anthropic"andrequestUrl: .../v1/v1/messages.
Expected Behavior
- The API format chosen in the settings UI (chat-completions) must be respected; reasoning-preference should be translated to
reasoning_effort: low|medium|highon the OpenAI-compatible path (or mapped server-side), not injected as an Anthropicthinkingobject, and the Anthropic suffix must not be appended to the provider's base URL.
Actual Behavior
- Anthropic format is forced for the request; double
/v1URL;thinkinginjected; OpenCode Go answers 400 and the model is unusable whenever reasoning is enabled.
Workaround (works, but costs the preference selector)
Setting in ~/.zcode/v2/config.json:
.provider["<id>"].models["glm-5.3-flash"].reasoning.enabled = false
stops the field injection and the model works again — but with a fixed reasoning level; the Low/High/Max UI selector becomes a no-op.
Related
- #492 (x-opencode-session header, now resolved for ZCode)
- #306 (reasoning effort injection on custom providers — adjacent root area: mapping reasoning → request params; this report adds the providerKind/endpointType mismatch triggered by
reasoning.enabled)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
No source files are named in the report. Start at the settings UI provider registration and the connectivity-test request path, then trace how reasoning.enabled selects endpointType and maps request parameters. Done means Chat Completions providers retain their configured endpoint, send reasoning_effort instead of thinking, and pass the reported OpenCode Go reproduction.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- electron
- Domain
- api, backend-api-design
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 52/100