{t("comboHealthRemainingQuota", {
- value: formatPercent(provider.remainingPct, 1),
+ value: formatPercentOrDash(provider.remainingPct, 1),
})}
diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts
index 2ead593b78..860e9f03f9 100644
--- a/src/app/api/health/route.ts
+++ b/src/app/api/health/route.ts
@@ -14,6 +14,7 @@ import { NextResponse } from "next/server";
* public on an exposed instance, so version, uptime and memory stay behind the authenticated
* `/api/monitoring/health`. For a probe that also confirms the database answers, use
* `/api/health/ping`.
+ * Readiness (DB-backed) lives at /api/health/ping (pingDb, 503 when down); this route stays liveness-only so a slow DB never restarts the container.
*/
export const dynamic = "force-dynamic";
diff --git a/src/app/api/system/version/route.ts b/src/app/api/system/version/route.ts
index f3ec9c26d4..5284d00077 100644
--- a/src/app/api/system/version/route.ts
+++ b/src/app/api/system/version/route.ts
@@ -25,6 +25,7 @@ import {
} from "@/lib/system/versionCheck";
import { resolveGlobalOmniroutePath } from "@/lib/system/globalPackagePath";
import { restartRunningServer } from "@/lib/system/processManagerRestart";
+import { APP_CONFIG } from "@/shared/constants/appConfig";
// #5542 — On Windows npm is `npm.cmd`; Node ≥24 refuses to execFile a `.cmd` without
// a shell (nodejs/node#52554 → "spawn npm ENOENT"). buildNpmExecOptions enables the
// shell on win32 only; SERVICE_VERSION_PATTERN keeps the shell-joined version safe.
@@ -35,11 +36,7 @@ const execFileAsync = promisify(execFile);
export const dynamic = "force-dynamic";
function getCurrentVersion(): string {
- try {
- return require("../../../../../package.json").version as string;
- } catch {
- return "unknown";
- }
+ return APP_CONFIG.version;
}
/**
diff --git a/src/lib/combos/controlCenter.ts b/src/lib/combos/controlCenter.ts
index 825f96942c..d606d2ec93 100644
--- a/src/lib/combos/controlCenter.ts
+++ b/src/lib/combos/controlCenter.ts
@@ -31,10 +31,10 @@ export interface ComboControlCenterHealth {
totalRequests?: number;
};
quotaHealth?: {
- worstRemainingPct?: number;
+ worstRemainingPct?: number | null;
providers?: Array<{
provider: string;
- remainingPct: number;
+ remainingPct: number | null;
isExhausted: boolean;
trend: "improving" | "stable" | "declining";
}>;
diff --git a/src/lib/usage/comboHealth.ts b/src/lib/usage/comboHealth.ts
index a9f12ac1f2..2bb72b5e95 100644
--- a/src/lib/usage/comboHealth.ts
+++ b/src/lib/usage/comboHealth.ts
@@ -32,7 +32,7 @@ type QuotaSnapshotView = {
type ProviderHealth = {
provider: string;
- remainingPct: number;
+ remainingPct: number | null;
isExhausted: boolean;
trend: "improving" | "stable" | "declining";
};
@@ -112,11 +112,12 @@ function calculateGini(values: number[]): number {
return (2 * weightedSum) / (count * sum) - (count + 1) / count;
}
-function buildProviderHealth(provider: string, snapshots: QuotaSnapshotRow[]): ProviderHealth {
+export function buildProviderHealth(provider: string, snapshots: QuotaSnapshotRow[]): ProviderHealth {
if (snapshots.length === 0) {
return {
provider,
- remainingPct: 0,
+ remainingPct: null,
+ // stable: no data yet, not exhausted — null pct, not 0
isExhausted: false,
trend: "stable",
};
@@ -186,7 +187,7 @@ function buildProviderHealth(provider: string, snapshots: QuotaSnapshotRow[]): P
return {
provider,
- remainingPct: roundNumber(lastAverage),
+ remainingPct: lastValues.length === 0 ? null : roundNumber(lastAverage),
isExhausted,
trend,
};
@@ -218,10 +219,10 @@ function buildConnectionHealth(
});
const firstRemaining =
- (firstSnapshot as unknown as QuotaSnapshotView | undefined)?.remainingPercentage ?? 0;
+ (firstSnapshot as unknown as QuotaSnapshotView | undefined)?.remainingPercentage ?? null;
const lastRemaining =
- (lastSnapshot as unknown as QuotaSnapshotView | undefined)?.remainingPercentage ?? 0;
- const delta = lastRemaining - firstRemaining;
+ (lastSnapshot as unknown as QuotaSnapshotView | undefined)?.remainingPercentage ?? null;
+ const delta = (lastRemaining ?? 0) - (firstRemaining ?? 0);
let trend: ProviderHealth["trend"] = "stable";
if (delta >= 5) trend = "improving";
@@ -229,7 +230,7 @@ function buildConnectionHealth(
return {
provider: `${provider}:${connectionId}`,
- remainingPct: roundNumber(lastRemaining),
+ remainingPct: lastRemaining === null ? null : roundNumber(lastRemaining),
isExhausted:
(ordered[ordered.length - 1] as unknown as QuotaSnapshotView | undefined)?.isExhausted === 1,
trend,
@@ -317,22 +318,19 @@ function buildPerformance(comboName: string, since: string): ComboHealthMetrics[
};
}
-function buildQuotaHealth(providers: string[], since: string): ComboHealthMetrics["quotaHealth"] {
+export function buildQuotaHealth(providers: string[], since: string): ComboHealthMetrics["quotaHealth"] {
const providerHealth = providers.map((provider) =>
buildProviderHealth(provider, getQuotaSnapshots({ provider, since }))
);
- const worstRemainingPct =
- providerHealth.length > 0
- ? providerHealth.reduce(
- (lowest, entry) => Math.min(lowest, entry.remainingPct),
- providerHealth[0].remainingPct
- )
- : 0;
+ const nonNull = providerHealth
+ .map((entry) => entry.remainingPct)
+ .filter((v): v is number => typeof v === "number");
+ const worst = nonNull.length > 0 ? Math.min(...nonNull) : null;
return {
providers: providerHealth,
- worstRemainingPct: roundNumber(worstRemainingPct),
+ worstRemainingPct: worst === null ? null : roundNumber(worst),
};
}
diff --git a/src/shared/types/utilization.ts b/src/shared/types/utilization.ts
index 90fb14fce0..ba519ed9fe 100644
--- a/src/shared/types/utilization.ts
+++ b/src/shared/types/utilization.ts
@@ -58,11 +58,11 @@ export interface ComboHealthMetrics {
quotaHealth: {
providers: Array<{
provider: string;
- remainingPct: number;
+ remainingPct: number | null;
isExhausted: boolean;
trend: "improving" | "stable" | "declining";
}>;
- worstRemainingPct: number;
+ worstRemainingPct: number | null;
};
usageSkew: {
modelDistribution: Array<{
diff --git a/tests/unit/api-health-version-source.test.ts b/tests/unit/api-health-version-source.test.ts
new file mode 100644
index 0000000000..24d32d2be6
--- /dev/null
+++ b/tests/unit/api-health-version-source.test.ts
@@ -0,0 +1,24 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import path from "node:path";
+import { GET } from "@/app/api/health/route";
+import { APP_CONFIG } from "@/shared/constants/appConfig";
+
+test("GET /api/health stays minimal: 200, no version anywhere", async () => {
+ const res = await GET();
+ assert.equal(res.status, 200);
+ assert.equal(res.headers.get("ETag"), null);
+ assert.equal(res.headers.get("X-OmniRoute-Version"), null);
+ const body = (await res.json()) as Record