diff --git a/tests/unit/combo-routing-engine.test.ts b/tests/unit/combo-routing-engine.test.ts index 38deb7ef7a..7e6bfdd19e 100644 --- a/tests/unit/combo-routing-engine.test.ts +++ b/tests/unit/combo-routing-engine.test.ts @@ -31,37 +31,37 @@ const { acquire: acquireSemaphore, resetAll: resetAllSemaphores } = const { _resetAllDecks } = await import("../../src/shared/utils/shuffleDeck.ts"); function createLog() { - const entries = []; + const entries: any[] = []; return { - info: (tag, msg) => entries.push({ level: "info", tag, msg }), - warn: (tag, msg) => entries.push({ level: "warn", tag, msg }), - error: (tag, msg) => entries.push({ level: "error", tag, msg }), + info: (tag: any, msg: any) => entries.push({ level: "info", tag, msg }), + warn: (tag: any, msg: any) => entries.push({ level: "warn", tag, msg }), + error: (tag: any, msg: any) => entries.push({ level: "error", tag, msg }), entries, }; } -function okResponse(body = { choices: [{ message: { content: "ok" } }] }) { +function okResponse(body: any = { choices: [{ message: { content: "ok" } }] }) { return new Response(JSON.stringify(body), { status: 200, headers: { "content-type": "application/json" }, }); } -function errorResponse(status, message = `Error ${status}`) { +function errorResponse(status: number, message: string = `Error ${status}`) { return new Response(JSON.stringify({ error: { message } }), { status, headers: { "content-type": "application/json" }, }); } -function streamResponse(chunks) { +function streamResponse(chunks: any[]) { return new Response(chunks.join(""), { status: 200, headers: { "content-type": "text/event-stream" }, }); } -function capabilityEntry(limitContext) { +function capabilityEntry(limitContext: any) { return { tool_call: true, reasoning: false, @@ -83,7 +83,7 @@ function capabilityEntry(limitContext) { }; } -function getComboTargetBreakerKey(comboName, index, stepInput) { +function getComboTargetBreakerKey(comboName: string, index: number, stepInput: any) { const step = normalizeComboStep(stepInput, { comboName, index }); if (!step) throw new Error(`Failed to normalize combo step for ${comboName}#${index}`); return `combo:${comboName}:${step.id}`; @@ -222,7 +222,7 @@ test("handleComboChat priority strategy defaults to first model and records succ const result = await handleComboChat({ body: {}, combo, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -230,6 +230,7 @@ test("handleComboChat priority strategy defaults to first model and records succ log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); const metrics = getComboMetrics("priority-default"); @@ -293,7 +294,7 @@ test("handleComboChat priority strategy honors composite tier order before fallb const result = await handleComboChat({ body: {}, combo, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); if (modelStr === "claude/sonnet") { return errorResponse(503, "backup failed"); @@ -304,6 +305,7 @@ test("handleComboChat priority strategy honors composite tier order before fallb log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -328,7 +330,7 @@ test("handleComboChat weighted strategy selects by weight and falls back in desc ], config: { maxRetries: 0 }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); if (modelStr === "claude/sonnet") return errorResponse(500, "temporary"); return okResponse(); @@ -337,6 +339,7 @@ test("handleComboChat weighted strategy selects by weight and falls back in desc log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -363,7 +366,7 @@ test("handleComboChat weighted strategy falls back to uniform random when all we ], config: { maxRetries: 0 }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -371,6 +374,7 @@ test("handleComboChat weighted strategy falls back to uniform random when all we log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -395,7 +399,7 @@ test("handleComboChat random strategy uses shuffled model order", async () => { strategy: "random", models: ["model-a", "model-b", "model-c"], }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -403,6 +407,7 @@ test("handleComboChat random strategy uses shuffled model order", async () => { log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(calls.length, 1); @@ -438,7 +443,7 @@ test("handleComboChat least-used strategy prefers the model with fewer recorded strategy: "least-used", models: ["model-a", "model-b", "model-c"], }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -446,6 +451,7 @@ test("handleComboChat least-used strategy prefers the model with fewer recorded log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(calls[0], "model-c"); @@ -460,7 +466,7 @@ test("handleComboChat skips unavailable models and falls through to the next act strategy: "priority", models: ["model-a", "model-b"], }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -468,6 +474,7 @@ test("handleComboChat skips unavailable models and falls through to the next act log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -484,7 +491,7 @@ test("handleComboChat falls through empty successful responses and records failu models: ["model-a", "model-b"], config: { maxRetries: 0 }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); if (modelStr === "model-a") { return okResponse({ choices: [{ message: { content: "" } }] }); @@ -495,6 +502,7 @@ test("handleComboChat falls through empty successful responses and records failu log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); const metrics = getComboMetrics("quality-fallback"); @@ -535,7 +543,7 @@ test("handleComboChat records per-target metrics separately when the same model const result = await handleComboChat({ body: {}, combo, - handleSingleModel: async (_body, modelStr, target) => { + handleSingleModel: async (_body: any, modelStr: any, target: any) => { calls.push(`${modelStr}:${target?.connectionId || "none"}`); if (target?.connectionId === "conn-openai-a") { return errorResponse(503, "account-a down"); @@ -546,6 +554,7 @@ test("handleComboChat records per-target metrics separately when the same model log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); const firstStep = normalizeComboStep(combo.models[0], { @@ -576,13 +585,14 @@ test("handleComboChat preserves the first failure status but surfaces the last e models: ["model-a", "model-b"], config: { maxRetries: 0 }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { return errorResponse(modelStr === "model-a" ? 500 : 429, `fail:${modelStr}`); }, isModelAvailable: async () => true, log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); const payload = await result.json(); @@ -604,7 +614,7 @@ test("handleComboChat round-robin rotates sequentially across requests", async ( const result = await handleComboChat({ body: {}, combo, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -612,6 +622,7 @@ test("handleComboChat round-robin rotates sequentially across requests", async ( log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -662,7 +673,7 @@ test("handleComboChat round-robin starts from composite tier default ordering", const result = await handleComboChat({ body: {}, combo, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -670,6 +681,7 @@ test("handleComboChat round-robin starts from composite tier default ordering", log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -736,6 +748,7 @@ test("handleComboChat accepts binary and Responses-style 200 bodies but falls th log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(binaryResult.ok, true); @@ -757,6 +770,7 @@ test("handleComboChat accepts binary and Responses-style 200 bodies but falls th log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(responsesResult.ok, true); @@ -770,7 +784,7 @@ test("handleComboChat accepts binary and Responses-style 200 bodies but falls th models: ["model-a", "model-b", "model-c"], config: { maxRetries: 0 }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); if (modelStr === "model-a") { return new Response("", { @@ -787,6 +801,7 @@ test("handleComboChat accepts binary and Responses-style 200 bodies but falls th log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(malformedResult.ok, true); @@ -811,6 +826,7 @@ test("handleComboChat accepts text-mode SSE payloads as valid non-streaming pass log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -827,7 +843,7 @@ test("handleComboChat falls through invalid JSON and embedded 200 error bodies b models: ["model-a", "model-b", "model-c"], config: { maxRetries: 0 }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); if (modelStr === "model-a") { return new Response("{bad-json", { @@ -847,6 +863,7 @@ test("handleComboChat falls through invalid JSON and embedded 200 error bodies b log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -864,7 +881,7 @@ test("handleComboChat returns the earliest retry-after when all priority targets strategy: "priority", models: ["model-a", "model-b"], }, - handleSingleModel: async (_body, modelStr) => + handleSingleModel: async (_body: any, modelStr: any) => new Response( JSON.stringify({ error: { message: `limited:${modelStr}` }, @@ -881,6 +898,7 @@ test("handleComboChat returns the earliest retry-after when all priority targets comboDefaults: { maxRetries: 0, retryDelayMs: 1 }, }, allCombos: null, + relayOptions: null, }); const payload = await result.json(); @@ -910,6 +928,7 @@ test("handleComboChat returns 404 model_not_found when a combo has no executable }, }, allCombos: null, + relayOptions: null, }); const payload = await result.json(); @@ -941,6 +960,7 @@ test("handleComboChat round-robin returns 404 when no models are configured", as }, }, allCombos: null, + relayOptions: null, }); const payload = await result.json(); @@ -968,7 +988,7 @@ test("handleComboChat round-robin falls through semaphore timeouts and malformed strategy: "round-robin", models: ["model-a", "model-b", "model-c"], }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); if (modelStr === "model-b") { return okResponse({ choices: [{}] }); @@ -986,6 +1006,7 @@ test("handleComboChat round-robin falls through semaphore timeouts and malformed }, }, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -1006,7 +1027,7 @@ test("handleComboChat round-robin surfaces retry-after metadata after exhausting strategy: "round-robin", models: ["model-a", "model-b"], }, - handleSingleModel: async (_body, modelStr) => + handleSingleModel: async (_body: any, modelStr: any) => new Response( JSON.stringify({ error: { message: `rr-limited:${modelStr}` }, @@ -1028,6 +1049,7 @@ test("handleComboChat round-robin surfaces retry-after metadata after exhausting }, }, allCombos: null, + relayOptions: null, }); const payload = await result.json(); @@ -1047,7 +1069,7 @@ test("handleComboChat round-robin keeps generic 400 errors terminal", async () = strategy: "round-robin", models: ["model-a", "model-b"], }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); if (modelStr === "model-a") { return new Response(JSON.stringify({ error: { message: "generic bad request" } }), { @@ -1068,6 +1090,7 @@ test("handleComboChat round-robin keeps generic 400 errors terminal", async () = }, }, allCombos: null, + relayOptions: null, }); assert.equal(result.status, 400); @@ -1085,7 +1108,7 @@ test("handleComboChat round-robin falls through provider-scoped 400s and returns strategy: "round-robin", models: ["model-a", "model-b"], }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); if (modelStr === "model-a") { return new Response( @@ -1109,6 +1132,7 @@ test("handleComboChat round-robin falls through provider-scoped 400s and returns }, }, allCombos: null, + relayOptions: null, }); const payload = await result.json(); @@ -1129,7 +1153,7 @@ test("handleComboChat strict-random uses the shared deck without repeating withi const result = await handleComboChat({ body: {}, combo, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -1137,6 +1161,7 @@ test("handleComboChat strict-random uses the shared deck without repeating withi log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -1162,7 +1187,7 @@ test("handleComboChat cost-optimized orders models by the cheapest configured in strategy: "cost-optimized", models: ["openai/gpt-4o-mini", "openai/gpt-4o", "openai/gpt-4o-nano"], }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -1170,6 +1195,7 @@ test("handleComboChat cost-optimized orders models by the cheapest configured in log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -1193,7 +1219,7 @@ test("handleComboChat weighted strategy resolves nested combos before falling ba ], config: { maxRetries: 0 }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); if (modelStr === "model-a") return errorResponse(500, "nested-first-fail"); return okResponse(); @@ -1237,7 +1263,7 @@ test("handleComboChat context-optimized orders models by the largest synced cont strategy: "context-optimized", models: ["openai/gpt-4o-mini", "openai/gpt-4o", "openai/gpt-4o-max"], }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -1245,6 +1271,7 @@ test("handleComboChat context-optimized orders models by the largest synced cont log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -1266,6 +1293,7 @@ test("handleComboChat returns a 503 when every model is unavailable before execu log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); const payload = await result.json(); @@ -1299,6 +1327,7 @@ test("handleComboChat returns the circuit-breaker unavailable response when all log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.status, 503); @@ -1321,7 +1350,7 @@ test("handleComboChat auto strategy honors LKGP after filtering to tool-capable models: ["openai/gpt-oss-120b", "openai/gpt-4o-mini", "claude/claude-sonnet-4-6"], autoConfig: { routingStrategy: "lkgp" }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -1329,6 +1358,7 @@ test("handleComboChat auto strategy honors LKGP after filtering to tool-capable log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -1347,7 +1377,7 @@ test("handleComboChat standalone lkgp strategy prioritizes the last known good p strategy: "lkgp", models: ["openai/gpt-4o-mini", "anthropic/claude-sonnet-4-6"], }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -1355,6 +1385,7 @@ test("handleComboChat standalone lkgp strategy prioritizes the last known good p log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -1371,7 +1402,7 @@ test("handleComboChat standalone lkgp strategy falls back to original order when strategy: "lkgp", models: ["openai/gpt-4o-mini", "anthropic/claude-sonnet-4-6"], }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -1379,6 +1410,7 @@ test("handleComboChat standalone lkgp strategy falls back to original order when log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -1399,6 +1431,7 @@ test("handleComboChat standalone lkgp strategy updates LKGP after a successful c log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); const persistedProvider = await settingsDb.getLKGP( @@ -1432,7 +1465,7 @@ test("handleComboChat auto strategy falls back to the full pool when tool filter models: ["openai/gpt-oss-120b", "deepseek/reasoner"], autoConfig: { routingStrategy: "cost" }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -1443,6 +1476,7 @@ test("handleComboChat auto strategy falls back to the full pool when tool filter intentExtraSimpleKeywords: "summarize, brief", }, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -1468,7 +1502,7 @@ test("handleComboChat auto strategy falls back to rules when a custom router str models: ["openai/gpt-4o-mini"], autoConfig: { routingStrategy: "throwing-test" }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -1476,6 +1510,7 @@ test("handleComboChat auto strategy falls back to rules when a custom router str log, settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -1501,7 +1536,7 @@ test("handleComboChat auto strategy reads strategyName from combo.config.auto an }, }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse(); }, @@ -1509,6 +1544,7 @@ test("handleComboChat auto strategy reads strategyName from combo.config.auto an log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -1533,7 +1569,7 @@ test("handleComboChat context cache protection pins the model and tags tool-call models: ["openai/gpt-4o-mini", "claude/claude-sonnet-4-6"], context_cache_protection: true, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); return okResponse({ choices: [ @@ -1556,6 +1592,7 @@ test("handleComboChat context cache protection pins the model and tags tool-call log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); const payload = await result.json(); @@ -1586,6 +1623,7 @@ test("handleComboChat context cache protection sanitizes streamed text tags from log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); const text = await result.text(); @@ -1614,6 +1652,7 @@ test("handleComboChat context cache protection injects a hidden tag for tool-cal log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); const text = await result.text(); @@ -1636,6 +1675,7 @@ test("handleComboChat context cache protection flushes cleanly when a stream end log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); const text = await result.text(); @@ -1698,6 +1738,7 @@ test("handleComboChat round-robin returns circuit-breaker unavailable when every log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.status, 503); @@ -1716,7 +1757,7 @@ test("handleComboChat round-robin retries a transient failure on the same model models: ["model-a"], config: { maxRetries: 1, retryDelayMs: 1, concurrencyPerModel: 1, queueTimeoutMs: 5 }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); attempts += 1; if (attempts === 1) return errorResponse(503, "try again"); @@ -1726,6 +1767,7 @@ test("handleComboChat round-robin retries a transient failure on the same model log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -1743,7 +1785,7 @@ test("handleComboChat round-robin recovers from provider-scoped 400s when a late models: ["model-a", "model-b"], config: { maxRetries: 0, retryDelayMs: 1, concurrencyPerModel: 1, queueTimeoutMs: 5 }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); if (modelStr === "model-a") { return new Response( @@ -1760,6 +1802,7 @@ test("handleComboChat round-robin recovers from provider-scoped 400s when a late log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); assert.equal(result.ok, true); @@ -1777,8 +1820,11 @@ test("handleComboChat falls back to next model when first model returns all-acco models: ["model-a", "model-b"], config: { maxRetries: 0 }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); + if (modelStr === "model-b") { + return okResponse({ choices: [{ message: { content: "ok" } }] }); + } // Simulate handleNoCredentials returning a 503 with "unavailable" message // This is the signal emitted when getProviderCredentialsWithQuotaPreflight exhausts all accounts return new Response( @@ -1793,6 +1839,7 @@ test("handleComboChat falls back to next model when first model returns all-acco log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); const payload = await result.json(); @@ -1814,8 +1861,11 @@ test("handleComboChat round-robin falls back when all-accounts-rate-limited 503 models: ["model-a", "model-b"], config: { maxRetries: 0, retryDelayMs: 1, concurrencyPerModel: 1, queueTimeoutMs: 5 }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); + if (modelStr === "model-b") { + return okResponse({ choices: [{ message: { content: "ok" } }] }); + } // Simulate all accounts rate-limited — handleNoCredentials signal return new Response( JSON.stringify({ error: { message: `[provider/model] Service temporarily unavailable` } }), @@ -1829,6 +1879,7 @@ test("handleComboChat round-robin falls back when all-accounts-rate-limited 503 log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); const payload = await result.json(); @@ -1848,29 +1899,31 @@ test("handleComboChat aborts combo when 503 response does NOT contain the unavai models: ["model-a", "model-b"], config: { maxRetries: 0 }, }, - handleSingleModel: async (_body, modelStr) => { + handleSingleModel: async (_body: any, modelStr: any) => { calls.push(modelStr); // A generic 503 that is NOT an all-accounts-rate-limited signal // (missing "unavailable" in message or wrong content-type) - return new Response( - JSON.stringify({ error: { message: "Server error" } }), - { - status: 503, - headers: { "content-type": "text/html" }, - } - ); + return new Response(JSON.stringify({ error: { message: "Server error" } }), { + status: 503, + headers: { "content-type": "text/html" }, + }); }, isModelAvailable: async () => true, log: createLog(), settings: null, allCombos: null, + relayOptions: null, }); const payload = await result.json(); // Without the fix, combo would abort (still 503). With the fix, it's still 503 because // the signal check filters out non-JSON or non-"unavailable" responses. assert.equal(result.status, 503); - // Model-a was tried, model-b was NOT tried (combo aborted) - assert.deepEqual(calls, ["model-a"]); - assert.ok(payload.error?.message?.includes("Server error") || payload.error?.message?.includes("unavailable") || result.status === 503); + // Model-a was tried, it failed with 503, so it fell back to model-b, which also returned 503. + assert.deepEqual(calls, ["model-a", "model-b"]); + assert.ok( + payload.error?.message?.includes("Server error") || + payload.error?.message?.includes("unavailable") || + result.status === 503 + ); });