From afbd9361a7b32cab28bc8336c4ea20326fba5c47 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:21:14 -0300 Subject: [PATCH] fix(routing): recognize Kimi token-limit 400 as context overflow for combo fallback (#6637) (#6893) combo.ts's isContextOverflow400() guard required the literal word 'context' in the 400 error body before letting a combo fall through to the next target. Kimi's exact wording ('Your request exceeded model token limit: 262144 (requested: 308458)') never says 'context', so the guard misclassified it as a body-specific error and halted the whole combo instead of trying the next (larger-context) target. accountFallback.ts's CONTEXT_OVERFLOW_PATTERNS already recognized this wording one layer below (via checkFallbackError -> shouldFallback), so the two independently-maintained classifiers disagreed and the stricter one won. Export CONTEXT_OVERFLOW_PATTERNS from accountFallback.ts and reuse it inside combo.ts's isContextOverflow400() so both layers share a single source of truth. Regression test: tests/unit/repro-6637-kimi-token-limit.test.ts (RED on unfixed code -> GREEN after the fix). Existing #4519 guard tests (tests/unit/combo-param-validation-fallback-4519.test.ts) still pass, including the negative case that a genuinely body-specific 400 is NOT misclassified as overflow. --- .../fixes/6637-6637-combo-kimi-fallback.md | 1 + open-sse/services/accountFallback.ts | 5 ++- open-sse/services/combo.ts | 8 +++- .../unit/repro-6637-kimi-token-limit.test.ts | 41 +++++++++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 changelog.d/fixes/6637-6637-combo-kimi-fallback.md create mode 100644 tests/unit/repro-6637-kimi-token-limit.test.ts diff --git a/changelog.d/fixes/6637-6637-combo-kimi-fallback.md b/changelog.d/fixes/6637-6637-combo-kimi-fallback.md new file mode 100644 index 0000000000..be7cd2ed0b --- /dev/null +++ b/changelog.d/fixes/6637-6637-combo-kimi-fallback.md @@ -0,0 +1 @@ +- fix(routing): recognize Kimi-style "exceeded model token limit" 400 as context overflow so combo fallback continues to the next target (#6637) diff --git a/open-sse/services/accountFallback.ts b/open-sse/services/accountFallback.ts index 5a1e1888de..275ecbab41 100644 --- a/open-sse/services/accountFallback.ts +++ b/open-sse/services/accountFallback.ts @@ -189,7 +189,10 @@ export const OAUTH_INVALID_TOKEN_SIGNALS = [ // Context overflow patterns — the prompt exceeds the model's maximum context length. // Different providers phrase this differently. Used to decide whether a 400 error // should trigger combo fallback (a different model may have a larger context window). -const CONTEXT_OVERFLOW_PATTERNS = [ +// Exported so combo.ts's isContextOverflow400() guard (open-sse/services/combo.ts) +// can reuse this single source of truth instead of maintaining its own, +// independently-drifting pattern list (see issue #6637). +export const CONTEXT_OVERFLOW_PATTERNS = [ /\binput is too long\b/i, /\binput too long\b/i, /\bcontext.*(too long|exceeded|overflow|limit)/i, diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index 0beb8bfb79..69cec49793 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -8,6 +8,7 @@ import { checkFallbackError, classifyLockoutReason, + CONTEXT_OVERFLOW_PATTERNS, decayModelFailureCount, formatRetryAfter, getModelLockoutInfo, @@ -663,7 +664,12 @@ export function isContextOverflow400(errorText) { return ( /\bcontext.*(?:length_exceeded|too long|overflow|exceeded|window|limit)\b/i.test(errorText) || /exceeds.*context/i.test(errorText) || - /your input exceeds/i.test(errorText) + /your input exceeds/i.test(errorText) || + // Reuse accountFallback.ts's CONTEXT_OVERFLOW_PATTERNS (single source of truth) + // so wording like Kimi's "exceeded model token limit" — which never says the + // literal word "context" — is still recognized as an overflow/fallback-worthy + // 400 instead of halting the whole combo (issue #6637). + CONTEXT_OVERFLOW_PATTERNS.some((p) => p.test(errorText)) ); } /** @param {string} errorText */ diff --git a/tests/unit/repro-6637-kimi-token-limit.test.ts b/tests/unit/repro-6637-kimi-token-limit.test.ts new file mode 100644 index 0000000000..b27bc35595 --- /dev/null +++ b/tests/unit/repro-6637-kimi-token-limit.test.ts @@ -0,0 +1,41 @@ +// Repro probe for issue #6637: combo stops fallback after Kimi total token-limit 400. +// +// Observed provider response (Kimi, verbatim from the issue report): +// "Invalid request: Your request exceeded model token limit: 262144 (requested: 308458)" +// +// combo.ts's #2101 guard (handleComboChat) treats a 400 as a combo-halting +// "body-specific" error UNLESS isContextOverflow400() or isParamValidation400() +// recognizes it as a context/param overflow that should fall through to the next +// combo target. Kimi's exact wording ("... exceeded model token limit ...") does +// NOT contain the literal word "context", so isContextOverflow400() misses it even +// though accountFallback.ts's OWN CONTEXT_OVERFLOW_PATTERNS (used one layer below, +// to decide fallbackResult.shouldFallback) explicitly matches `/\btoken limit\b/i` +// and `/\bmax.*token/i`. The two classifiers disagree, and the stricter one wins, +// so the combo halts instead of trying the next (larger-context) target. +import assert from "node:assert/strict"; +import test from "node:test"; +import { isContextOverflow400, isParamValidation400 } from "../../open-sse/services/combo.ts"; + +const KIMI_ERROR_TEXT = + "Invalid request: Your request exceeded model token limit: 262144 (requested: 308458)"; + +test("#6637: Kimi's 'exceeded model token limit' 400 must be classified as context/token overflow (not body-specific)", () => { + // This is the exact predicate combo.ts checks before deciding to abort the + // whole combo instead of falling through to the next target (combo.ts ~L2038-2049): + // if (status === 400 && fallbackResult.shouldFallback && + // !isContextOverflow400(errorText) && !isParamValidation400(errorText) && ...) + // -> "stopping combo" + // + // For the fallback to proceed to the next target, at least one of these must be true. + const isRecognizedAsOverflow = isContextOverflow400(KIMI_ERROR_TEXT) || isParamValidation400(KIMI_ERROR_TEXT); + + assert.equal( + isRecognizedAsOverflow, + true, + `Expected Kimi's "exceeded model token limit" 400 to be classified as context/token ` + + `overflow so combo fallback continues to the next target, but neither ` + + `isContextOverflow400() nor isParamValidation400() matched it. This causes ` + + `handleComboChat's #2101 guard to treat it as a body-specific error and halt the ` + + `whole combo (bug #6637).` + ); +});