Issue Description
When a conversation runs on a custom (BYOK) OpenAI-compatible provider, Qoder unconditionally injects the non-standard top-level parameter enable_thinking into the chat-completion request body. Upstreams that strictly validate top-level parameters reject the whole request, so BYOK against those providers is completely unusable — every message fails within ~1 second.
Affected provider in my case: NVIDIA NIM (https://integrate.api.nvidia.com/v1). enable_thinking is not part of NVIDIA’s public API surface — it is an internal chat-template field, and NVIDIA’s hosted endpoints expose it as the standard reasoning_effort instead. Any unknown top-level key triggers a hard 400.
This is not an account, quota or network issue: the upstream answers the request in under a second with a parameter-validation error that names the offending key.
Steps to Reproduce
- Qoder desktop → add a custom provider:
type = openai-compatible,protocol = openai,baseUrl = https://integrate.api.nvidia.com/v1, a valid NVIDIA API key. - Add any model under it, e.g.
nvidia/nemotron-3-super-120b-a12b. - Start a new chat, select that model, send any one-line message.
- The turn fails after ~1s showing only a generic “系统发生异常 / Something went wrong” card, with no reason surfaced.
- To get the real error, open
~/.qoder/logs/sessions/<project>/<sessionId>/segments/*.jsonland readmodel.request.attempt_failed.
Already ruled out — this is not fixable from configuration:
capabilities.thinking.modes: []+supportsEffort: false+supportedEffortLevels: []capabilities.thinking.modes: []+supportsEffort: true+supportedEffortLevels: ["medium","high"]
Both were tested after fully quitting and restarting Qoder (confirmed the new settings.json was picked up; settings.json is the only store for provider config, there is no second cache). Both still produce the identical 400.
Expected Behavior
The client should not inject vendor-private parameters into a BYOK request. Concretely, either:
- Emit
enable_thinkingonly when the model explicitly declares thinking support (capabilities.thinking.modesnon-empty) — including not emitting it with valuefalsewhen thinking is off; or - Expose a per-provider
omitParams/paramAllowlistfield so users can drop parameters their upstream rejects; or - Place such vendor-specific fields inside
chat_template_kwargs/extra_bodyrather than at the top level.
Separately: when the upstream returns a 4xx with an explicit message, that message should be shown in the UI instead of the generic error card. This failure cost me a long debugging session that the error text alone would have resolved in seconds.
Actual Behavior
Every request fails immediately and the UI gives no diagnostic. Real error from the session log:
model.request.attempt_failed attempt=1 max_attempts=11 status=400
error_message="Custom provider request failed: 400 Validation: Unsupported parameter(s): `enable_thinking`"
retryable=false will_retry=false
turn.finished duration_ms=993
error="The model service rejected this request. Please check the request and try again."
Cause, traced in @qoder-ai/qoder-agent-sdk → dist/_worker/qoder-worker-runtime.obf.mjs (minified names from build 0.4.3). The custom-openai parameter mapping writes the key whenever a reasoning object exists, including the disabled case:
switch (r.thinking?.type) {
case "disabled": i.enable_thinking = !1; break; // key still present, value false
case "adaptive": t && (i.enable_thinking = !0); break;
case "enabled": i.enable_thinking = !0, i[e] = r.thinking.budgetTokens; break;
default: void 0 !== r.enabled && (i.enable_thinking = r.enabled);
}
The layer above always supplies an effort value, whose default is "none" (visible in the run log as [session.runtime_config] … reasoning_effort=none). "none" is a truthy string, so this branch runs:
C.reasoning_effort = w;
"none" === w ? (C.enable_thinking = !1, delete C.reasoning_budget_tokens)
: (C.enable_thinking = !0, …);
Result: enable_thinking is in the body on every BYOK request, with false when thinking is off and true when it is on. NVIDIA rejects the key regardless of its value, so no capability declaration can avoid the 400.
Scope: reproducible with every model declared under this provider (16 failures, 0 successes across nvidia/nemotron-3-ultra-550b-a55b, nvidia/nemotron-3-super-120b-a12b, deepseek-ai/deepseek-v4-flash, moonshotai/kimi-k3). vLLM / SGLang style deployments that also keep enable_thinking inside chat_template_kwargs are very likely hit by the same thing.
Workaround in use: a loopback-only Node shim on 127.0.0.1 that deletes enable_thinking / thinking_budget / reasoning_budget_tokens / preserve_thinking and keeps the standard reasoning_effort before forwarding. That is not something end users should have to build, which is why I’m reporting it.
Screenshots / Screen Recordings
The generic error card shown for a 400 that carries an explicit upstream message (attach screenshot here).
Operating System
Windows 11 Pro, build 22000, x64
Current Qoder Version (Menu → About Qoder → Copy)
0.4.3 (stable)
If the exact “Copy” string differs, please prefer the copied value; the running build is confirmed from the crash stack as:
productVersion 0.4.3, commit e921dfbf7e82e00ee61078e57540d2a614f8fa0f,
buildNumber 76166284, electron 43.1.1, win32-x64, buildTime 2026-09-26