fix: resolve combo-routing-engine test regression and TS errors

This commit is contained in:
diegosouzapw
2026-04-18 16:53:26 -03:00
parent b909013058
commit 860d596c3b

View File

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