OpenRouter reasoning models: chat-completions path never sends the `reasoning` param and drops `reasoning_details`
Summary
On the chat-completions path, PAL never sends OpenRouter's reasoning request field, so reasoning effort can't be controlled via thinking_mode for OpenRouter reasoning models (e.g. z-ai/glm-5.2). Independently, the returned reasoning_details is silently dropped — the handler reads only message.content. The Responses-API path does send the reasoning param, but it also drops the trace, so reasoning output is surfaced on neither path.
Where (upstream 7afc7c1, v9.8.2, providers/openai_compatible.py)
_generate_with_responses_endpoint: readsdefault_reasoning_effortand sends"reasoning": {"effort": …}(≈L416–423); reached only whenuse_openai_response_apiis set.generate_content(chat-completions):completion_params(≈L586–610) never includes areasoningfield; onlytemperature,max_tokensand a fixed kwargs whitelist (top_p,frequency_penalty,presence_penalty,seed,stop,stream) are forwarded.- chat-completions
_attempt()reads onlyresponse.choices[0].message.content(≈L643);message.reasoning/reasoning_detailsare never read (the Responses path likewise extracts onlyoutput_text). - The effort field already exists:
default_reasoning_effort(providers/shared/model_capabilities.py:56) — it's just not applied on this path. - Note: OpenRouter's
reasoningis a nested body object (reasoning: {effort|enabled|max_tokens}), distinct from OpenAI's top-levelreasoning_effort— hence OpenRouter-specific, passed via the SDK'sextra_body.
Repro (live, z-ai/glm-5.2)
Same prompt, only the reasoning field toggled:
reasoning:{enabled:true} → message.reasoning_details present · usage…reasoning_tokens = 156
reasoning:{enabled:false} → no reasoning_details · reasoning_tokens = 0So OpenRouter returns a real reasoning trace when asked — but it never reaches the caller, because the chat-completions handler reads only message.content. And without reasoning:{…} in the body, effort can't be set at all.
Proposed direction (small, reuses existing pieces)
On the chat-completions path, when the provider is OpenRouter and the model is reasoning-capable (gate on supports_extended_thinking), send reasoning via extra_body — effort from default_reasoning_effort (the field the Responses path already uses), optionally overridden by the per-call thinking_mode. Effort vocabulary can be model-specific (e.g. GLM-5.2 accepts xhigh), so the general mapping should stay conservative. Surface the returned reasoning_details on ModelResponse.metadata, consistent with the existing pattern; whether to also expose it in content/history is a design call I'd defer to maintainers.
Gated so non-reasoning models and non-OpenRouter (OpenAI-compatible) providers are untouched.
I have a working PoC doing exactly this (verified live: GLM-5.2 returns its reasoning trace at a controllable effort; non-flagged models unchanged) — a one-file diff vs 7afc7c1:
https://github.com/BeehiveInnovations/pal-mcp-server/compare/7afc7c1...sergiobe31:pal-mcp-server:openrouter-reasoning
Happy to open a PR if this is a direction you'd accept — wanted to check first.
Source: BeehiveInnovations/pal-mcp-server