Summary
ucode pi with databricks-openai/system.ai.gpt-5, -gpt-5-mini, -gpt-5-nano or -gpt-5-5-pro fails on every request with:
OpenAI API error (400): 400 status code (no body)
Cause: _pi_gpt_model_entry declares reasoning: True but never sets thinkingLevelMap, so when thinking is off Pi sends reasoning: {effort: "none"}, which those four models reject.
Root cause
src/ucode/agents/pi.py:157-172:
def _pi_gpt_model_entry(model_id: str) -> dict:
entry: dict = {"id": model_id, "contextWindow": ..., "maxTokens": ...}
if "gpt-5" in model_id.lower().replace(".", "-"):
entry["reasoning"] = True
entry["input"] = ["text", "image"]
return entry
Pi's contract (pi-ai/dist/api/openai-responses.js:243-247) is that the model entry declares its own off-state; absent thinkingLevelMap it defaults to "none":
else if (model.provider !== "github-copilot" && model.thinkingLevelMap?.off !== null) {
params.reasoning = { effort: (model.thinkingLevelMap?.off ?? "none") };
}
"none" is only valid on gpt-5.1+. We never set the override, so older ids get an invalid value.
Measured, against /ai-gateway/codex/v1/responses
| Model |
effort:"none" |
effort:"minimal" |
omitted |
gpt-5, gpt-5-mini, gpt-5-nano |
400 |
200 |
200 |
gpt-5-5-pro |
400 |
400 |
200 |
gpt-5-1/-2/-3-codex/-4/-4-mini/-4-nano/-5/-6-luna/-6-sol/-6-terra |
200 |
200 |
200 |
Gateway response on failure:
{"error_code":"BAD_REQUEST","message":"{\n \"error\": {\n \"message\": \"Unsupported value: 'none' is not supported with the 'gpt-5' model. Supported values are: 'minimal', 'low', 'medium', and 'high'.\"..."}
I bisected the rest of Pi's Responses payload against the gateway and it is all fine: store:false, prompt_cache_key, prompt_cache_retention:"24h", prompt_cache_options, include:["reasoning.encrypted_content"], developer role, flat tool schemas with additionalProperties:false, and the underscore session_id / x-client-request-id affinity headers — all 200.
Why the error message is useless
Pi's error-body.js recovery only reads error.error, and the gateway wraps its payload as {"error_code", "message"}. So e.error === undefined, the body is discarded, and every gateway 400 renders identically. Filed upstream separately. That is why this took so long to pin down, but the config bug is ours.
Proposed fix
Set the off-state explicitly in _pi_gpt_model_entry. thinkingLevelMap: {"off": None} makes Pi omit reasoning entirely (the ?.off !== null branch above) — verified 200 on all 14 ids, and it's the same pattern we already use for the Gemini 3.x entries.
if "gpt-5" in model_id.lower().replace(".", "-"):
entry["reasoning"] = True
entry["input"] = ["text", "image"]
entry["thinkingLevelMap"] = {"off": None}
{"off": "minimal"} is the alternative if we want to keep an explicit low-reasoning signal, but it still 400s on gpt-5-5-pro, so it would need per-model handling.
Worth noting databricks-openai is the only provider block in pi.py with no compat/capability hardening — databricks-claude needed supportsEagerToolInputStreaming: False and databricks-mlflow needed supportsStore/supportsStrictMode: False. Same class of omission. The docstrings at pi.py:12-22 and the note at databricks.py:2446 ("required for oss only") should be updated too.
Happy to put up the PR.
Environment
- ucode 0.1.0, pi 0.83.0
- workspace:
dbc-a5d4177a-49dc.cloud.databricks.com
- config:
~/.ucode/pi-home/.pi/agent/models.json
Summary
ucode piwithdatabricks-openai/system.ai.gpt-5,-gpt-5-mini,-gpt-5-nanoor-gpt-5-5-profails on every request with:Cause:
_pi_gpt_model_entrydeclaresreasoning: Truebut never setsthinkingLevelMap, so when thinking is off Pi sendsreasoning: {effort: "none"}, which those four models reject.Root cause
src/ucode/agents/pi.py:157-172:Pi's contract (
pi-ai/dist/api/openai-responses.js:243-247) is that the model entry declares its own off-state; absentthinkingLevelMapit defaults to"none":"none"is only valid on gpt-5.1+. We never set the override, so older ids get an invalid value.Measured, against
/ai-gateway/codex/v1/responseseffort:"none"effort:"minimal"gpt-5,gpt-5-mini,gpt-5-nanogpt-5-5-progpt-5-1/-2/-3-codex/-4/-4-mini/-4-nano/-5/-6-luna/-6-sol/-6-terraGateway response on failure:
{"error_code":"BAD_REQUEST","message":"{\n \"error\": {\n \"message\": \"Unsupported value: 'none' is not supported with the 'gpt-5' model. Supported values are: 'minimal', 'low', 'medium', and 'high'.\"..."}I bisected the rest of Pi's Responses payload against the gateway and it is all fine:
store:false,prompt_cache_key,prompt_cache_retention:"24h",prompt_cache_options,include:["reasoning.encrypted_content"],developerrole, flat tool schemas withadditionalProperties:false, and the underscoresession_id/x-client-request-idaffinity headers — all 200.Why the error message is useless
Pi's
error-body.jsrecovery only readserror.error, and the gateway wraps its payload as{"error_code", "message"}. Soe.error === undefined, the body is discarded, and every gateway 400 renders identically. Filed upstream separately. That is why this took so long to pin down, but the config bug is ours.Proposed fix
Set the off-state explicitly in
_pi_gpt_model_entry.thinkingLevelMap: {"off": None}makes Pi omitreasoningentirely (the?.off !== nullbranch above) — verified 200 on all 14 ids, and it's the same pattern we already use for the Gemini 3.x entries.{"off": "minimal"}is the alternative if we want to keep an explicit low-reasoning signal, but it still 400s ongpt-5-5-pro, so it would need per-model handling.Worth noting
databricks-openaiis the only provider block inpi.pywith nocompat/capability hardening —databricks-claudeneededsupportsEagerToolInputStreaming: Falseanddatabricks-mlflowneededsupportsStore/supportsStrictMode: False. Same class of omission. The docstrings atpi.py:12-22and the note atdatabricks.py:2446("required forossonly") should be updated too.Happy to put up the PR.
Environment
dbc-a5d4177a-49dc.cloud.databricks.com~/.ucode/pi-home/.pi/agent/models.json