Versions — pi 0.87.1 (macOS), @commandcode/pi-commandcode-provider 0.3.0.
Problem
pi's thinking levels for every Command Code model stop at high. max never shows up when cycling with shift+tab or in /thinking, and asking for it on the command line is silently clamped back to high:
$ pi -p --model command-code/deepseek/deepseek-v4.1-flash:max "reply with exactly: ok"
ok
$ # the session log records the level that was actually used:
$ grep thinking_level_change ~/.pi/agent/sessions/**/*.jsonl
{"type":"thinking_level_change","thinkingLevel":"high"}
PI_REASONING_LEVEL=max behaves the same way, and nothing warns that the request was downgraded. This applies to all 81 models in the catalog, not just DeepSeek, and no configuration reaches max — modelThinkingLevels and defaultThinkingLevel are clamped the same way.
max is a value the gateway accepts (POST /provider/v1/chat/completions, deepseek/deepseek-v4.1-flash, max_tokens: 16):
reasoning_effort |
gateway |
low / medium / high / max |
200 |
minimal |
400 (see below) |
Why
getSupportedThinkingLevels() in @earendil-works/pi-ai:
const EXTENDED_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
export function getSupportedThinkingLevels(model) {
if (!model.reasoning) return ["off"];
return EXTENDED_THINKING_LEVELS.filter((level) => {
const mapped = model.thinkingLevelMap?.[level];
if (mapped === null) return false;
if (level === "xhigh" || level === "max") return mapped !== undefined; // opt-in
return true;
});
}
xhigh and max are opt-in — pi only offers them when thinkingLevelMap names them, and docs/rpc-commands.md:278 describes them as levels "exposed only when supported by the selected model". toModel never sets thinkingLevelMap, so the offered set is off | minimal | low | medium | high, and clampThinkingLevel() pulls anything above high back down to it.
The mirror image: minimal is offered but rejected
The same missing map makes the fallback "every level that is not opt-in is supported", so pi offers minimal and the gateway answers 400:
$ pi -p --model command-code/deepseek/deepseek-v4.1-flash:minimal "reply with exactly: ok"
400: {"message":"Invalid option: expected one of \"low\"|\"medium\"|\"high\"|\"xhigh\"|\"max\"","type":"invalid_request_error","param":"reasoning_effort"}
Reproduced against 12 models on the /chat/completions route — deepseek/deepseek-v4.1-flash, deepseek/deepseek-v4-pro, Qwen/Qwen3.8-Max, MiniMaxAI/MiniMax-M3, moonshotai/Kimi-K3, zai-org/GLM-5.3, google/gemini-3.8-flash, xai/grok-4.7, meta/muse-spark-1.3, xiaomi/mimo-v2.6-pro, sakana/fugu-ultra, gpt-6-astra — all return that same message. off is unaffected: pi omits the field entirely and the request returns 200. The /messages route (Claude) is untested here, plan-limited on this key.
Fix
Only minimal is a clear-cut bug — the gateway rejects it, so it should not be offered:
model.thinkingLevelMap = { minimal: null };
Adding max: "max" on top of that puts max back into shift+tab for the /chat/completions models, which is what this report is about:
model.thinkingLevelMap = { minimal: null, max: "max" };
Whether max deserves a place on every open model, and whether xhigh belongs anywhere here, is a call for the maintainer — xhigh is an OpenAI-style value that pi treats as a per-model opt-in, and nothing in the catalog currently claims it. /provider/v1/models returns no capability fields today (id, object, created, owned_by, name, context_length, supported_endpoints), so a per-model reasoning_efforts list there would let toModel derive the whole map instead of hardcoding one, the same way pricing, max_output_tokens, modalities and reasoning are already read when the API sends them.
Versions — pi 0.87.1 (macOS),
@commandcode/pi-commandcode-provider0.3.0.Problem
pi's thinking levels for every Command Code model stop at
high.maxnever shows up when cycling withshift+tabor in/thinking, and asking for it on the command line is silently clamped back tohigh:PI_REASONING_LEVEL=maxbehaves the same way, and nothing warns that the request was downgraded. This applies to all 81 models in the catalog, not just DeepSeek, and no configuration reachesmax—modelThinkingLevelsanddefaultThinkingLevelare clamped the same way.maxis a value the gateway accepts (POST /provider/v1/chat/completions,deepseek/deepseek-v4.1-flash,max_tokens: 16):reasoning_effortlow/medium/high/maxminimalWhy
getSupportedThinkingLevels()in@earendil-works/pi-ai:xhighandmaxare opt-in — pi only offers them whenthinkingLevelMapnames them, anddocs/rpc-commands.md:278describes them as levels "exposed only when supported by the selected model".toModelnever setsthinkingLevelMap, so the offered set isoff | minimal | low | medium | high, andclampThinkingLevel()pulls anything abovehighback down to it.The mirror image:
minimalis offered but rejectedThe same missing map makes the fallback "every level that is not opt-in is supported", so pi offers
minimaland the gateway answers 400:Reproduced against 12 models on the
/chat/completionsroute —deepseek/deepseek-v4.1-flash,deepseek/deepseek-v4-pro,Qwen/Qwen3.8-Max,MiniMaxAI/MiniMax-M3,moonshotai/Kimi-K3,zai-org/GLM-5.3,google/gemini-3.8-flash,xai/grok-4.7,meta/muse-spark-1.3,xiaomi/mimo-v2.6-pro,sakana/fugu-ultra,gpt-6-astra— all return that same message.offis unaffected: pi omits the field entirely and the request returns 200. The/messagesroute (Claude) is untested here, plan-limited on this key.Fix
Only
minimalis a clear-cut bug — the gateway rejects it, so it should not be offered:Adding
max: "max"on top of that putsmaxback intoshift+tabfor the/chat/completionsmodels, which is what this report is about:Whether
maxdeserves a place on every open model, and whetherxhighbelongs anywhere here, is a call for the maintainer —xhighis an OpenAI-style value that pi treats as a per-model opt-in, and nothing in the catalog currently claims it./provider/v1/modelsreturns no capability fields today (id, object, created, owned_by, name, context_length, supported_endpoints), so a per-modelreasoning_effortslist there would lettoModelderive the whole map instead of hardcoding one, the same waypricing,max_output_tokens,modalitiesandreasoningare already read when the API sends them.