From 12de25b6f6e2e55e89b22ec99418a50d2b1b71bf Mon Sep 17 00:00:00 2001 From: Chewji <126886556+Chewji9875@users.noreply.github.com> Date: Mon, 29 Jun 2026 11:33:15 +0700 Subject: [PATCH] fix(combo): advance to next model on 400 'model not supported' (#5249) A 400 classified as MODEL_CAPACITY (e.g. 'requested model is not supported') hit the #2101 anti-loop stop-branch and halted the combo instead of advancing. Drop the MODEL_CAPACITY trigger from that stop condition so model-specific 400s fall through to the next combo target; genuinely body-specific 400s (malformed/invalid/bad-request substrings) still stop. Validated: combo-strategies suite 16/16 (incl. the new regression test) green on current release tip, typecheck clean. Stale pre-merge CI was from an older base. Co-authored-by: Chewji9875 --- open-sse/services/combo.ts | 4 +--- tests/unit/combo-strategies.test.ts | 36 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index ec46ffe968..1cfdc5131b 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -17,7 +17,6 @@ import { recordProviderFailure, selectLockoutCooldownMs, } from "./accountFallback.ts"; -import { RateLimitReason } from "../config/constants.ts"; import { errorResponse, unavailableResponse } from "../utils/error.ts"; import { recordComboIntent, @@ -2345,8 +2344,7 @@ export async function handleComboChat({ fallbackResult.shouldFallback && !isContextOverflow400(errorText) && !isParamValidation400(errorText) && - (fallbackResult.reason === RateLimitReason.MODEL_CAPACITY || - errorText.toLowerCase().includes("context") || + (errorText.toLowerCase().includes("context") || errorText.toLowerCase().includes("prompt") || errorText.toLowerCase().includes("token") || errorText.toLowerCase().includes("malformed") || diff --git a/tests/unit/combo-strategies.test.ts b/tests/unit/combo-strategies.test.ts index 173d0cd68e..dc19036722 100644 --- a/tests/unit/combo-strategies.test.ts +++ b/tests/unit/combo-strategies.test.ts @@ -708,3 +708,39 @@ test("reset-aware strategy scores provider-specific weekly windows when availabl assert.equal(await selectedConnectionFor(combo), soon); }); + +test("priority combo advances to next model when first returns 400 'model not supported'", async () => { + const name = `model-not-supported-${randomUUID()}`; + const combo = await combosDb.createCombo({ + name, + strategy: "priority", + models: ["openai/gpt-4", "openai/gpt-3.5-turbo"], + }); + + const calls: string[] = []; + const response = await handleComboChat({ + body: reqBodyTextArray, + combo, + allCombos: [combo], + isModelAvailable: undefined, + relayOptions: undefined, + signal: undefined, + settings: {}, + log: makeLog(), + handleSingleModel: async (_body: unknown, modelStr: string) => { + calls.push(modelStr); + if (modelStr === "openai/gpt-4") { + return Response.json( + { error: { message: "requested model is not supported" } }, + { status: 400 } + ); + } + return okResponse(modelStr); + }, + }); + + assert.equal(response.status, 200, "combo should advance to second model and return 200"); + assert.equal(calls.length, 2, "combo should have tried both models"); + assert.equal(calls[0], "openai/gpt-4", "first model should be tried first"); + assert.equal(calls[1], "openai/gpt-3.5-turbo", "second model should be tried after 400"); +});