fix: pass max reasoning effort through by default, add global model registry fallback (#8057)

This commit is contained in:
Mo'men Qatr
2026-08-06 16:04:56 +03:00
committed by diegosouzapw
parent aae408f585
commit dbf6cff7d7
4 changed files with 320 additions and 167 deletions

View File

@@ -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
}
}
}
}

View File

@@ -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;
}

View File

@@ -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 maxxhigh`
);
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;
}

View File

@@ -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<string, unknown>).reasoning_effort, "xhigh");
assert.equal(
(result as Record<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>;
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<string, unknown>;
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<string, unknown>).reasoning.effort, "max");
assert.equal((result as Record<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).reasoning.effort, "max");
assert.equal(
(result as Record<string, unknown>).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<string, unknown>).reasoning.effort, "xhigh");
assert.equal(
(result as Record<string, unknown>).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<string, unknown>).reasoning.effort, "max");
assert.equal((result as Record<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).reasoning_effort, "max");
assert.equal(
(result as Record<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).reasoning.effort, "max");
assert.equal(
(result as Record<string, unknown>).reasoning.summary,
"auto",
"other reasoning fields preserved"
);
assert.equal((result as Record<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>).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<string, unknown>)),
"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<string, unknown>).output_config.effort, "high");
});