From bc52d4a459b1d546e87981fb7a1bc0e163bbdb71 Mon Sep 17 00:00:00 2001 From: zerone0x Date: Tue, 24 Feb 2026 04:36:20 +0100 Subject: [PATCH] fix(openrouter): skip reasoning effort injection for 'auto' routing model The 'auto' model on OpenRouter dynamically routes to any underlying model OpenRouter selects, including reasoning-required endpoints. Previously, OpenClaw would unconditionally inject `reasoning.effort: "none"` into every request when the thinking level was "off", which causes a 400 error on models where reasoning is mandatory and cannot be disabled. Root cause: - openrouter/auto has reasoning: false in the built-in catalog - With thinking level "off", createOpenRouterWrapper injects `reasoning: { effort: "none" }` via mapThinkingLevelToOpenRouterReasoningEffort - For any OpenRouter-routed model that requires reasoning this results in: "400 Reasoning is mandatory for this endpoint and cannot be disabled" - The reasoning: false is then persisted back to models.json on every ensureOpenClawModelsJson call, so manually removing it has no lasting effect Fix: - In applyExtraParamsToAgent, when provider is "openrouter" and the model id is "auto", pass undefined as thinkingLevel to createOpenRouterWrapper so no reasoning.effort is injected at all, letting OpenRouter's upstream model handle it natively - Add an explanatory comment in buildOpenrouterProvider clarifying that the reasoning: false catalog value does NOT cause effort injection for "auto" Users who need explicit reasoning control should target a specific model id (e.g. openrouter/deepseek/deepseek-r1) rather than the auto router. Fixes #24851 (cherry picked from commit aa554397980972d917dece09ab03c4cc15f5d100) --- src/agents/models-config.providers.ts | 6 ++++++ src/agents/pi-embedded-runner/extra-params.ts | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/agents/models-config.providers.ts b/src/agents/models-config.providers.ts index 3662ce9a3..4f921b6dd 100644 --- a/src/agents/models-config.providers.ts +++ b/src/agents/models-config.providers.ts @@ -685,6 +685,12 @@ function buildOpenrouterProvider(): ProviderConfig { { id: OPENROUTER_DEFAULT_MODEL_ID, name: "OpenRouter Auto", + // reasoning: false here is a catalog default only; it does NOT cause + // `reasoning.effort: "none"` to be sent for the "auto" routing model. + // applyExtraParamsToAgent skips the reasoning effort injection for + // model id "auto" because it dynamically routes to any OpenRouter model + // (including ones where reasoning is mandatory and cannot be disabled). + // See: openclaw/openclaw#24851 reasoning: false, input: ["text", "image"], cost: OPENROUTER_DEFAULT_COST, diff --git a/src/agents/pi-embedded-runner/extra-params.ts b/src/agents/pi-embedded-runner/extra-params.ts index 66b077af2..0d88bdf08 100644 --- a/src/agents/pi-embedded-runner/extra-params.ts +++ b/src/agents/pi-embedded-runner/extra-params.ts @@ -546,7 +546,14 @@ export function applyExtraParamsToAgent( if (provider === "openrouter") { log.debug(`applying OpenRouter app attribution headers for ${provider}/${modelId}`); - agent.streamFn = createOpenRouterWrapper(agent.streamFn, thinkingLevel); + // "auto" is a dynamic routing model — we don't know which underlying model + // OpenRouter will select, and it may be a reasoning-required endpoint. + // Omit the thinkingLevel so we never inject `reasoning.effort: "none"`, + // which would cause a 400 on models where reasoning is mandatory. + // Users who need reasoning control should target a specific model ID. + // See: openclaw/openclaw#24851 + const openRouterThinkingLevel = modelId === "auto" ? undefined : thinkingLevel; + agent.streamFn = createOpenRouterWrapper(agent.streamFn, openRouterThinkingLevel); agent.streamFn = createOpenRouterSystemCacheWrapper(agent.streamFn); }