From 7818494a4b8e400a13c7909b5c80842d0c0f23a6 Mon Sep 17 00:00:00 2001 From: Dizzle <112548150+maxmad64bis@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:35:53 +0200 Subject: [PATCH] fix(health): omit idle models from health matrix by default Idle rows stay counted per account; includeIdleModels=true restores the full list. --- .../16333-health-matrix-omit-idle-models.md | 1 + .../health/ProviderHealthMatrixCard.tsx | 6 +- src/app/api/providers/health-matrix/route.ts | 6 + src/lib/monitoring/providerHealthMatrix.ts | 15 +- tests/unit/provider-health-matrix.test.ts | 227 ++++++++++++++++++ 5 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 changelog.d/fixes/16333-health-matrix-omit-idle-models.md diff --git a/changelog.d/fixes/16333-health-matrix-omit-idle-models.md b/changelog.d/fixes/16333-health-matrix-omit-idle-models.md new file mode 100644 index 000000000000..8dbce692c069 --- /dev/null +++ b/changelog.d/fixes/16333-health-matrix-omit-idle-models.md @@ -0,0 +1 @@ +- **fix(health):** Shrink the provider health matrix payload by omitting idle models by default, with `includeIdleModels=true` to restore the full list ([#16333](https://github.com/diegosouzapw/OmniRoute/pull/16333)) — thanks @maxmad64bis diff --git a/src/app/(dashboard)/dashboard/health/ProviderHealthMatrixCard.tsx b/src/app/(dashboard)/dashboard/health/ProviderHealthMatrixCard.tsx index 58363e23fdce..d36c9b55ef8b 100644 --- a/src/app/(dashboard)/dashboard/health/ProviderHealthMatrixCard.tsx +++ b/src/app/(dashboard)/dashboard/health/ProviderHealthMatrixCard.tsx @@ -40,6 +40,7 @@ type HealthMatrixAccount = { errorCode: string | null; backoffLevel: number; modelCount: number; + omittedIdleModelCount: number; issueCount: number; models: HealthMatrixModel[]; }; @@ -200,7 +201,8 @@ function ModelPill({ model }: { model: HealthMatrixModel }) { function AccountRow({ account }: { account: HealthMatrixAccount }) { const t = useTranslations("health"); const visibleModels = account.models.slice(0, 8); - const hiddenCount = Math.max(0, account.models.length - visibleModels.length); + const omittedIdleModels = account.omittedIdleModelCount ?? 0; + const hiddenCount = omittedIdleModels + Math.max(0, account.models.length - visibleModels.length); const additionalModelsLabel = t.has("additionalModels") ? t("additionalModels", { count: hiddenCount }) : `+${hiddenCount} more models`; @@ -246,7 +248,7 @@ function AccountRow({ account }: { account: HealthMatrixAccount }) { {account.state} - {visibleModels.length > 0 ? ( + {visibleModels.length > 0 || omittedIdleModels > 0 ? (
{visibleModels.map((model) => ( diff --git a/src/app/api/providers/health-matrix/route.ts b/src/app/api/providers/health-matrix/route.ts index 46ac4617af0e..71e5fd9fbc1d 100644 --- a/src/app/api/providers/health-matrix/route.ts +++ b/src/app/api/providers/health-matrix/route.ts @@ -16,6 +16,10 @@ const healthMatrixQuerySchema = z.object({ .enum(["true", "false", "1", "0"]) .nullable() .transform((value) => (value === null ? true : value === "true" || value === "1")), + includeIdleModels: z + .enum(["true", "false", "1", "0"]) + .nullable() + .transform((value) => (value === null ? false : value === "true" || value === "1")), }); export async function GET(request: Request) { @@ -28,6 +32,7 @@ export async function GET(request: Request) { provider: url.searchParams.get("provider"), range: url.searchParams.get("range"), includeHealthy: url.searchParams.get("includeHealthy"), + includeIdleModels: url.searchParams.get("includeIdleModels"), }); if (!parsedQuery.success) { @@ -40,6 +45,7 @@ export async function GET(request: Request) { provider: parsedQuery.data.provider, range: parsedQuery.data.range, includeHealthy: parsedQuery.data.includeHealthy, + includeIdleModels: parsedQuery.data.includeIdleModels, }); return NextResponse.json(report); } catch (error) { diff --git a/src/lib/monitoring/providerHealthMatrix.ts b/src/lib/monitoring/providerHealthMatrix.ts index f9af936417aa..6b29d62707b0 100644 --- a/src/lib/monitoring/providerHealthMatrix.ts +++ b/src/lib/monitoring/providerHealthMatrix.ts @@ -16,6 +16,7 @@ export interface ProviderHealthMatrixOptions { provider?: string | null; range?: ProviderHealthMatrixRange | string | null; includeHealthy?: boolean; + includeIdleModels?: boolean; } export interface ProviderHealthMatrixModel { @@ -47,6 +48,7 @@ export interface ProviderHealthMatrixAccount { errorCode: string | null; backoffLevel: number; modelCount: number; + omittedIdleModelCount: number; issueCount: number; models: ProviderHealthMatrixModel[]; } @@ -487,7 +489,11 @@ export async function buildProviderHealthMatrix( if (model) modelIds.add(model); } - const models = [...modelIds].sort().map((model): ProviderHealthMatrixModel => { + // Idle lines (no traffic in the window and no live lockout) stay out of the + // default payload but remain counted, so large catalogs transfer only + // the models with activity. + const includeIdleModels = options.includeIdleModels === true; + const builtModels = [...modelIds].sort().map((model): ProviderHealthMatrixModel => { const stat = statsByTarget.get(statsKey(provider, connectionId, model)) ?? null; const lockout = lockoutsByTarget.get(modelLockKey(provider, connectionId, model)) ?? null; const isLockedOut = Boolean(lockout); @@ -508,6 +514,10 @@ export async function buildProviderHealthMatrix( lastErrorAt: stat?.lastErrorAt ?? null, }; }); + const models = includeIdleModels + ? builtModels + : builtModels.filter((model) => model.status !== "idle"); + const omittedIdleModelCount = includeIdleModels ? 0 : modelIds.size - models.length; const cooldownUntil = connection ? parseTimeMs(connection.rateLimitedUntil) : null; const cooldownRemainingMs = cooldownUntil ? Math.max(0, cooldownUntil - now) : 0; @@ -529,7 +539,8 @@ export async function buildProviderHealthMatrix( lastErrorType: connection ? toString(connection.lastErrorType) : null, errorCode: connection ? toString(connection.errorCode) : null, backoffLevel: connection ? toNumber(connection.backoffLevel) : 0, - modelCount: models.length, + modelCount: modelIds.size, + omittedIdleModelCount, issueCount, models, }); diff --git a/tests/unit/provider-health-matrix.test.ts b/tests/unit/provider-health-matrix.test.ts index 28dc9dfcfe2c..5f5656465b68 100644 --- a/tests/unit/provider-health-matrix.test.ts +++ b/tests/unit/provider-health-matrix.test.ts @@ -276,6 +276,233 @@ test("provider health matrix route requires management auth", async () => { assert.equal(body.summary.connectionCount, 1); }); +test("provider health matrix omits idle models by default and counts them", async () => { + const connection = (await providersDb.createProviderConnection({ + id: "matrix-idle-connection", + provider: PROVIDER, + authType: "apikey", + name: "matrix-key", + apiKey: "test-key", + isActive: true, + })) as Record; + const connectionId = String(connection.id); + + const syncedInputs = Array.from({ length: 600 }, (_, index) => ({ + id: `matrix-idle-model-${index}`, + name: `Matrix Idle Model ${index}`, + })); + await modelsDb.replaceSyncedAvailableModelsForConnection(PROVIDER, connectionId, syncedInputs); + const startedAt = Date.now(); + for (let index = 0; index < 3; index += 1) { + await callLogs.saveCallLog({ + id: `matrix-idle-log-${index}`, + timestamp: new Date(startedAt + index).toISOString(), + status: 200, + provider: PROVIDER, + connectionId, + model: `matrix-idle-model-${index}`, + duration: 100, + }); + } + + const report = await matrix.buildProviderHealthMatrix({ + provider: PROVIDER, + range: "24h", + includeHealthy: true, + }); + + const account = report.providers[0]?.accounts[0]; + assert.ok(account); + assert.equal(account.models.length, 3); + assert.equal(account.omittedIdleModelCount, 597); + assert.equal(account.modelCount, 600); + assert.ok(account.models.every((candidate) => candidate.status !== "idle")); + assert.equal(report.summary.modelCount, 600); +}); + +test("provider health matrix renders a locked model without traffic", async () => { + const connection = (await providersDb.createProviderConnection({ + id: "matrix-idle-locked-connection", + provider: PROVIDER, + authType: "apikey", + name: "matrix-key", + apiKey: "test-key", + isActive: true, + })) as Record; + const connectionId = String(connection.id); + + await modelsDb.replaceSyncedAvailableModelsForConnection(PROVIDER, connectionId, [ + { id: "matrix-idle-locked-model", name: "Matrix Idle Locked Model" }, + ]); + accountFallback.lockModel( + PROVIDER, + connectionId, + "matrix-idle-locked-model", + "quota_exhausted", + 60_000, + {} + ); + + const report = await matrix.buildProviderHealthMatrix({ + provider: PROVIDER, + range: "24h", + includeHealthy: true, + }); + + const account = report.providers[0]?.accounts[0]; + assert.ok(account); + assert.equal(account.models.length, 1); + assert.equal(account.models[0]?.status, "locked"); + assert.equal(account.omittedIdleModelCount, 0); + assert.equal(account.modelCount, 1); +}); + +test("provider health matrix renders every synced model when idle models are included", async () => { + const connection = (await providersDb.createProviderConnection({ + id: "matrix-idle-full-connection", + provider: PROVIDER, + authType: "apikey", + name: "matrix-key", + apiKey: "test-key", + isActive: true, + })) as Record; + const connectionId = String(connection.id); + + const syncedInputs = Array.from({ length: 600 }, (_, index) => ({ + id: `matrix-idle-full-model-${index}`, + name: `Matrix Idle Full Model ${index}`, + })); + await modelsDb.replaceSyncedAvailableModelsForConnection(PROVIDER, connectionId, syncedInputs); + const startedAt = Date.now(); + for (let index = 0; index < 3; index += 1) { + await callLogs.saveCallLog({ + id: `matrix-idle-full-log-${index}`, + timestamp: new Date(startedAt + index).toISOString(), + status: 200, + provider: PROVIDER, + connectionId, + model: `matrix-idle-full-model-${index}`, + duration: 100, + }); + } + + const report = await matrix.buildProviderHealthMatrix({ + provider: PROVIDER, + range: "24h", + includeHealthy: true, + includeIdleModels: true, + }); + + const account = report.providers[0]?.accounts[0]; + assert.ok(account); + assert.equal(account.models.length, 600); + assert.equal(account.omittedIdleModelCount, 0); + assert.equal(account.modelCount, 600); +}); + +test("provider health matrix route renders every synced model when idle models are included", async () => { + await enableManagementAuth(); + const connection = (await providersDb.createProviderConnection({ + id: "matrix-idle-route-connection", + provider: PROVIDER, + authType: "apikey", + name: "matrix-key", + apiKey: "test-key", + isActive: true, + })) as Record; + const connectionId = String(connection.id); + + await modelsDb.replaceSyncedAvailableModelsForConnection(PROVIDER, connectionId, [ + { id: "matrix-idle-route-model", name: "Matrix Idle Route Model" }, + ]); + + const compact = await route.GET( + await makeManagementSessionRequest("http://localhost/api/providers/health-matrix") + ); + assert.equal(compact.status, 200); + const compactBody = await compact.json(); + const compactAccount = compactBody.providers[0]?.accounts[0]; + assert.equal(compactAccount.models.length, 0); + assert.equal(compactAccount.omittedIdleModelCount, 1); + assert.equal(compactAccount.modelCount, 1); + + const full = await route.GET( + await makeManagementSessionRequest( + "http://localhost/api/providers/health-matrix?includeIdleModels=true" + ) + ); + assert.equal(full.status, 200); + const fullBody = await full.json(); + const fullAccount = fullBody.providers[0]?.accounts[0]; + assert.equal(fullAccount.models.length, 1); + assert.equal(fullAccount.omittedIdleModelCount, 0); + assert.equal(fullAccount.modelCount, 1); +}); + +test("provider health matrix route rejects an invalid idle-models flag", async () => { + await enableManagementAuth(); + + const invalid = await route.GET( + await makeManagementSessionRequest( + "http://localhost/api/providers/health-matrix?includeIdleModels=yes" + ) + ); + assert.equal(invalid.status, 400); +}); + +test("provider health matrix leaves an account without synced models unchanged", async () => { + await providersDb.createProviderConnection({ + id: "matrix-idle-empty-connection", + provider: PROVIDER, + authType: "apikey", + name: "matrix-key", + apiKey: "test-key", + isActive: true, + }); + + const report = await matrix.buildProviderHealthMatrix({ + provider: PROVIDER, + range: "24h", + includeHealthy: true, + }); + + const account = report.providers[0]?.accounts[0]; + assert.ok(account); + assert.equal(account.models.length, 0); + assert.equal(account.modelCount, 0); + assert.equal(account.omittedIdleModelCount, 0); +}); + +test("provider health matrix counts an account with only idle models as omitted", async () => { + const connection = (await providersDb.createProviderConnection({ + id: "matrix-idle-only-connection", + provider: PROVIDER, + authType: "apikey", + name: "matrix-key", + apiKey: "test-key", + isActive: true, + })) as Record; + const connectionId = String(connection.id); + + const syncedInputs = Array.from({ length: 600 }, (_, index) => ({ + id: `matrix-idle-only-model-${index}`, + name: `Matrix Idle Only Model ${index}`, + })); + await modelsDb.replaceSyncedAvailableModelsForConnection(PROVIDER, connectionId, syncedInputs); + + const report = await matrix.buildProviderHealthMatrix({ + provider: PROVIDER, + range: "24h", + includeHealthy: true, + }); + + const account = report.providers[0]?.accounts[0]; + assert.ok(account); + assert.equal(account.models.length, 0); + assert.equal(account.modelCount, 600); + assert.equal(account.omittedIdleModelCount, 600); +}); + test("provider health matrix route rejects invalid query parameters", async () => { await enableManagementAuth();