diff --git a/open-sse/executors/base/reasoningEffort.ts b/open-sse/executors/base/reasoningEffort.ts index 3f35d89221..b25eda95f5 100644 --- a/open-sse/executors/base/reasoningEffort.ts +++ b/open-sse/executors/base/reasoningEffort.ts @@ -158,11 +158,11 @@ export function supportsMaxEffortForProvider(provider: string, model: string): b const isOpencodeGoDeepSeek = provider === "opencode-go" && resolvedModelId.toLowerCase().includes("deepseek"); const isOllamaCloud = provider === "ollama-cloud"; - // 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; + // Command Code's upstream API accepts the literal DeepSeek/OpenAI effort value + // `max`; do not rewrite it to OmniRoute's internal `xhigh` spelling. + const isCommandCode = provider === "command-code"; + return isClaude || isOpencodeGoDeepSeek || isOllamaCloud || isMoonshotK3 || isCommandCode; } // ── Effort carrier helpers (#7044) ────────────────────────────────────────── @@ -272,6 +272,17 @@ export function sanitizeReasoningEffortForProvider( return stripEffortValue(b, c); } + // Command Code accepts the literal top-tier value `max`, while the shared + // standardization stage may have already represented the client's `max` as + // OmniRoute's internal `xhigh`. Convert it back before the upstream request. + if (provider === "command-code" && effortStr === "xhigh") { + log?.info?.( + "REASONING_SANITIZE", + `${provider}/${modelStr}: normalized reasoning_effort xhigh → max` + ); + return writeEffortValue(b, "max", c); + } + // Native DeepSeek (api.deepseek.com) — V4 thinking mode accepts reasoning_effort // ONLY as {high, max} (its own top tier is literally "max"). OmniRoute's internal // scale is low|medium|high|xhigh where xhigh is the top, so map onto DeepSeek's diff --git a/tests/unit/base-executor-sanitize-effort.test.ts b/tests/unit/base-executor-sanitize-effort.test.ts index 925e342ea2..2a712106c5 100644 --- a/tests/unit/base-executor-sanitize-effort.test.ts +++ b/tests/unit/base-executor-sanitize-effort.test.ts @@ -783,6 +783,45 @@ test("sanitizeReasoningEffortForProvider: opencode-go DeepSeek V4 Pro preserves } }); +type EffortCarrierResult = { + reasoning_effort?: string; + reasoning?: { effort?: string }; +}; + +test("sanitizeReasoningEffortForProvider: command-code preserves literal max", () => { + const body = { reasoning_effort: "max" }; + const result = sanitizeReasoningEffortForProvider( + body, + "command-code", + "deepseek/deepseek-v4-flash", + null + ) as EffortCarrierResult; + assert.equal(result.reasoning_effort, "max"); +}); + +test("sanitizeReasoningEffortForProvider: command-code preserves nested literal max", () => { + const body = { reasoning: { effort: "max" } }; + const result = sanitizeReasoningEffortForProvider( + body, + "command-code", + "gpt-5.6-luna", + null + ) as EffortCarrierResult; + assert.equal(result.reasoning?.effort, "max"); +}); + +test("sanitizeReasoningEffortForProvider: command-code maps normalized xhigh back to max", () => { + const body = { reasoning_effort: "xhigh", reasoning: { effort: "xhigh" } }; + const result = sanitizeReasoningEffortForProvider( + body, + "command-code", + "gpt-5.6-luna", + null + ) as EffortCarrierResult; + assert.equal(result.reasoning_effort, "max"); + assert.equal(result.reasoning?.effort, "max"); +}); + 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. diff --git a/tests/unit/v1-models-auth-leak-9320.test.ts b/tests/unit/v1-models-auth-leak-9320.test.ts index 9d86d1c70e..9dc9b17a36 100644 --- a/tests/unit/v1-models-auth-leak-9320.test.ts +++ b/tests/unit/v1-models-auth-leak-9320.test.ts @@ -80,7 +80,10 @@ test("#9320: authenticated request (valid API key) returns 200 with models", asy // Create a valid API key await apiKeysDb.createApiKey("test-key-9320", "test-machine-9320"); const keys = await apiKeysDb.getApiKeys(); - const apiKey = Array.isArray(keys) ? keys.find((k) => k.name === "test-key-9320") : null; + type ApiKeyRecord = Awaited>[number]; + const apiKey = Array.isArray(keys) + ? keys.find((k: ApiKeyRecord) => k.name === "test-key-9320") + : null; assert.ok(apiKey, "API key must have been created"); const res = await v1ModelsCatalog.getUnifiedModelsResponse(