You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Custom OpenAI-compatible model: /thinking offers high, which breaks llama.cpp Qwen3.8 template, and the failure is retried 5 times #53202
With a custom OpenAI-compatible provider (llama-swap → llama.cpp llama-server --jinja) serving Qwen3.8, /thinking offers Default, low, medium and high, although the model config declares no variants. Default works. Picking high makes every request fail, because the model's chat template only accepts low, medium and xhigh:
Error: Jinja Exception: Unexpected reasoning effort high. Supported types are xhigh (default), medium, and low.
llama-server answers HTTP 500 in ~7 ms, and opencode retries the request 5 times (⚠ Retrying in 7s · attempt 5) before showing the error.
Two issues:
Effort levels are offered for a custom model that declares none. high can never work here, and the backend's xhigh can't be selected.
A deterministic error is retried. In packages/opencode/src/session/retry.ts, retryable() retries any status >= 500, even when the error is not marked retryable.
Suggested fix for 2: don't retry a 500 whose body carries a template / validation error (Jinja Exception, chat template, raise_exception). The check would go in retryable(), and in statusReason() in packages/llm/src/route/executor.ts, which maps every >= 500 to the always-retryable ProviderInternalReason. I can open a PR if this direction is OK.
Thanks for the detailed report. Until we improve the defaults, you can work around the first issue by declaring the variants yourself.
When a custom model has no variants, opencode falls back to a generic low / medium / high effort list for openai-compatible providers. As soon as you set variants on the model, that fallback is skipped and /thinking shows only the variants you define. Each variant's settings.reasoningEffort is sent as reasoning_effort, so you can match exactly what the Qwen3.8 template accepts:
Default still sends no effort, so the template's own default applies. A variant can also set body (merged into the request JSON) or headers if a backend needs something other than reasoning_effort, for example "body": { "chat_template_kwargs": { ... } }.
We'll also look at making the built-in variant logic less presumptuous for custom OpenAI-compatible models, and at the retry behavior for deterministic 5xx errors.
Thanks for the detailed report and for offering a PR!
As far as I can tell, both behaviours are how v2 currently works on purpose, so I'm treating this as an improvement request:
Effort levels on custom models. If a custom OpenAI-compatible model sets neither variants nor capabilities.reasoning, OpenCode assumes it supports the generic low / medium / high efforts. You can replace that list with the efforts your template accepts, which also makes xhigh selectable:
Retrying the 500. In v2 the error is classified in packages/ai/src/provider-error.ts (packages/opencode/src/session/retry.ts isn't in the current repo). Every status >= 500 is treated as a temporary provider error and retried. Here llama.cpp sends a 500 for a request that can never succeed, so not retrying would mean recognising template errors in the response body. That's a reasonable idea, and maintainers will decide whether that kind of body check fits.
Another affected provider: Refuseless, with OpenCode v2.2.5 on macOS. This also affects clients consuming model.list (observed in T3 Code).
With a custom @opencode/ai/providers/openai-compatible provider at https://api.refuseless.com/v1, and neither variants nor capabilities.reasoning declared, opencode api model.list returns low, medium, high, each mapped to the same settings.reasoningEffort. Selecting Medium produced:
reasoning_effort must be one of off (or none), low, high, or max.
Confirmed the existing workaround on v2.2.5: adding this under the model's capabilities makes both a fresh standalone model.list and the active service after opencode reload return exactly Off/Low/High/Max:
Verification covered catalog output, without a new inference request. The provider rejection above came from the original failed request. The fallback is still present in packages/core/src/config/plugin/provider.ts, with the generic effort list in packages/core/src/variant.ts. Please consider this provider when revisiting inferred reasoning support for custom models.
Activity
github-actions commentedon Oct 4, 2026
Thanks for updating the issue with the required description, reproduction steps, and system information!
opencode-agent commentedon Oct 5, 2026
Thanks for the detailed report. Until we improve the defaults, you can work around the first issue by declaring the variants yourself.
When a custom model has no
variants, opencode falls back to a genericlow/medium/higheffort list foropenai-compatibleproviders. As soon as you setvariantson the model, that fallback is skipped and/thinkingshows only the variants you define. Each variant'ssettings.reasoningEffortis sent asreasoning_effort, so you can match exactly what the Qwen3.8 template accepts:Defaultstill sends no effort, so the template's own default applies. A variant can also setbody(merged into the request JSON) orheadersif a backend needs something other thanreasoning_effort, for example"body": { "chat_template_kwargs": { ... } }.We'll also look at making the built-in variant logic less presumptuous for custom OpenAI-compatible models, and at the retry behavior for deterministic 5xx errors.
opencodejr commentedon Oct 27, 2026
Thanks for the detailed report and for offering a PR!
As far as I can tell, both behaviours are how v2 currently works on purpose, so I'm treating this as an improvement request:
variantsnorcapabilities.reasoning, OpenCode assumes it supports the genericlow/medium/highefforts. You can replace that list with the efforts your template accepts, which also makesxhighselectable:packages/ai/src/provider-error.ts(packages/opencode/src/session/retry.tsisn't in the current repo). Every status>= 500is treated as a temporary provider error and retried. Here llama.cpp sends a 500 for a request that can never succeed, so not retrying would mean recognising template errors in the response body. That's a reasonable idea, and maintainers will decide whether that kind of body check fits.Related issues:
jokaperes commentedon Nov 4, 2026
Another affected provider: Refuseless, with OpenCode v2.2.5 on macOS. This also affects clients consuming
model.list(observed in T3 Code).With a custom
@opencode/ai/providers/openai-compatibleprovider athttps://api.refuseless.com/v1, and neithervariantsnorcapabilities.reasoningdeclared,opencode api model.listreturnslow,medium,high, each mapped to the samesettings.reasoningEffort. Selecting Medium produced:Affected model:
deepseek-v4.1-flash-abliterated-free. Refuseless documents that Medium is rejected, and publishes per-modelreasoning_efforts/reasoning_default: https://docs.refuseless.com/guides/reasoning and https://docs.refuseless.com/api/models.Confirmed the existing workaround on v2.2.5: adding this under the model's
capabilitiesmakes both a fresh standalonemodel.listand the active service afteropencode reloadreturn exactly Off/Low/High/Max:Verification covered catalog output, without a new inference request. The provider rejection above came from the original failed request. The fallback is still present in
packages/core/src/config/plugin/provider.ts, with the generic effort list inpackages/core/src/variant.ts. Please consider this provider when revisiting inferred reasoning support for custom models.