fix: reqId ReferenceError in safety-net redirect, dead code, filename typo (#6097)

Fix reqId ReferenceError in safety-net combo redirect + dead-code + DESING→DESIGN rename. Integrated into release/v3.8.44.
This commit is contained in:
Semianchuk Vitalii 2026-07-03 22:56:42 +01:00 committed by GitHub
parent 631c6c4dc3
commit 32e4c906ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 146 additions and 7 deletions

View file

@ -89,12 +89,6 @@ function advancedWindowResetAt(entry: QuotaCacheEntry, now: number): { exhausted
return { exhausted: false };
}
// If we know the window duration, check if the *next* window also passed.
if (entry.windowDurationMs && entry.windowDurationMs > 0) {
const elapsed = now - resetMs;
if (elapsed >= 0) return { exhausted: false };
}
return null;
}

View file

@ -933,7 +933,7 @@ async function handleSingleModelChat(
allCombos: [],
relayOptions: undefined,
signal: request?.signal ?? null,
correlationId: reqId,
correlationId: runtimeOptions?.correlationId ?? null,
});
}

View file

@ -0,0 +1,145 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
// #6097 — Regression guard for the "safety-net combo redirect" ReferenceError.
//
// `handleSingleModelChat` (src/sse/handlers/chat.ts) contains a safety net: when
// `resolveModelOrError` resolves the incoming model to a combo object (e.g. an
// `auto/*` virtual combo), it redirects to `handleComboChat`. The argument object
// it built used `correlationId: reqId`, but `reqId` is only defined in the SIBLING
// `handleChat` scope — NOT inside `handleSingleModelChat`. Whenever that redirect
// actually fired, the handler threw `ReferenceError: reqId is not defined`, which
// surfaced to the client as a 502 `{"error":{"message":"reqId is not defined"}}`.
//
// The redirect is reached in practice when a persisted combo contains an `auto/*`
// member: `handleComboChat` iterates the outer combo's targets and calls
// `handleSingleModelChat("auto/fast", …)`; there `resolveModelOrError` discovers the
// virtual auto-combo and takes the safety-net branch. (The top-level `handleChat`
// combo lookup only sees the OUTER combo, so the auto member is only expanded one
// layer deeper — exactly the "main handler's combo lookup missed it" case the code
// comment describes.)
//
// The fix mirrors the 3 sibling call-sites: `correlationId: runtimeOptions?.correlationId ?? null`.
//
// This test drives the real /v1/chat/completions route with a nested-auto combo and
// asserts the redirect completes (200 + real upstream dispatch) instead of throwing.
// WITHOUT the fix it fails: 502 with the "reqId is not defined" body and zero fetches.
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-chat-safetynet-6097-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const combosDb = await import("../../src/lib/db/combos.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const chatRoute = await import("../../src/app/api/v1/chat/completions/route.ts");
const originalFetch = globalThis.fetch;
async function flushBackgroundWork() {
await new Promise((resolve) => setTimeout(resolve, 50));
await new Promise((resolve) => setImmediate(resolve));
}
async function resetStorage() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
test.beforeEach(async () => {
globalThis.fetch = originalFetch;
await resetStorage();
});
test.afterEach(async () => {
await flushBackgroundWork();
globalThis.fetch = originalFetch;
});
test.after(async () => {
await flushBackgroundWork();
globalThis.fetch = originalFetch;
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test(
"#6097 safety-net combo redirect does not throw ReferenceError: reqId is not defined",
async () => {
// A healthy provider connection so the inner auto/* combo has a candidate to
// dispatch to once the safety-net redirect completes.
await providersDb.createProviderConnection({
provider: "openai",
authType: "apikey",
name: "openai-safetynet-6097",
apiKey: "sk-safetynet-6097",
isActive: true,
testStatus: "active",
});
// A persisted combo whose single member is a virtual `auto/*` combo. The
// top-level handler resolves the OUTER combo; only when handleComboChat calls
// handleSingleModelChat("auto/fast", …) does resolveModelOrError discover the
// auto combo and fire the safety-net redirect.
await combosDb.createCombo({
name: "nested-auto-6097",
strategy: "priority",
models: [{ provider: "auto", model: "fast" }],
});
const fetchCalls: string[] = [];
globalThis.fetch = async (url: any) => {
fetchCalls.push(String(url));
return Response.json({
id: "chatcmpl-safetynet-6097",
choices: [{ message: { role: "assistant", content: "OK" } }],
});
};
const request = new Request("http://localhost/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
// Forces the combo target to be attempted (bypasses availability
// pre-skipping) so the redirect path is exercised deterministically.
"X-Internal-Test": "combo-health-check",
},
body: JSON.stringify({
model: "nested-auto-6097",
messages: [{ role: "user", content: "Reply with OK only." }],
max_tokens: 16,
stream: false,
temperature: 0,
}),
});
const response = await chatRoute.POST(request);
const bodyText = await response.text();
// Primary guard: the exact bug signature must never appear.
assert.ok(
!bodyText.includes("reqId is not defined"),
`safety-net redirect leaked a ReferenceError: ${bodyText.slice(0, 200)}`
);
// The redirect must complete successfully (buggy version returned 502).
assert.equal(
response.status,
200,
`expected 200 after safety-net redirect, got ${response.status}: ${bodyText.slice(0, 200)}`
);
// And it must have proceeded past the redirect into a real upstream dispatch
// (buggy version threw before any fetch → zero calls).
assert.ok(
fetchCalls.length > 0,
"expected the redirected inner combo to reach a real upstream fetch"
);
const body = JSON.parse(bodyText) as any;
assert.equal(body.choices[0].message.content, "OK");
}
);