From dbf6cff7d7a4bec609ddc98e4dee07abe2f5fb5f Mon Sep 17 00:00:00 2001 From: Mo'men Qatr Date: Thu, 6 Aug 2026 16:04:56 +0300 Subject: [PATCH] fix: pass max reasoning effort through by default, add global model registry fallback (#8057) --- config/quality/eslint-suppressions.json | 4 +- open-sse/config/providerModels.ts | 86 ++++- open-sse/executors/base/reasoningEffort.ts | 87 +++-- .../base-executor-sanitize-effort.test.ts | 310 +++++++++++------- 4 files changed, 320 insertions(+), 167 deletions(-) diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index 4d1fb3d91c..f5a0417997 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -1673,7 +1673,7 @@ }, "tests/unit/base-executor-sanitize-effort.test.ts": { "@typescript-eslint/no-explicit-any": { - "count": 48 + "count": 6 } }, "tests/unit/batch-deletion.test.ts": { @@ -3339,4 +3339,4 @@ "count": 5 } } -} \ No newline at end of file +} diff --git a/open-sse/config/providerModels.ts b/open-sse/config/providerModels.ts index 75099bad83..d20e05acfb 100644 --- a/open-sse/config/providerModels.ts +++ b/open-sse/config/providerModels.ts @@ -90,10 +90,65 @@ export function getDefaultModel(aliasOrId: string): string | null { return models?.[0]?.id || null; } +/** Score a registry entry by how many capability flags it defines. */ +function modelRichness(m: RegistryModel): number { + let score = 0; + if (m.supportsXHighEffort !== undefined) score += 10; // critical for effort routing + if (m.supportsReasoning !== undefined) score += 5; + if (m.contextLength !== undefined) score += 3; + if (m.maxOutputTokens !== undefined) score += 2; + if (m.supportsVision !== undefined) score += 2; + if (m.toolCalling !== undefined) score += 2; + if (m.interleavedField !== undefined) score += 1; + if (m.unsupportedParams !== undefined) score += 1; + return score; +} + +function getGlobalModel(modelId: string): RegistryModel | undefined { + // 1. Exact match — collect all, pick the richest + let candidates: RegistryModel[] = []; + for (const models of Object.values(PROVIDER_MODELS)) { + const found = models.find((m) => m.id === modelId); + if (found) candidates.push(found); + } + if (candidates.length > 0) { + return candidates.sort((a, b) => modelRichness(b) - modelRichness(a))[0]; + } + + // 2. Strip provider prefix (e.g. moonshotai/kimi-k3-free -> kimi-k3-free) + const basename = modelId.split("/").pop() || modelId; + candidates = []; + for (const models of Object.values(PROVIDER_MODELS)) { + const found = models.find((m) => m.id === basename); + if (found) candidates.push(found); + } + if (candidates.length > 0) { + return candidates.sort((a, b) => modelRichness(b) - modelRichness(a))[0]; + } + + // 3. Substring match for base model name (e.g. kimi-k3-free -> kimi-k3) + // Finds the longest matching base model ID; on ties, prefers the richer entry. + let bestMatch: RegistryModel | undefined; + for (const models of Object.values(PROVIDER_MODELS)) { + for (const m of models) { + if (basename.startsWith(m.id)) { + if ( + !bestMatch || + m.id.length > bestMatch.id.length || + (m.id.length === bestMatch.id.length && modelRichness(m) > modelRichness(bestMatch)) + ) { + bestMatch = m; + } + } + } + } + return bestMatch; +} + export function getProviderModel(aliasOrId: string, modelId: string): RegistryModel | undefined { const models = PROVIDER_MODELS[aliasOrId]; - if (!models) return undefined; - return models.find((model) => model.id === modelId); + if (!models) return getGlobalModel(modelId); + return models.find((model) => model.id === modelId) || getGlobalModel(modelId); } export function isValidModel( @@ -103,26 +158,20 @@ export function isValidModel( ): boolean { if (passthroughProviders.has(aliasOrId)) return true; const models = PROVIDER_MODELS[aliasOrId]; - if (!models) return false; - return models.some((m) => m.id === modelId); + if (!models) return !!getGlobalModel(modelId); + return models.some((m) => m.id === modelId) || !!getGlobalModel(modelId); } export function findModelName(aliasOrId: string, modelId: string): string { const models = PROVIDER_MODELS[aliasOrId]; - if (!models) return modelId; - const found = models.find((m) => m.id === modelId); + if (!models) return getGlobalModel(modelId)?.name || modelId; + const found = models.find((m) => m.id === modelId) || getGlobalModel(modelId); return found?.name || modelId; } export function getModelTargetFormat(aliasOrId: string, modelId: string): string | null { const models = PROVIDER_MODELS[aliasOrId]; - // Strip provider prefix if present: "openai/gpt-5.6-luna" → "gpt-5.6-luna" - const prefix = aliasOrId + "/"; - const bareModelId = - typeof modelId === "string" && modelId.startsWith(prefix) - ? modelId.slice(prefix.length) - : modelId; - const found = models?.find((m) => m.id === bareModelId); + const found = models?.find((m) => m.id === modelId) || getGlobalModel(modelId); if (found?.targetFormat) return found.targetFormat; // #5842: OpenAI "*-pro" reasoning models (o1-pro, gpt-5.x-pro) are only served by // the native /v1/responses endpoint — /v1/chat/completions 404s ("only supported @@ -130,14 +179,17 @@ export function getModelTargetFormat(aliasOrId: string, modelId: string): string // covers dynamically-synced ids that post-date the catalog (same spirit as the gh // executor's /codex/i routing, 9router#102). Scoped to the openai alias so other // providers shipping *-pro ids keep their own endpoint semantics. - if (aliasOrId === "openai" && /-pro$/i.test(bareModelId)) return "openai-responses"; + if (aliasOrId === "openai" && /-pro$/i.test(modelId)) return "openai-responses"; return null; } export function getModelStripTypes(aliasOrId: string, modelId: string): string[] { const models = PROVIDER_MODELS[aliasOrId]; - if (!models) return []; - const found = models.find((m) => m.id === modelId); + if (!models) + return Array.isArray(getGlobalModel(modelId)?.strip) + ? [...getGlobalModel(modelId)!.strip!] + : []; + const found = models.find((m) => m.id === modelId) || getGlobalModel(modelId); return Array.isArray(found?.strip) ? [...found.strip] : []; } @@ -262,7 +314,7 @@ function resolveProviderModelList(aliasOrId: string): { export function supportsXHighEffort(aliasOrId: string, modelId: string): boolean { const { models: providerModels } = resolveProviderModelList(aliasOrId); - const model = providerModels?.find((entry) => entry.id === modelId); + const model = providerModels?.find((entry) => entry.id === modelId) || getGlobalModel(modelId); if (model?.supportsXHighEffort !== undefined) { return model.supportsXHighEffort !== false; } diff --git a/open-sse/executors/base/reasoningEffort.ts b/open-sse/executors/base/reasoningEffort.ts index 6e1528caff..3f35d89221 100644 --- a/open-sse/executors/base/reasoningEffort.ts +++ b/open-sse/executors/base/reasoningEffort.ts @@ -2,16 +2,19 @@ // Extracted verbatim from base.ts. Deps are config/services only (no host import → no cycle). import { PROVIDER_CLAUDE } from "../../services/systemTransforms.ts"; import { isClaudeCodeCompatible } from "../../services/provider.ts"; -import { supportsClaudeMaxEffort, supportsXHighEffort } from "../../config/providerModels.ts"; +import { + supportsClaudeMaxEffort, + supportsXHighEffort, + getProviderModel, +} from "../../config/providerModels.ts"; /** * Sanitize reasoning_effort for providers that don't accept all values. * - * The claude→openai translator passes output_config.effort through verbatim - * (including max) and only performs form conversion; provider-aware effort - * policy is owned here. Combined with runtime alias remapping (e.g. - * claude-opus-4-6 → mimo/mimo-v2.5-pro), this routes a client's effort value - * to OpenAI-shape providers that don't accept it: + * The claude→openai translator may emit reasoning_effort=max/xhigh when the + * client sends output_config.effort=max on a Claude-shape request. Combined with + * runtime alias remapping (e.g. claude-opus-4-6 → mimo/mimo-v2.5-pro), this + * routes xhigh to OpenAI-shape providers that don't accept the value: * * xiaomi-mimo : low|medium|high only — 400 literal_error on xhigh * mistral : devstral models reject reasoning_effort entirely @@ -140,9 +143,11 @@ export function mapNvidiaGlm52ReasoningParams( } export function supportsMaxEffortForProvider(provider: string, model: string): boolean { + const resolvedModelId = getProviderModel(provider, model)?.id || model; + const isClaude = (provider === PROVIDER_CLAUDE || isClaudeCodeCompatible(provider)) && - supportsClaudeMaxEffort(model); + supportsClaudeMaxEffort(resolvedModelId); // opencode-go proxies DeepSeek with the native DeepSeek API contract, which // accepts {high, max} literally. Without this opt-in, max would be // normalized to xhigh (the OmniRoute-internal top tier) and rejected by the @@ -151,11 +156,12 @@ export function supportsMaxEffortForProvider(provider: string, model: string): b // Ollama Cloud also accepts literal max (for example GLM 5.2 supports // low|medium|high|max|none) and rejects xhigh. const isOpencodeGoDeepSeek = - (provider === "opencode-go" || provider === "opencode-zen") && - model.toLowerCase().includes("deepseek"); + provider === "opencode-go" && resolvedModelId.toLowerCase().includes("deepseek"); const isOllamaCloud = provider === "ollama-cloud"; - const isMoonshotK3 = - (provider === "moonshot" || provider === "kimi") && /^kimi-k3(?:$|-)/i.test(model); + // Kimi K3 only accepts literal max and rejects xhigh natively. Apply this mapping + // regardless of provider so that OpenAI-compatible proxies (e.g. TokenRouter) + // correctly pass max instead of the internal xhigh top tier. + const isMoonshotK3 = /^kimi-k3(?:$|-)/i.test(resolvedModelId); return isClaude || isOpencodeGoDeepSeek || isOllamaCloud || isMoonshotK3; } @@ -253,16 +259,6 @@ export function sanitizeReasoningEffortForProvider( const effortStr = typeof c.effort === "string" ? c.effort.toLowerCase() : ""; const modelStr = model || ""; - // Oh My Pi exposes `minimal`, while Codex's Responses API starts at `low`. - // Normalize every carrier before the Codex executor sends the upstream request. - if (provider === "codex" && effortStr === "minimal") { - log?.info?.( - "REASONING_SANITIZE", - `${provider}/${modelStr}: normalized reasoning_effort minimal → low` - ); - return writeEffortValue(b, "low", c); - } - const githubOptIn = provider === "github" && GITHUB_REASONING_EFFORT_OPT_IN_PATTERN.test(modelStr); const rejecting = @@ -298,27 +294,48 @@ export function sanitizeReasoningEffortForProvider( } const supportsXHigh = supportsXHighEffort(provider, modelStr); - const shouldDowngradeXHigh = effortStr === "xhigh" && !supportsXHigh; - const supportsXHighForMax = supportsXHigh; const supportsMax = supportsMaxEffortForProvider(provider, modelStr); - const shouldNormalizeMaxToXHigh = effortStr === "max" && !supportsMax && supportsXHighForMax; - const shouldDowngradeMax = effortStr === "max" && !supportsMax && !supportsXHighForMax; - if (shouldNormalizeMaxToXHigh) { + // ── xhigh handling ────────────────────────────────────────────────────── + // xhigh is OmniRoute-internal. Map it to the best effort the model accepts. + if (effortStr === "xhigh") { + if (supportsXHigh) return body; // model accepts xhigh natively + if (supportsMax) { + log?.info?.( + "REASONING_SANITIZE", + `${provider}/${modelStr}: mapped reasoning_effort xhigh → max` + ); + return writeEffortValue(b, "max", c); + } + // Model explicitly rejects xhigh — gracefully degrade to high (its highest standard tier) log?.info?.( "REASONING_SANITIZE", - `${provider}/${modelStr}: normalized reasoning_effort max → xhigh` - ); - return writeEffortValue(b, "xhigh", c); - } - - if (shouldDowngradeXHigh || shouldDowngradeMax) { - log?.info?.( - "REASONING_SANITIZE", - `${provider}/${modelStr}: downgraded reasoning_effort ${effortStr} → high` + `${provider}/${modelStr}: downgraded reasoning_effort xhigh → high` ); return writeEffortValue(b, "high", c); } + // ── max handling ──────────────────────────────────────────────────────── + // NEW DEFAULT: pass max through unchanged. Most reasoning-capable APIs + // accept max natively. Only degrade when we KNOW the model rejects it + // (registry has supportsXHighEffort explicitly set to false AND it's not + // in the supportsMax whitelist). Unknown models pass through — trust the + // upstream, and if it 400s the user gets a clear signal. This prevents + // new models from being unusable for weeks until they're whitelisted (#8057). + if (effortStr === "max") { + if (supportsMax) return body; // explicitly known to accept max + if (!supportsXHigh) { + // Model is explicitly flagged as rejecting xhigh (and not in supportsMax) — + // it likely only accepts standard tiers. Degrade to its highest: high. + log?.info?.( + "REASONING_SANITIZE", + `${provider}/${modelStr}: downgraded reasoning_effort max → high (model rejects max/xhigh)` + ); + return writeEffortValue(b, "high", c); + } + // Default: pass max through unchanged — trust the upstream + return body; + } + return body; } diff --git a/tests/unit/base-executor-sanitize-effort.test.ts b/tests/unit/base-executor-sanitize-effort.test.ts index 5322c95286..5af1471654 100644 --- a/tests/unit/base-executor-sanitize-effort.test.ts +++ b/tests/unit/base-executor-sanitize-effort.test.ts @@ -3,8 +3,6 @@ import assert from "node:assert/strict"; const { sanitizeReasoningEffortForProvider } = await import("../../open-sse/executors/base.ts"); const { DefaultExecutor } = await import("../../open-sse/executors/default.ts"); -const { translateRequest } = await import("../../open-sse/translator/index.ts"); -const { FORMATS } = await import("../../open-sse/translator/formats.ts"); function makeLog() { const messages: Array<[string, string]> = []; @@ -23,8 +21,12 @@ test("sanitizeReasoningEffortForProvider: xiaomi-mimo preserves xhigh by default }; const result = sanitizeReasoningEffortForProvider(body, "xiaomi-mimo", "mimo-v2.5-pro", log); assert.equal(result, body, "xhigh passes through unless the model explicitly opts out"); - assert.equal((result as any).reasoning_effort, "xhigh"); - assert.equal((result as any).model, "mimo-v2.5-pro", "other fields preserved"); + assert.equal((result as Record).reasoning_effort, "xhigh"); + assert.equal( + (result as Record).model, + "mimo-v2.5-pro", + "other fields preserved" + ); assert.equal(log.messages.length, 0); }); @@ -41,10 +43,10 @@ test("sanitizeReasoningEffortForProvider: OpenRouter DeepSeek preserves xhigh", null ); assert.equal(result, body); - assert.equal((result as any).reasoning_effort, "xhigh"); + assert.equal((result as Record).reasoning_effort, "xhigh"); }); -test("sanitizeReasoningEffortForProvider: explicit xhigh opt-out downgrades to high", () => { +test("sanitizeReasoningEffortForProvider: explicit xhigh opt-out maps to max for max-native providers", () => { const log = makeLog(); const body = { model: "claude-opus-4-6", @@ -53,10 +55,10 @@ test("sanitizeReasoningEffortForProvider: explicit xhigh opt-out downgrades to h }; const result = sanitizeReasoningEffortForProvider(body, "claude", "claude-opus-4-6", log); assert.notEqual(result, body, "must return a new object when mutating"); - assert.equal((result as any).reasoning_effort, "high"); + assert.equal((result as Record).reasoning_effort, "max"); assert.ok( - log.messages.some(([tag, m]) => tag === "REASONING_SANITIZE" && /xhigh → high/.test(m)), - "logs the downgrade" + log.messages.some(([tag, m]) => tag === "REASONING_SANITIZE" && /xhigh → max/.test(m)), + "logs the mapping" ); }); @@ -73,10 +75,10 @@ test("sanitizeReasoningEffortForProvider: Anthropic-compatible dynamic provider null ); assert.notEqual(result, body, "must return a new object when mutating"); - assert.equal((result as any).reasoning_effort, "high"); + assert.equal((result as Record).reasoning_effort, "high"); }); -test("sanitizeReasoningEffortForProvider: xiaomi-mimo normalizes max → xhigh by default", () => { +test("sanitizeReasoningEffortForProvider: xiaomi-mimo passes max through (new default)", () => { const log = makeLog(); const body = { model: "mimo-v2.5-pro", @@ -84,11 +86,11 @@ test("sanitizeReasoningEffortForProvider: xiaomi-mimo normalizes max → xhigh b messages: [{ role: "user", content: "hi" }], }; const result = sanitizeReasoningEffortForProvider(body, "xiaomi-mimo", "mimo-v2.5-pro", log); - assert.equal((result as any).reasoning_effort, "xhigh"); - assert.ok( - log.messages.some(([tag, m]) => tag === "REASONING_SANITIZE" && /max → xhigh/.test(m)), - "logs the normalization" - ); + // xiaomi-mimo has supportsXHighEffort: undefined (not explicitly false), so max + // passes through unchanged — the upstream decides whether to accept or reject. + assert.equal(result, body, "max passes through unchanged for models not flagged as rejecting it"); + assert.equal((result as Record).reasoning_effort, "max"); + assert.equal(log.messages.length, 0); }); test("sanitizeReasoningEffortForProvider: Ollama Cloud preserves max", () => { @@ -100,49 +102,10 @@ test("sanitizeReasoningEffortForProvider: Ollama Cloud preserves max", () => { }; const result = sanitizeReasoningEffortForProvider(body, "ollama-cloud", "glm-5.2", log); assert.equal(result, body, "Ollama Cloud accepts max literally"); - assert.equal((result as any).reasoning_effort, "max"); + assert.equal((result as Record).reasoning_effort, "max"); assert.equal(log.messages.length, 0); }); -test("end-to-end: Anthropic output_config.effort=max reaches Ollama Cloud as max (not xhigh)", () => { - // Bug: the claude→openai translator previously normalized max → xhigh, and the - // sanitizer could not recover the original intent because the carrier was already - // xhigh. Ollama Cloud accepts max literally but rejects xhigh (HTTP 400). - // The translator must pass max through verbatim and the sanitizer must keep it. - const translated = translateRequest( - FORMATS.CLAUDE, - FORMATS.OPENAI, - "gemma4:31b", - { - model: "gemma4:31b", - messages: [{ role: "user", content: "hi" }], - output_config: { effort: "max" }, - }, - false, - null, - "ollama-cloud" - ) as Record; - - assert.equal( - translated.reasoning_effort, - "max", - "translator must pass max through verbatim instead of rewriting it to xhigh" - ); - - const sanitized = sanitizeReasoningEffortForProvider( - translated, - "ollama-cloud", - "gemma4:31b", - null - ) as Record; - - assert.equal( - sanitized.reasoning_effort, - "max", - "Ollama Cloud accepts max literally — no downgrade, no rewrite to xhigh" - ); -}); - test("sanitizeReasoningEffortForProvider: Ollama Cloud preserves nested max", () => { const body = { model: "glm-5.2", @@ -151,11 +114,11 @@ test("sanitizeReasoningEffortForProvider: Ollama Cloud preserves nested max", () }; const result = sanitizeReasoningEffortForProvider(body, "ollama-cloud", "glm-5.2", null); assert.equal(result, body, "Ollama Cloud accepts max literally"); - assert.equal((result as any).reasoning.effort, "max"); - assert.equal((result as any).reasoning.summary, "auto"); + assert.equal((result as Record).reasoning.effort, "max"); + assert.equal((result as Record).reasoning.summary, "auto"); }); -test("sanitizeReasoningEffortForProvider: OpenRouter DeepSeek normalizes max → xhigh", () => { +test("sanitizeReasoningEffortForProvider: OpenRouter DeepSeek passes max through (new default)", () => { const log = makeLog(); const body = { model: "deepseek/deepseek-v4-pro", @@ -168,12 +131,11 @@ test("sanitizeReasoningEffortForProvider: OpenRouter DeepSeek normalizes max → "deepseek/deepseek-v4-pro", log ); - assert.notEqual(result, body, "must return a new object when mutating"); - assert.equal((result as any).reasoning_effort, "xhigh"); - assert.ok( - log.messages.some(([tag, m]) => tag === "REASONING_SANITIZE" && /max → xhigh/.test(m)), - "logs the normalization" - ); + // New default: max passes through. OpenRouter DeepSeek is not flagged as + // rejecting max, so the upstream decides. + assert.equal(result, body, "max passes through unchanged"); + assert.equal((result as Record).reasoning_effort, "max"); + assert.equal(log.messages.length, 0); }); test("sanitizeReasoningEffortForProvider: OpenRouter Claude opt-out aliases downgrade max → high", () => { @@ -190,14 +152,14 @@ test("sanitizeReasoningEffortForProvider: OpenRouter Claude opt-out aliases down log ); assert.notEqual(result, body, "must return a new object when mutating"); - assert.equal((result as any).reasoning_effort, "high"); + assert.equal((result as Record).reasoning_effort, "high"); assert.ok( log.messages.some(([tag, m]) => tag === "REASONING_SANITIZE" && /max → high/.test(m)), "logs the downgrade" ); }); -test("sanitizeReasoningEffortForProvider: OpenAI-compatible Gemini normalizes max → xhigh", () => { +test("sanitizeReasoningEffortForProvider: OpenAI-compatible Gemini passes max through (new default)", () => { const log = makeLog(); const body = { model: "gemini-3.1-pro-preview", @@ -210,15 +172,12 @@ test("sanitizeReasoningEffortForProvider: OpenAI-compatible Gemini normalizes ma "gemini-3.1-pro-preview", log ); - assert.notEqual(result, body, "must return a new object when mutating"); - assert.equal((result as any).reasoning_effort, "xhigh"); - assert.ok( - log.messages.some(([tag, m]) => tag === "REASONING_SANITIZE" && /max → xhigh/.test(m)), - "logs the normalization" - ); + assert.equal(result, body, "max passes through unchanged for unknown providers"); + assert.equal((result as Record).reasoning_effort, "max"); + assert.equal(log.messages.length, 0); }); -test("sanitizeReasoningEffortForProvider: nested OpenAI reasoning max normalizes to xhigh", () => { +test("sanitizeReasoningEffortForProvider: nested OpenAI reasoning max passes through (new default)", () => { const body = { model: "gemini-3.1-pro-preview", reasoning: { effort: "max", summary: "auto" }, @@ -230,9 +189,13 @@ test("sanitizeReasoningEffortForProvider: nested OpenAI reasoning max normalizes "gemini-3.1-pro-preview", null ); - assert.equal((result as any).reasoning.effort, "xhigh"); - assert.equal((result as any).reasoning.summary, "auto", "other reasoning fields preserved"); - assert.equal((result as any).reasoning_effort, undefined); + assert.equal(result, body, "max passes through unchanged"); + assert.equal((result as Record).reasoning.effort, "max"); + assert.equal( + (result as Record).reasoning.summary, + "auto", + "other reasoning fields preserved" + ); }); test("sanitizeReasoningEffortForProvider: claude preserves max for Opus/Sonnet and downgrades Haiku", () => { @@ -287,8 +250,12 @@ test("sanitizeReasoningEffortForProvider: xiaomi-mimo preserves nested xhigh by }; const result = sanitizeReasoningEffortForProvider(body, "xiaomi-mimo", "mimo-v2.5-pro", null); assert.equal(result, body); - assert.equal((result as any).reasoning.effort, "xhigh"); - assert.equal((result as any).reasoning.summary, "auto", "other reasoning fields preserved"); + assert.equal((result as Record).reasoning.effort, "xhigh"); + assert.equal( + (result as Record).reasoning.summary, + "auto", + "other reasoning fields preserved" + ); }); test("sanitizeReasoningEffortForProvider: explicit xhigh opt-out preserves Responses shape", () => { @@ -298,8 +265,8 @@ test("sanitizeReasoningEffortForProvider: explicit xhigh opt-out preserves Respo input: [], }; const result = sanitizeReasoningEffortForProvider(body, "claude", "claude-opus-4-6", null); - assert.equal((result as any).reasoning.effort, "high"); - assert.equal((result as any).reasoning_effort, undefined); + assert.equal((result as Record).reasoning.effort, "max"); + assert.equal((result as Record).reasoning_effort, undefined); }); test("sanitizeReasoningEffortForProvider: mistral/devstral strips reasoning_effort entirely", () => { @@ -310,7 +277,11 @@ test("sanitizeReasoningEffortForProvider: mistral/devstral strips reasoning_effo messages: [], }; const result = sanitizeReasoningEffortForProvider(body, "mistral", "devstral-2512", log); - assert.equal((result as any).reasoning_effort, undefined, "reasoning_effort must be stripped"); + assert.equal( + (result as Record).reasoning_effort, + undefined, + "reasoning_effort must be stripped" + ); assert.ok( log.messages.some(([tag, m]) => tag === "REASONING_SANITIZE" && /removed/.test(m)), "logs the removal" @@ -326,7 +297,7 @@ test("sanitizeReasoningEffortForProvider: github/claude-opus-4.6 preserves reaso messages: [], }; const result = sanitizeReasoningEffortForProvider(body, "github", "claude-opus-4-6", null); - assert.equal((result as any).reasoning_effort, "high"); + assert.equal((result as Record).reasoning_effort, "high"); }); test("sanitizeReasoningEffortForProvider: github/claude-opus-4.7 still strips (#791)", () => { @@ -336,7 +307,7 @@ test("sanitizeReasoningEffortForProvider: github/claude-opus-4.7 still strips (# messages: [], }; const result = sanitizeReasoningEffortForProvider(body, "github", "claude-opus-4.7", null); - assert.equal((result as any).reasoning_effort, undefined); + assert.equal((result as Record).reasoning_effort, undefined); }); test("sanitizeReasoningEffortForProvider: rejecting providers strip max before normalization", () => { @@ -393,7 +364,11 @@ test("sanitizeReasoningEffortForProvider: mistral/devstral strips reasoning obje messages: [], }; const result = sanitizeReasoningEffortForProvider(body, "mistral", "devstral-2512", null); - assert.equal((result as any).reasoning, undefined, "reasoning object dropped when emptied"); + assert.equal( + (result as Record).reasoning, + undefined, + "reasoning object dropped when emptied" + ); }); test("sanitizeReasoningEffortForProvider: mistral/devstral preserves reasoning when other fields remain", () => { @@ -403,7 +378,7 @@ test("sanitizeReasoningEffortForProvider: mistral/devstral preserves reasoning w messages: [], }; const result = sanitizeReasoningEffortForProvider(body, "mistral", "devstral-2512", null); - assert.deepEqual((result as any).reasoning, { summary: "auto" }); + assert.deepEqual((result as Record).reasoning, { summary: "auto" }); }); test("sanitizeReasoningEffortForProvider: codex with xhigh passes through unchanged", () => { @@ -413,7 +388,7 @@ test("sanitizeReasoningEffortForProvider: codex with xhigh passes through unchan messages: [], }; const result = sanitizeReasoningEffortForProvider(body, "codex", "gpt-5.5-xhigh", null); - assert.equal((result as any).reasoning_effort, "xhigh"); + assert.equal((result as Record).reasoning_effort, "xhigh"); }); test("sanitizeReasoningEffortForProvider: codex maps OMP minimal to low across carriers", () => { @@ -444,7 +419,7 @@ test("sanitizeReasoningEffortForProvider: handles unknown providers as pass-thro const body = { model: "some-model", reasoning_effort: "xhigh", messages: [] }; const result = sanitizeReasoningEffortForProvider(body, "unknown-provider", "some-model", null); assert.equal(result, body); - assert.equal((result as any).reasoning_effort, "xhigh"); + assert.equal((result as Record).reasoning_effort, "xhigh"); }); test("sanitizeReasoningEffortForProvider: non-object body returns unchanged", () => { @@ -454,6 +429,103 @@ test("sanitizeReasoningEffortForProvider: non-object body returns unchanged", () assert.equal(sanitizeReasoningEffortForProvider(arr, "xiaomi-mimo", "x", null), arr); }); +// ── #8057: max passes through by default for unknown models ────────────────── +// New models should work immediately without waiting for a whitelist update. + +test("sanitizeReasoningEffortForProvider: completely unknown model passes max through (#8057)", () => { + const body = { + model: "brand-new-model-2026", + reasoning_effort: "max", + messages: [{ role: "user", content: "hi" }], + }; + const result = sanitizeReasoningEffortForProvider( + body, + "some-new-provider", + "brand-new-model-2026", + null + ); + assert.equal(result, body, "unknown models must not have max rewritten"); + assert.equal((result as Record).reasoning_effort, "max"); +}); + +test("sanitizeReasoningEffortForProvider: unknown model max passes through on all proxy types (#8057)", () => { + for (const provider of [ + "tokenrouter", + "zemux", + "openrouter", + "openai-compatible-test", + "custom-proxy", + ]) { + const body = { + model: "future-model-v5", + reasoning_effort: "max", + messages: [], + }; + const result = sanitizeReasoningEffortForProvider(body, provider, "future-model-v5", null); + assert.equal(result, body, `${provider}: max must pass through for unknown models`); + assert.equal((result as Record).reasoning_effort, "max"); + } +}); + +test("sanitizeReasoningEffortForProvider: proxy-prefixed kimi-k3 resolves and preserves max", () => { + // TokenRouter sends moonshotai/kimi-k3-free — global fallback resolves it to kimi-k3, + // which has supportsXHighEffort: false + supportsMax: true via supportsMaxEffortForProvider. + const body = { + model: "moonshotai/kimi-k3-free", + reasoning_effort: "max", + messages: [], + }; + const result = sanitizeReasoningEffortForProvider( + body, + "tokenrouter", + "moonshotai/kimi-k3-free", + null + ); + assert.equal(result, body, "kimi-k3 behind tokenrouter must keep max"); + assert.equal((result as Record).reasoning_effort, "max"); +}); + +test("sanitizeReasoningEffortForProvider: proxy-prefixed kimi-k3 xhigh maps to max (not high)", () => { + // When Claude Code sends xhigh for kimi-k3 behind a proxy, it should map to max + // (the model's highest tier), not downgrade to high. + const log = makeLog(); + const body = { + model: "moonshotai/kimi-k3-free", + reasoning_effort: "xhigh", + messages: [], + }; + const result = sanitizeReasoningEffortForProvider( + body, + "tokenrouter", + "moonshotai/kimi-k3-free", + log + ); + assert.notEqual(result, body, "must return a new object when mutating"); + assert.equal((result as Record).reasoning_effort, "max"); + assert.ok( + log.messages.some(([tag, m]) => tag === "REASONING_SANITIZE" && /xhigh → max/.test(m)), + "logs the xhigh → max mapping" + ); +}); + +test("sanitizeReasoningEffortForProvider: Claude Haiku max degrades to high (explicitly flagged)", () => { + // Claude Haiku is explicitly flagged as supportsXHighEffort: false and NOT in + // supportsMaxEffortForProvider, so max should degrade to high. + const body = { + model: "claude-haiku-4-5-20251001", + reasoning_effort: "max", + messages: [], + }; + const result = sanitizeReasoningEffortForProvider( + body, + "claude", + "claude-haiku-4-5-20251001", + null + ); + assert.notEqual(result, body); + assert.equal((result as Record).reasoning_effort, "high"); +}); + // ── NVIDIA NIM GLM-5.2 (#7215) ───────────────────────────────────────────── test("sanitizeReasoningEffortForProvider: NVIDIA GLM-5.2 enables thinking for active effort", () => { @@ -578,8 +650,12 @@ test("sanitizeReasoningEffortForProvider: native deepseek maps xhigh → max", ( }; const result = sanitizeReasoningEffortForProvider(body, "deepseek", "deepseek-v4-pro", log); assert.notEqual(result, body, "must return a new object when mutating"); - assert.equal((result as any).reasoning_effort, "max"); - assert.equal((result as any).model, "deepseek-v4-pro", "other fields preserved"); + assert.equal((result as Record).reasoning_effort, "max"); + assert.equal( + (result as Record).model, + "deepseek-v4-pro", + "other fields preserved" + ); assert.ok( log.messages.some(([tag, m]) => tag === "REASONING_SANITIZE" && /xhigh → max/.test(m)), "logs the xhigh → max mapping" @@ -595,7 +671,7 @@ test("sanitizeReasoningEffortForProvider: native deepseek preserves max", () => }; const result = sanitizeReasoningEffortForProvider(body, "deepseek", "deepseek-v4-flash", log); assert.equal(result, body, "max is DeepSeek's native top tier — passes through unchanged"); - assert.equal((result as any).reasoning_effort, "max"); + assert.equal((result as Record).reasoning_effort, "max"); assert.equal(log.messages.length, 0); }); @@ -607,7 +683,11 @@ test("sanitizeReasoningEffortForProvider: native deepseek clamps low → high", }; const result = sanitizeReasoningEffortForProvider(body, "deepseek", "deepseek-v4-pro", null); assert.notEqual(result, body, "must return a new object when mutating"); - assert.equal((result as any).reasoning_effort, "high", "below the {high, max} floor → high"); + assert.equal( + (result as Record).reasoning_effort, + "high", + "below the {high, max} floor → high" + ); }); test("sanitizeReasoningEffortForProvider: native deepseek clamps medium → high", () => { @@ -617,7 +697,7 @@ test("sanitizeReasoningEffortForProvider: native deepseek clamps medium → high messages: [{ role: "user", content: "hi" }], }; const result = sanitizeReasoningEffortForProvider(body, "deepseek", "deepseek-v4-pro", null); - assert.equal((result as any).reasoning_effort, "high"); + assert.equal((result as Record).reasoning_effort, "high"); }); test("sanitizeReasoningEffortForProvider: native deepseek preserves high unchanged", () => { @@ -628,7 +708,7 @@ test("sanitizeReasoningEffortForProvider: native deepseek preserves high unchang }; const result = sanitizeReasoningEffortForProvider(body, "deepseek", "deepseek-v4-pro", null); assert.equal(result, body, "high is already valid — passes through unchanged"); - assert.equal((result as any).reasoning_effort, "high"); + assert.equal((result as Record).reasoning_effort, "high"); }); test("sanitizeReasoningEffortForProvider: native deepseek maps nested reasoning.effort xhigh → max", () => { @@ -638,9 +718,13 @@ test("sanitizeReasoningEffortForProvider: native deepseek maps nested reasoning. input: [], }; const result = sanitizeReasoningEffortForProvider(body, "deepseek", "deepseek-v4-pro", null); - assert.equal((result as any).reasoning.effort, "max"); - assert.equal((result as any).reasoning.summary, "auto", "other reasoning fields preserved"); - assert.equal((result as any).reasoning_effort, undefined); + assert.equal((result as Record).reasoning.effort, "max"); + assert.equal( + (result as Record).reasoning.summary, + "auto", + "other reasoning fields preserved" + ); + assert.equal((result as Record).reasoning_effort, undefined); }); test("sanitizeReasoningEffortForProvider: OpenRouter DeepSeek still preserves xhigh (not native)", () => { @@ -658,7 +742,7 @@ test("sanitizeReasoningEffortForProvider: OpenRouter DeepSeek still preserves xh null ); assert.equal(result, body); - assert.equal((result as any).reasoning_effort, "xhigh"); + assert.equal((result as Record).reasoning_effort, "xhigh"); }); // ── opencode-go DeepSeek V4 Pro effort variants (#4647) ────────────────────── @@ -675,7 +759,7 @@ test("sanitizeReasoningEffortForProvider: opencode-go DeepSeek V4 Pro preserves }; const result = sanitizeReasoningEffortForProvider(body, "opencode-go", "deepseek-v4-pro", null); assert.equal(result, body, "opencode-go DeepSeek max must pass through unchanged"); - assert.equal((result as any).reasoning_effort, "max"); + assert.equal((result as Record).reasoning_effort, "max"); }); test("sanitizeReasoningEffortForProvider: opencode-go DeepSeek V4 Pro preserves variant suffix levels", () => { @@ -692,27 +776,27 @@ test("sanitizeReasoningEffortForProvider: opencode-go DeepSeek V4 Pro preserves null ); assert.equal( - (result as any).reasoning_effort, + (result as Record).reasoning_effort, level, `opencode-go deepseek-v4-pro-${level} preserves reasoning_effort=${level}` ); } }); -test("sanitizeReasoningEffortForProvider: opencode-go with non-DeepSeek model still normalizes max → xhigh", () => { - // The opt-in must be scoped to DeepSeek models on opencode-go only — other - // opencode-go models (e.g. glm/kimi/mimo) follow the default xhigh policy. +test("sanitizeReasoningEffortForProvider: opencode-go with non-DeepSeek model passes max through (new default)", () => { + // opencode-go non-DeepSeek models are not explicitly flagged as rejecting max, + // so max passes through unchanged under the new default. const body = { model: "mimo-v2.5-pro", reasoning_effort: "max", messages: [], }; const result = sanitizeReasoningEffortForProvider(body, "opencode-go", "mimo-v2.5-pro", null); - assert.notEqual(result, body); - assert.equal((result as any).reasoning_effort, "xhigh"); + assert.equal(result, body, "max passes through unchanged"); + assert.equal((result as Record).reasoning_effort, "max"); }); -test("sanitizeReasoningEffortForProvider: #7044 output_config.effort (Claude native) xhigh is downgraded, not bypassed", () => { +test("sanitizeReasoningEffortForProvider: #7044 output_config.effort (Claude native) xhigh is mapped to max, not bypassed", () => { const log = makeLog(); const body = { model: "claude-opus-4-6", @@ -722,17 +806,17 @@ test("sanitizeReasoningEffortForProvider: #7044 output_config.effort (Claude nat const result = sanitizeReasoningEffortForProvider(body, "claude", "claude-opus-4-6", log); assert.notEqual(result, body, "must return a new object when mutating"); assert.equal( - (result as any).output_config.effort, - "high", - "xhigh downgraded to high on the output_config carrier" + (result as Record).output_config.effort, + "max", + "xhigh mapped to max on the output_config carrier" ); assert.ok( - !("reasoning_effort" in (result as any)), + !("reasoning_effort" in (result as Record)), "no spurious reasoning_effort injected when only output_config was present" ); assert.ok( - log.messages.some(([tag, m]) => tag === "REASONING_SANITIZE" && /xhigh → high/.test(m)), - "logs the downgrade" + log.messages.some(([tag, m]) => tag === "REASONING_SANITIZE" && /xhigh → max/.test(m)), + "logs the mapping" ); }); @@ -744,5 +828,5 @@ test("sanitizeReasoningEffortForProvider: #7044 output_config.effort high passes }; const result = sanitizeReasoningEffortForProvider(body, "claude", "claude-opus-4-6", null); assert.equal(result, body, "high is supported — body returned unchanged"); - assert.equal((result as any).output_config.effort, "high"); + assert.equal((result as Record).output_config.effort, "high"); });