mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
fix: resolve combo-routing-engine test regression and TS errors
This commit is contained in:
@@ -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
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user