mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-18 21:22:28 +03:00
fix(api): keep bulk hidden-model load inside catalog builder's error boundary
Post-sync-merge fixup for #9147/#10313 against release/v3.8.50: - Resolve the catalog.ts/catalogCache.ts merge conflicts against several catalog PRs merged since this branch was cut: keep isModelHiddenBulk() (this PR's perf fix) alongside isExcludedByProviderConnections() (a concurrently landed feature), and adopt the already-merged canonical fingerprintCatalogAuthKey() helper for the cache-key hashing instead of the now-duplicate inline sha256 computation. - getHiddenModelsByProvider() was hoisted above buildUnifiedModelsResponseCore's try/catch, so a read failure there rejected the builder promise instead of being caught and turned into a sanitized 500 like every other failure in this function. Combined with the pre-existing promise.finally() dangling chain in catalogCache.ts's in-flight coalescing, that produced a genuine unhandled rejection. Move the bulk-load call back inside the try block. - Align tests/unit/models-catalog-route.test.ts and tests/unit/10313-catalog-cache-key-hashing.test.ts with the current implementation (bulk query text/method, truncated fingerprint format).
This commit is contained in:
@@ -241,17 +241,22 @@ async function buildUnifiedModelsResponseCore(
|
||||
await yieldCatalogBuildTurn();
|
||||
}
|
||||
};
|
||||
// #9147: `getModelIsHidden()` is a SQLite read per call (custom row + compat list)
|
||||
// and the build consults it ~16× per entry. Bulk-load the hidden-model map once
|
||||
// (one query — `getHiddenModelsByProvider`) and resolve from memory for the whole
|
||||
// build. A provider absent from the map has no hidden models at all — `false`,
|
||||
// no on-demand fallback (that would reintroduce the per-call SQLite reads).
|
||||
const hiddenModelsByProvider = getHiddenModelsByProvider();
|
||||
const isModelHiddenBulk = (providerId: string, modelId: string): boolean => {
|
||||
const hiddenSet = hiddenModelsByProvider.get(providerId);
|
||||
return hiddenSet ? hiddenSet.has(modelId) : false;
|
||||
};
|
||||
try {
|
||||
// #9147: `getModelIsHidden()` is a SQLite read per call (custom row + compat list)
|
||||
// and the build consults it ~16× per entry. Bulk-load the hidden-model map once
|
||||
// (one query — `getHiddenModelsByProvider`) and resolve from memory for the whole
|
||||
// build. A provider absent from the map has no hidden models at all — `false`,
|
||||
// no on-demand fallback (that would reintroduce the per-call SQLite reads).
|
||||
// Deliberately kept INSIDE this try block (not hoisted above it): the builder's
|
||||
// own catch below is what converts a build-time failure into a sanitized 500
|
||||
// Response instead of a rejected promise — hoisting this bulk read above the
|
||||
// try would let a crash here propagate as an unhandled rejection instead
|
||||
// (catalogCache.ts's in-flight coalescing does not fully consume rejections).
|
||||
const hiddenModelsByProvider = getHiddenModelsByProvider();
|
||||
const isModelHiddenBulk = (providerId: string, modelId: string): boolean => {
|
||||
const hiddenSet = hiddenModelsByProvider.get(providerId);
|
||||
return hiddenSet ? hiddenSet.has(modelId) : false;
|
||||
};
|
||||
let settings: Record<string, any> = {};
|
||||
try {
|
||||
settings = await getSettings();
|
||||
|
||||
@@ -106,17 +106,20 @@ test("cache keys embed the sha256 digest of the secret, never the raw secret (#1
|
||||
const catalogKeys = probe.keys.filter(isCatalogCacheKey);
|
||||
assert.ok(catalogKeys.length > 0, "expected catalog cache Map.set() calls");
|
||||
|
||||
const { createHash } = await import("node:crypto");
|
||||
const digestA = createHash("sha256").update("sk-10313-DIGEST-A").digest("hex");
|
||||
const digestB = createHash("sha256").update("sk-10313-DIGEST-B").digest("hex");
|
||||
// #10538 sync note: buildCatalogCacheKey() delegates to the canonical
|
||||
// fingerprintCatalogAuthKey() (landed on release/v3.8.50 independently of #10313),
|
||||
// which truncates the sha256 hex digest to 16 chars for a shorter Map key. Derive
|
||||
// the expected fingerprint the same way rather than re-hardcoding the full digest.
|
||||
const digestA = catalogCacheMod.fingerprintCatalogAuthKey("sk-10313-DIGEST-A");
|
||||
const digestB = catalogCacheMod.fingerprintCatalogAuthKey("sk-10313-DIGEST-B");
|
||||
const rawA = "sk-10313-DIGEST-A";
|
||||
const rawB = "sk-10313-DIGEST-B";
|
||||
|
||||
// The hashed fingerprint, not the raw secret, rides in the cache keys.
|
||||
const keysWithDigestA = catalogKeys.filter((k) => k.includes(digestA));
|
||||
const keysWithDigestB = catalogKeys.filter((k) => k.includes(digestB));
|
||||
assert.ok(keysWithDigestA.length > 0, `expected a cache key embedding sha256 of A: ${catalogKeys.join(",")}`);
|
||||
assert.ok(keysWithDigestB.length > 0, `expected a cache key embedding sha256 of B: ${catalogKeys.join(",")}`);
|
||||
assert.ok(keysWithDigestA.length > 0, `expected a cache key embedding the fingerprint of A: ${catalogKeys.join(",")}`);
|
||||
assert.ok(keysWithDigestB.length > 0, `expected a cache key embedding the fingerprint of B: ${catalogKeys.join(",")}`);
|
||||
|
||||
// Raw secrets must never appear (issue #10313 root cause).
|
||||
assert.ok(!catalogKeys.some((k) => k.includes(rawA) || k.includes(rawB)));
|
||||
|
||||
@@ -1340,18 +1340,24 @@ test("v1 models catalog returns 500 when model compatibility lookup crashes", as
|
||||
|
||||
db.prepare = (sql) => {
|
||||
const statement = originalPrepare(sql);
|
||||
if (String(sql) !== "SELECT value FROM key_value WHERE namespace = ? AND key = ?") {
|
||||
// #9147: the catalog builder now resolves hidden models via a single bulk
|
||||
// read (`getHiddenModelsByProvider()`, src/lib/db/models.ts) instead of the
|
||||
// old per-provider `SELECT value FROM key_value WHERE namespace = ? AND
|
||||
// key = ?` / readCompatList() lookup — intercept the bulk query's `.all()`
|
||||
// call so this test still exercises "DB read for model visibility crashes
|
||||
// -> catalog endpoint surfaces 500" against the current implementation.
|
||||
if (
|
||||
String(sql) !==
|
||||
"SELECT namespace, key, value FROM key_value WHERE namespace IN ('modelCompatOverrides', 'customModels')"
|
||||
) {
|
||||
return statement;
|
||||
}
|
||||
|
||||
return new Proxy(statement, {
|
||||
get(target, prop, receiver) {
|
||||
if (prop === "get") {
|
||||
if (prop === "all") {
|
||||
return (...args) => {
|
||||
if (args[0] === "modelCompatOverrides") {
|
||||
throw new Error("compat lookup boom");
|
||||
}
|
||||
return target.get(...args);
|
||||
throw new Error("compat lookup boom");
|
||||
};
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
|
||||
Reference in New Issue
Block a user