docs thinking
Thinking: one dial, every wire
1,467 parole · docs/thinking.md
rovecode has one effort dial — --effort, ROVECODE_EFFORT, /effort — with five positions:
auto | off | low | medium | high (src/core/types.ts ThinkingEffort). Every provider names the same idea
differently, and several cannot be switched off or have no reasoning mode at all. src/providers/thinking.ts
translates the dial per model family and says, in one sentence, what the model will actually receive.
/effort prints that sentence as its second line; rovecode model show [provider/model] prints all five.
Rules (Berkay, 2026-09-04):
- auto sends nothing — the endpoint's own default stands. The Claude 5 family reasons adaptively on its
own; the old default
offswitched that off and was most of "we are not getting the model's real performance".autois the runtime default. - off is an explicit disable where the API has one; otherwise nothing is sent and the sentence says why.
- low / medium / high map onto the endpoint's own vocabulary. Where it has fewer steps, medium rounds up (a rovecode "medium" asks for more than "low").
- A model that always reasons or has no reasoning mode receives nothing — sending a dial such a model
rejects is a hard 400, not a downgrade (a grok
-non-reasoningvariant; gpt-4-class on OpenAI proper). - The catalog's word is stronger than any regex: a model that models.dev lists without a reasoning mode
(
ModelRef.reasoning === false, stamped bycli/runtime.ts buildDef) receives nothing on every dialect. - Dialects are matched by model id, provider-agnostic (the same GLM under kaesra, zai or openrouter is one
family) — except OpenRouter, whose unified
reasoningobject wins for every model it fronts, and Groq's Qwen vocabulary.
Measured vs documented: only the Anthropic rows and GLM-5.3 are measured against the live endpoints
(stream.ts header, profiles.ts header, 2026-09-03). Every other row is the vendor's published API reference as
of 2026-09 and is marked doc. If an endpoint answers 400 to one of these fields, that row is wrong — fix the
dialect in thinking.ts, not the caller.
The matrix
— = nothing is sent (the sentence names the reason). Levels on one line mean low / medium / high.
| wire · family (dialect id) | auto | off | low / medium / high | status |
|---|---|---|---|---|
Anthropic Messages, effort shape — measured on claude-opus-5, claude-sonnet-5, claude-opus-4-5; the shape is learned, never hard-coded, and effort is the first guess for any Anthropic model |
— | thinking: {type: "disabled"} |
output_config: {effort: "low"/"medium"/"high"} |
measured |
| Anthropic Messages, budget shape — measured on claude-sonnet-4-5, claude-haiku-4-5; reached by flipping after the effort shape's 400 | — | thinking: {type: "disabled"} |
thinking: {type: "enabled", budget_tokens: 2048 / 8192 / 24576}; max_tokens raised to at least budget + 4096 |
measured |
OpenAI-compat · OpenRouter (any model, provider === "openrouter") |
— | reasoning: {enabled: false} |
reasoning: {effort: "low"/"medium"/"high"} |
doc |
OpenAI-compat · GLM-5.3 / 5.3-flash (glm-5.3) |
— | — (cannot be disabled; the endpoint default is max) | reasoning_effort: "low" / "high" / "max" (+ the profile's thinking: {type: "enabled", clear_thinking: false}); under ROVECODE_PROFILE=off the plain OpenAI word goes instead — that A/B is what the switch is for |
measured |
OpenAI-compat · GLM 4.5 – 5.x other than 5.3 (glm) |
— | thinking: {type: "disabled"} |
thinking: {type: "enabled"} (no levels) |
doc |
OpenAI-compat · DeepSeek chat / V3.x (deepseek) |
— | thinking: {type: "disabled"} |
thinking: {type: "enabled"} (no levels) |
doc |
| OpenAI-compat · DeepSeek reasoner / R1 | — | — (always thinks) | — | doc |
OpenAI-compat · Qwen / QwQ on DashScope and most hosts (qwen) |
— | enable_thinking: false |
enable_thinking: true, thinking_budget: 2048 / 8192 / 24576 |
doc |
| OpenAI-compat · Qwen on Groq | — | reasoning_effort: "none" |
reasoning_effort: "default" |
doc |
OpenAI-compat · Kimi K2.5 (kimi) |
— | thinking: {type: "disabled"} |
thinking: {type: "enabled"} |
doc |
| OpenAI-compat · Kimi K2 Thinking | — | — (always thinks) | — | doc |
| OpenAI-compat · Kimi K2 instruct | — | — (no thinking mode) | — | doc |
OpenAI-compat · Gemini via Google's OpenAI layer (gemini) |
— | Flash: extra_body.google.thinking_config.thinking_budget: 0 · Pro: — (minimum budget) |
reasoning_effort: "low"/"medium"/"high" (Google maps to a budget) |
doc |
OpenAI-compat · grok-3-mini (grok) |
— | — | reasoning_effort: "low" / "high" / "high" |
doc (no longer on docs.x.ai 2026-09-04) |
| OpenAI-compat · grok-4.3 / 4.5 / 4.6 / 4.20, and the retired grok-4-0709 / grok-4-fast slugs (served by grok-4.3 since 2026-05-15) | — | reasoning_effort: "none" |
reasoning_effort: "low"/"medium"/"high" |
fetched 2026-09-04 (docs.x.ai/docs/models/grok-4.3) |
OpenAI-compat · any grok -non-reasoning variant |
— | — (no reasoning mode) | — | fetched 2026-09-04 |
OpenAI-compat · gpt-oss (gpt-oss) |
— | — (low is the floor) | reasoning_effort: "low"/"medium"/"high" |
doc |
OpenAI-compat · o1 / o3 / o4 (openai o-series) |
— | — (cannot be disabled) | reasoning_effort: "low"/"medium"/"high" |
doc |
OpenAI-compat · gpt-5.1 and later (openai gpt-5) |
— | reasoning_effort: "none" |
reasoning_effort: "low"/"medium"/"high" |
doc |
| OpenAI-compat · gpt-5 / gpt-5-mini / gpt-5-nano | — | reasoning_effort: "minimal" (its floor) |
reasoning_effort: "low"/"medium"/"high" |
doc |
OpenAI-compat · gpt-4 class, gpt-3.5, chatgpt- (openai gpt-4 class) |
— | — (no reasoning mode; OpenAI rejects the field) | — | doc |
OpenAI-compat · Magistral (mistral) |
— | — (always reasons) | — | doc |
| OpenAI-compat · other Mistral models | — | — (no reasoning mode) | — | doc |
OpenAI-compat · MiniMax (minimax) |
— | — (always reasons) | — | doc |
OpenAI-compat · anything else (openai-compatible default) |
— | — (no common explicit disable) | reasoning_effort: "low"/"medium"/"high" — a model without a reasoning mode ignores it |
— |
| any dialect · catalog says no reasoning mode | — | — | — | catalog |
Not spoken natively: Gemini's own API (thinkingConfig), Ollama's native /api/chat (think), Vertex, Bedrock.
rovecode talks two wire protocols only (OpenAI-compatible and Anthropic Messages, providers.json protocol), so
Gemini is reached through Google's OpenAI-compatible layer or OpenRouter, and Ollama through its /v1 — where
reasoning_effort is honoured by gpt-oss and ignored by everything else, and there is no off (Ollama's
think: false lives on the native API). A Qwen behind vLLM wants chat_template_kwargs.enable_thinking, which
rovecode does not send; the DashScope pair is ignored there.
Anthropic: the learned shape
Anthropic has two request shapes and no model takes both (measured table in stream.ts). There is no
capability field to read, so anthropicPost tries the newer effort shape, and on a 400 that (a) says the shape
is unsupported and (b) mentions thinking/effort/budget, flips to the other shape and retries once. What
holds (pinned in test/unit/thinking.test.ts):
- Per provider + model. The memory key is
provider/model, so a gateway fronting the same model id cannot poison the direct endpoint's memory or the other way round. - Bounded. At most two sends per request; a 400 that is not about the dial ("image input is not supported for
this model") is returned as is and learns nothing;
autoandoffcarry no shape and never retry. - No oscillation. The flipped shape is remembered only when the retry was not the same refusal. A model that refuses both shapes leaves no memory: every request costs the same two sends and one error turn, never a flip-flop that alternates guaranteed failures.
- Process lifetime. A model's shape does not change under us; nothing is persisted.
Seeing it
/effort high
thinking: high — I reason before answering. It costs output tokens and delays the first word.
deepseek/deepseek-chat receives: thinking: { type: "enabled" } (DeepSeek has an on/off switch, no levels)
rovecode model show # the default model
rovecode model show openai/gpt-4o # any model
openai/gpt-4o (as named)
protocol openai
dialect catalog: no reasoning mode — the catalog lists no reasoning mode
effort auto (ROVECODE_EFFORT / --effort / /effort)
* auto nothing — auto: the endpoint's own default stands
off nothing — the catalog lists gpt-4o without a reasoning mode, so no dial is sent
…
Files
src/providers/thinking.ts— the dialect table,thinkingPlan,thinkingTable,thinkingLine,thinkingReport, the Anthropic budgets and both shapessrc/providers/stream.ts— spreads the plan into the body;anthropicPostlearns the shape (shapeByModel,anthropicShapeFor)src/providers/profiles.ts— GLM-5.3's words and wire fields (measured)src/core/types.ts—ThinkingEffort,ModelRef.reasoning;src/cli/runtime.ts buildDefstampsreasoningfrom the catalogsrc/core/voice.ts effortNote(level, receives);src/tui/app.ts/effort;src/cli/main.tsmodel showtest/unit/thinking.test.ts(the matrix, the adapters, the shape memory),test/unit/stream.test.ts,test/unit/effort-auto.test.ts