Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type HealthMatrixAccount = {
errorCode: string | null;
backoffLevel: number;
modelCount: number;
omittedIdleModelCount: number;
issueCount: number;
models: HealthMatrixModel[];
};
Expand Down Expand Up @@ -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`;
Expand Down Expand Up @@ -246,7 +248,7 @@ function AccountRow({ account }: { account: HealthMatrixAccount }) {
{account.state}
</Badge>
</div>
{visibleModels.length > 0 ? (
{visibleModels.length > 0 || omittedIdleModels > 0 ? (
<div className="mt-3 grid gap-2 lg:grid-cols-2">
{visibleModels.map((model) => (
<ModelPill key={`${account.connectionId || "none"}-${model.model}`} model={model} />
Expand Down
6 changes: 6 additions & 0 deletions src/app/api/providers/health-matrix/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down
15 changes: 13 additions & 2 deletions src/lib/monitoring/providerHealthMatrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface ProviderHealthMatrixOptions {
provider?: string | null;
range?: ProviderHealthMatrixRange | string | null;
includeHealthy?: boolean;
includeIdleModels?: boolean;
}

export interface ProviderHealthMatrixModel {
Expand Down Expand Up @@ -47,6 +48,7 @@ export interface ProviderHealthMatrixAccount {
errorCode: string | null;
backoffLevel: number;
modelCount: number;
omittedIdleModelCount: number;
issueCount: number;
models: ProviderHealthMatrixModel[];
}
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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,
});
Expand Down
227 changes: 227 additions & 0 deletions tests/unit/provider-health-matrix.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>;
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<string, unknown>;
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<string, unknown>;
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<string, unknown>;
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<string, unknown>;
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();

Expand Down
Loading