zai-org / zai-org/feedback

[Bug] v3.12.1 custom openai-compatible provider (OpenCode Go): reasoning enabled builds Anthropic-format request with thinking field → 400 invalid_request

Open
#629 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

priority: P2
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:

  1. 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 when reasoning.enabled=false, showing the format flip is triggered by the reasoning config, not the endpoint choice.
  2. Anthropic-only field injected: thinking is 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

  1. ZCode v3.12.1 → Settings → Providers → add custom provider: baseURL https://opencode.ai/zen/go/v1, API format Chat Completions, OpenCode Go API key, model glm-5.3-flash with reasoning enabled (variants low/max/high).
  2. Start a conversation → every turn fails with invalid_request: json: unknown field "thinking".
  3. Inspect ~/.zcode/v2/logs/<date>.log → the request shows endpointType: "anthropic" and requestUrl: .../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|high on the OpenAI-compatible path (or mapped server-side), not injected as an Anthropic thinking object, and the Anthropic suffix must not be appended to the provider's base URL.

Actual Behavior

  • Anthropic format is forced for the request; double /v1 URL; thinking injected; 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.