mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-13 10:43:43 +03:00
Compare commits
5 Commits
fix/audio-
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca23eed77c | ||
|
|
5f0a394091 | ||
|
|
918fba5e39 | ||
|
|
026e1cadaa | ||
|
|
b090b601a5 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -250,3 +250,8 @@ tests/homolog/.auth/
|
||||
tests/homolog/ui/.auth/
|
||||
homolog-report/
|
||||
docker-compose.yml.bak
|
||||
|
||||
# _tasks e um repo git SEPARADO (ver AGENTS.md). A linha _tasks/ (com barra) NAO
|
||||
# ignora um SYMLINK chamado _tasks; /_tasks (ancorado) cobre arquivo/symlink/dir na raiz
|
||||
# e impede que um git add -A recapture o symlink (incidente 2026-08-08).
|
||||
/_tasks
|
||||
|
||||
115
package-lock.json
generated
115
package-lock.json
generated
@@ -31,7 +31,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"commander": "^15.0.0",
|
||||
"csv-stringify": "^6.7.0",
|
||||
"dompurify": "^3.4.12",
|
||||
"dompurify": "^3.4.13",
|
||||
"express": "^5.2.1",
|
||||
"fetch-socks": "^1.3.3",
|
||||
"fflate": "^0.8.3",
|
||||
@@ -3692,9 +3692,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3711,9 +3708,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3730,9 +3724,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3749,9 +3740,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3768,9 +3756,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3787,9 +3772,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3806,9 +3788,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3825,9 +3804,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3844,9 +3820,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3869,9 +3842,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3894,9 +3864,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3919,9 +3886,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3944,9 +3908,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3969,9 +3930,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3994,9 +3952,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4019,9 +3974,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5393,9 +5345,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5412,9 +5361,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5431,9 +5377,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5450,9 +5393,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10670,9 +10610,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10690,9 +10627,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10710,9 +10644,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10730,9 +10661,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -12779,9 +12707,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -12795,9 +12720,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -12811,9 +12733,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -12827,9 +12746,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -12843,9 +12759,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -12859,9 +12772,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -17064,9 +16974,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.4.12",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz",
|
||||
"integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==",
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz",
|
||||
"integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
@@ -24477,6 +24387,17 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/libxmljs2/node_modules/brace-expansion": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/libxmljs2/node_modules/cacache": {
|
||||
"version": "19.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz",
|
||||
@@ -27576,9 +27497,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.16",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
|
||||
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
|
||||
"version": "3.3.18",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
|
||||
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"commander": "^15.0.0",
|
||||
"csv-stringify": "^6.7.0",
|
||||
"dompurify": "^3.4.12",
|
||||
"dompurify": "^3.4.13",
|
||||
"express": "^5.2.1",
|
||||
"fetch-socks": "^1.3.3",
|
||||
"fflate": "^0.8.3",
|
||||
@@ -395,7 +395,6 @@
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"dompurify": "^3.4.12",
|
||||
"fast-xml-parser": "^5.10.1",
|
||||
"sharp": "^0.35.0",
|
||||
"postcss": "^8.5.18",
|
||||
@@ -454,6 +453,10 @@
|
||||
},
|
||||
"xmlbuilder2": {
|
||||
"js-yaml": "^4.3.1"
|
||||
},
|
||||
"nanoid": "^3.3.17",
|
||||
"monaco-editor": {
|
||||
"dompurify": "^3.4.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,6 +332,18 @@ async function buildUnifiedModelsResponseCore(
|
||||
return collected;
|
||||
};
|
||||
|
||||
// Health-check exclusions (provider_specific_data.excludedModels) are enforced
|
||||
// at request time in getProviderCredentials(); mirror the same rule in the
|
||||
// catalog so ghost models do not appear as available. A model is hidden when
|
||||
// the provider HAS connections but NONE of them is eligible for it.
|
||||
const isExcludedByProviderConnections = (providerKey: string, modelId: string) => {
|
||||
const providerId = aliasToProviderId[providerKey] || providerKey;
|
||||
const alias = providerIdToAlias[providerId] || providerKey;
|
||||
const providerConnections = getConnectionsForProvider(providerId, alias, providerKey);
|
||||
if (providerConnections.length === 0) return false; // noAuth / no DB row: keep
|
||||
return !hasEligibleConnectionForModel(providerConnections, modelId);
|
||||
};
|
||||
|
||||
const providerSupportsModel = (providerKey: string, modelId: string) => {
|
||||
const providerId = aliasToProviderId[providerKey] || providerKey;
|
||||
const alias = providerIdToAlias[providerId] || providerKey;
|
||||
@@ -703,6 +715,7 @@ async function buildUnifiedModelsResponseCore(
|
||||
if (!providerSupportsModel(canonicalProviderId, model.id)) continue;
|
||||
const aliasId = `${alias}/${model.id}`;
|
||||
if (getModelIsHidden(canonicalProviderId, model.id)) continue;
|
||||
if (isExcludedByProviderConnections(canonicalProviderId, model.id)) continue;
|
||||
if (shouldHidePaid(canonicalProviderId, model.id, (model as { pricing?: unknown }).pricing))
|
||||
continue;
|
||||
|
||||
@@ -797,6 +810,7 @@ async function buildUnifiedModelsResponseCore(
|
||||
continue;
|
||||
}
|
||||
if (getModelIsHidden(providerId, sm.id)) continue;
|
||||
if (isExcludedByProviderConnections(canonicalProviderId, sm.id)) continue;
|
||||
// #6457: some upstream discovery catalogs (e.g. HuggingFace's live
|
||||
// `/v1/models`) return image/diffusion models with no modality info,
|
||||
// so `endpoints` below would default to ["chat"] and misrepresent
|
||||
@@ -1173,6 +1187,7 @@ async function buildUnifiedModelsResponseCore(
|
||||
if (!modelId) continue;
|
||||
if (model.isHidden === true) continue;
|
||||
if (getModelIsHidden(canonicalProviderId, modelId)) continue;
|
||||
if (isExcludedByProviderConnections(canonicalProviderId, modelId)) continue;
|
||||
// #6328: apply hidePaidModels to user-defined custom rows too.
|
||||
// Custom entries do not carry pricing, so shouldHidePaid() decides
|
||||
// via FREE_MODEL_IDS_BY_PROVIDER — matches synced/PROVIDER_MODELS.
|
||||
@@ -1305,6 +1320,7 @@ async function buildUnifiedModelsResponseCore(
|
||||
}
|
||||
|
||||
if (getModelIsHidden(canonicalProviderId, modelId)) continue;
|
||||
if (isExcludedByProviderConnections(canonicalProviderId, modelId)) continue;
|
||||
// #6328: apply hidePaidModels to alias-backed rows too. Alias mappings
|
||||
// point at providerKey/modelId with no pricing, so shouldHidePaid()
|
||||
// decides via the FREE_MODEL_IDS_BY_PROVIDER catalog tier.
|
||||
@@ -1377,6 +1393,7 @@ async function buildUnifiedModelsResponseCore(
|
||||
const modelId = typeof model.id === "string" ? model.id : null;
|
||||
if (!modelId) continue;
|
||||
if (getModelIsHidden(canonicalProviderId, modelId)) continue;
|
||||
if (isExcludedByProviderConnections(canonicalProviderId, modelId)) continue;
|
||||
// #6328: apply hidePaidModels to managed-fallback rows too. Compatible
|
||||
// provider fallbacks lack pricing; shouldHidePaid() decides via the
|
||||
// FREE_MODEL_IDS_BY_PROVIDER catalog tier.
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import { getDbInstance } from "./db/core";
|
||||
import { invalidateDbCache } from "./db/readCache";
|
||||
import { backupDbFile } from "./db/backup";
|
||||
import { registerDbStateResetter } from "./db/stateReset";
|
||||
|
||||
import {
|
||||
transformModelsDevToPricing,
|
||||
@@ -193,10 +194,32 @@ function mapCapabilityRecord(record: Record<string, unknown>): ModelCapabilityEn
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Process-local memo for models.dev pricing.
|
||||
*
|
||||
* `resolveCatalogPricing` used to call `getModelsDevPricing()` once per model
|
||||
* while building `/v1/models` (~10k+ times). Each call re-ran the full SQL scan
|
||||
* and `JSON.parse`d every pricing row, pegging the event loop for minutes
|
||||
* (see #9685 / #10052). Memoize until the next save/clear write.
|
||||
*/
|
||||
let modelsDevPricingCache: PricingByProvider | null = null;
|
||||
|
||||
function invalidateModelsDevPricingCache(): void {
|
||||
modelsDevPricingCache = null;
|
||||
}
|
||||
|
||||
// Register cache invalidation with DB state reset system so resetDbInstance() clears the memo.
|
||||
registerDbStateResetter(invalidateModelsDevPricingCache);
|
||||
|
||||
/**
|
||||
* Read synced pricing from `models_dev_pricing` namespace.
|
||||
* Results are memoized until `saveModelsDevPricing` / `clearModelsDevPricing`.
|
||||
*/
|
||||
export function getModelsDevPricing(): PricingByProvider {
|
||||
if (modelsDevPricingCache) {
|
||||
return modelsDevPricingCache;
|
||||
}
|
||||
|
||||
const db = getDbInstance();
|
||||
const rows = db
|
||||
.prepare("SELECT key, value FROM key_value WHERE namespace = 'models_dev_pricing'")
|
||||
@@ -213,6 +236,7 @@ export function getModelsDevPricing(): PricingByProvider {
|
||||
console.warn(`[MODELS_DEV] Corrupted pricing data for provider "${key}", skipping`);
|
||||
}
|
||||
}
|
||||
modelsDevPricingCache = synced;
|
||||
return synced;
|
||||
}
|
||||
|
||||
@@ -233,6 +257,7 @@ export function saveModelsDevPricing(data: PricingByProvider): void {
|
||||
});
|
||||
tx();
|
||||
backupDbFile("pre-write");
|
||||
invalidateModelsDevPricingCache();
|
||||
invalidateDbCache("pricing");
|
||||
}
|
||||
|
||||
@@ -243,6 +268,7 @@ export function clearModelsDevPricing(): void {
|
||||
const db = getDbInstance();
|
||||
db.prepare("DELETE FROM key_value WHERE namespace = 'models_dev_pricing'").run();
|
||||
backupDbFile("pre-write");
|
||||
invalidateModelsDevPricingCache();
|
||||
invalidateDbCache("pricing");
|
||||
}
|
||||
|
||||
|
||||
@@ -260,6 +260,45 @@ test("modelsDev pricing helpers persist records, skip corrupted rows, and clear
|
||||
assert.deepEqual(modelsDev.getModelsDevPricing(), {});
|
||||
});
|
||||
|
||||
test("getModelsDevPricing memoizes until save/clear (#9685)", async () => {
|
||||
const modelsDev = await importFresh("pricing-memo");
|
||||
const pricing = modelsDev.transformModelsDevToPricing(MOCK_MODELS_DEV_DATA);
|
||||
modelsDev.saveModelsDevPricing(pricing);
|
||||
|
||||
const first = modelsDev.getModelsDevPricing();
|
||||
const second = modelsDev.getModelsDevPricing();
|
||||
assert.equal(first, second, "repeated reads must return the same memoized object");
|
||||
|
||||
// Mutating DB under the cache must not be visible until invalidation.
|
||||
const db = core.getDbInstance();
|
||||
db.prepare("DELETE FROM key_value WHERE namespace = 'models_dev_pricing'").run();
|
||||
assert.equal(
|
||||
modelsDev.getModelsDevPricing(),
|
||||
first,
|
||||
"raw SQL without save/clear must not bypass the memo"
|
||||
);
|
||||
|
||||
modelsDev.clearModelsDevPricing();
|
||||
assert.deepEqual(modelsDev.getModelsDevPricing(), {});
|
||||
|
||||
modelsDev.saveModelsDevPricing(pricing);
|
||||
const afterSave = modelsDev.getModelsDevPricing();
|
||||
assert.notEqual(afterSave, first, "save must invalidate the memo");
|
||||
assert.equal(afterSave.openai["gpt-4o"].input, 2.5);
|
||||
|
||||
// Copilot review: DB reset must invalidate the memo so import/restore doesn't serve stale pricing.
|
||||
const beforeReset = modelsDev.getModelsDevPricing();
|
||||
core.resetDbInstance();
|
||||
const afterReset = modelsDev.getModelsDevPricing();
|
||||
assert.notEqual(
|
||||
afterReset,
|
||||
beforeReset,
|
||||
"resetDbInstance must invalidate the memo (Copilot #10055)"
|
||||
);
|
||||
// Data is still on disk after resetDbInstance(), but the cache was cleared and re-read from fresh DB.
|
||||
assert.equal(afterReset.openai["gpt-4o"].input, 2.5, "DB reset re-reads from fresh connection");
|
||||
});
|
||||
|
||||
test("modelsDev capabilities helpers create the table, persist rows, filter by provider/model, and expose context limits", async () => {
|
||||
const modelsDev = await importFresh("capabilities-storage");
|
||||
const capabilities = modelsDev.transformModelsDevToCapabilities(MOCK_MODELS_DEV_DATA);
|
||||
|
||||
Reference in New Issue
Block a user