- {row.limitLabel}
+
+ {row.limitLabel}
+ {incomplete && (
+
+ {t("pws.capacity.windowPartial")}
+
+ )}
+
{resetText}
diff --git a/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx b/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx
index b4d188da50..b2741a851f 100644
--- a/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx
+++ b/gui/src/components/provider-workspace/ProviderOverviewDashboard.tsx
@@ -7,7 +7,12 @@ import { useMemo } from "react";
import { useT, useI18n } from "../../i18n/shared";
import { IconAlert, IconChevron } from "../../icons";
import type { WorkspaceSections, WorkspaceItem } from "../../provider-workspace/catalog";
-import { accountQuotaFromReport, type ProviderQuotaReportView } from "../../provider-workspace/report";
+import {
+ accountQuotaFromReport,
+ capacityAggregationFromReport,
+ type CapacityWindowView,
+ type ProviderQuotaReportView,
+} from "../../provider-workspace/report";
import {
attentionReasonKey,
buildAttentionItems,
@@ -17,7 +22,7 @@ import {
relativeTimeLabelsFromT,
type ProviderUsageTotals,
} from "../../provider-workspace/usage";
-import { maxQuotaUtilisation } from "../QuotaBars";
+import { maxQuotaUtilisation, type QuotaWindowKey } from "../QuotaBars";
import { ProviderIcon } from "./ProviderRail";
import { formatProviderDisplayName } from "../../provider-icons";
import QuotaBars from "../QuotaBars";
@@ -64,8 +69,9 @@ export default function ProviderOverviewDashboard({
for (const item of allItems) {
const report = quotaReports[item.name];
const quota = report ? accountQuotaFromReport(report) : null;
- if (report && quota) {
- result.push({ item, report, urgency: maxQuotaUtilisation(quota) });
+ const aggregation = report ? capacityAggregationFromReport(report) : null;
+ if (report && (quota || aggregation?.presentation === "coverage-only")) {
+ result.push({ item, report, urgency: quota ? maxQuotaUtilisation(quota) : -1 });
}
}
return result.sort((a, b) => b.urgency - a.urgency || a.item.name.localeCompare(b.item.name));
@@ -162,13 +168,7 @@ export default function ProviderOverviewDashboard({
))}
@@ -236,6 +236,86 @@ export default function ProviderOverviewDashboard({
);
}
+function ProviderCapacityQuota({ report, pending }: { report: ProviderQuotaReportView; pending: boolean }) {
+ const t = useT();
+ const { locale } = useI18n();
+ const aggregation = capacityAggregationFromReport(report);
+ const primaryQuota = accountQuotaFromReport(report);
+ const showsAggregate = aggregation?.presentation === "aggregate";
+ const incompleteWindowKeys = new Set
();
+ const incompleteCustomWindowLabels = new Set();
+ if (showsAggregate && aggregation) {
+ if (aggregation.fiveHour?.incomplete) incompleteWindowKeys.add("fiveHour");
+ if (aggregation.weekly?.incomplete) incompleteWindowKeys.add("weekly");
+ if (aggregation.monthly?.incomplete) incompleteWindowKeys.add("monthly");
+ for (const window of aggregation.customWindows ?? []) {
+ if (window.incomplete) incompleteCustomWindowLabels.add(window.label);
+ }
+ }
+ const recoveryRows: Array<{ key: number; label: string; window: CapacityWindowView }> = showsAggregate && aggregation ? [
+ ...(aggregation.fiveHour ? [{ key: 0, label: t("codexAuth.fiveHour"), window: aggregation.fiveHour }] : []),
+ ...(aggregation.weekly ? [{ key: 1, label: t("codexAuth.weekly"), window: aggregation.weekly }] : []),
+ ...(aggregation.monthly ? [{ key: 2, label: t("codexAuth.monthly"), window: aggregation.monthly }] : []),
+ ...(aggregation.customWindows ?? []).map((window, index) => ({ key: index + 3, label: window.label, window })),
+ ] : [];
+ const formatPercent = (value: number) => new Intl.NumberFormat(locale, { maximumFractionDigits: 1 }).format(value);
+ const formatRecoveryAt = (value: number) => new Intl.DateTimeFormat(locale, {
+ dateStyle: "medium",
+ timeStyle: "short",
+ }).format(new Date(value > 10_000_000_000 ? value : value * 1000));
+
+ return (
+ <>
+ {showsAggregate && {t("pws.capacity.estimate")}
}
+ {(primaryQuota || pending) && (
+
+ )}
+ {aggregation && (
+
+ {recoveryRows.flatMap(({ key, label, window }) => (
+ window.nextRecoveryAt !== undefined && window.nextRecoveryPercent !== undefined
+ ? [
+ {t("pws.capacity.nextRecovery")} · {label} · {formatRecoveryAt(window.nextRecoveryAt)}
+ {t("pws.capacity.recoveryShare", { percent: formatPercent(window.nextRecoveryPercent) })}
+
]
+ : []
+ ))}
+ {showsAggregate && aggregation.currentAccount?.quota && (
+
+
+ {t("pws.capacity.currentAccount")}
+ {aggregation.currentAccount.plan ? ` · ${aggregation.currentAccount.plan}` : ""}
+
+
+
+ )}
+ {aggregation.incomplete && aggregation.excludedAccounts > 0 && (
+
+ {t("pws.capacity.incomplete", {
+ excluded: aggregation.excludedAccounts,
+ unknown: aggregation.unknownPlanAccounts,
+ })}
+
+ )}
+ {aggregation.partialWindowAccounts > 0 && (
+
+ {t("pws.capacity.partial", { count: aggregation.partialWindowAccounts })}
+
+ )}
+
+ )}
+ >
+ );
+}
+
function SummaryCard({ count, label, tone }: { count: number; label: string; tone: "ok" | "warn" | "muted" }) {
return (
diff --git a/gui/src/components/provider-workspace/ProviderWorkspaceShell.tsx b/gui/src/components/provider-workspace/ProviderWorkspaceShell.tsx
index b771ca7208..f762e6e2c3 100644
--- a/gui/src/components/provider-workspace/ProviderWorkspaceShell.tsx
+++ b/gui/src/components/provider-workspace/ProviderWorkspaceShell.tsx
@@ -53,6 +53,51 @@ const SORT_DEFS: { id: ProviderSortMode; labelKey: "pws.sort.az" | "pws.sort.za"
{ id: "accounts-first", labelKey: "pws.sort.accountsFirst" },
];
+const QUOTA_REPORT_MAX_AGE_MS = 30 * 60_000;
+
+function freshQuotaReport(value: unknown, now: number): ProviderQuotaReportView | null {
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
+ const row = value as Record
;
+ if (typeof row.updatedAt !== "number" || !Number.isFinite(row.updatedAt)) return null;
+ if (now - row.updatedAt >= QUOTA_REPORT_MAX_AGE_MS) return null;
+ if (!("quota" in row)) return null;
+ if (row.label !== undefined && typeof row.label !== "string") return null;
+ if (row.source !== undefined && typeof row.source !== "string") return null;
+ return {
+ ...(typeof row.label === "string" ? { label: row.label } : {}),
+ ...(typeof row.source === "string" ? { source: row.source } : {}),
+ updatedAt: row.updatedAt,
+ quota: row.quota,
+ ...(row.aggregation !== undefined ? { aggregation: row.aggregation } : {}),
+ };
+}
+
+function freshQuotaReportRecord(value: unknown, now = Date.now()): Record | null {
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
+ const out: Record = {};
+ for (const [provider, raw] of Object.entries(value)) {
+ const report = freshQuotaReport(raw, now);
+ if (provider.trim() && report) out[provider] = report;
+ }
+ return out;
+}
+
+function readFreshQuotaReportCache(key: string): Record | null {
+ return freshQuotaReportRecord(readSessionListCache(key));
+}
+
+function freshQuotaReportsFromResponse(value: unknown, now = Date.now()): Record {
+ if (!Array.isArray(value)) return {};
+ const out: Record = {};
+ for (const raw of value) {
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
+ const provider = (raw as Record).provider;
+ const report = freshQuotaReport(raw, now);
+ if (typeof provider === "string" && provider.trim() && report) out[provider] = report;
+ }
+ return out;
+}
+
export default function ProviderWorkspaceShell({
providers,
apiBase,
@@ -119,10 +164,13 @@ export default function ProviderWorkspaceShell({
readSessionListCache<{ models: Record }>(usageCacheKey)?.models ?? {}
));
const [quotaReports, setQuotaReports] = useState>(() => (
- readSessionListCache>(quotasCacheKey) ?? {}
+ readFreshQuotaReportCache(quotasCacheKey) ?? {}
));
const [usageLoading, setUsageLoading] = useState(() => !readSessionListCache(usageCacheKey));
- const [quotasLoading, setQuotasLoading] = useState(() => !readSessionListCache(quotasCacheKey));
+ const [quotasLoading, setQuotasLoading] = useState(() => {
+ const cached = readFreshQuotaReportCache(quotasCacheKey);
+ return !cached || Object.keys(cached).length === 0;
+ });
const [modelsLoadEpoch, setModelsLoadEpoch] = useState(0);
const filterWrapRef = useRef(null);
@@ -213,31 +261,29 @@ export default function ProviderWorkspaceShell({
useEffect(() => {
let cancelled = false;
const timeout = window.setTimeout(() => {
- if (!readSessionListCache(quotasCacheKey)) setQuotasLoading(true);
+ const cached = readFreshQuotaReportCache(quotasCacheKey);
+ if (!cached || Object.keys(cached).length === 0) setQuotasLoading(true);
// A forced bump means a mutation just changed the answer, so the server's TTL has to
// be bypassed. The old derived-key effect always read the cached view, which is why a
// switch could leave the bars showing the previous account's quota.
void fetch(`${apiBase}/api/provider-quotas${quotaForceRefresh ? "?refresh=1" : ""}`)
- .then(r => readJsonIfOk<{ reports?: Array<{ provider: string; label?: string; source?: string; updatedAt?: number; quota?: unknown }> }>(r))
+ .then(r => readJsonIfOk<{ reports?: Array<{ provider: string; label?: string; source?: string; updatedAt?: number; quota?: unknown; aggregation?: unknown }> }>(r))
.then((data) => {
if (cancelled || !data) return;
- // Merge so a partial/failed probe cannot wipe a previously good provider row.
+ // A successful endpoint response is authoritative, including an empty report list.
+ const next = freshQuotaReportsFromResponse(data.reports);
+ setQuotaReports(next);
+ writeSessionListCache(quotasCacheKey, next);
+ })
+ .catch(() => {
+ if (cancelled) return;
+ // Keep last-good only inside the same server freshness bound.
setQuotaReports(prev => {
- const next = { ...prev };
- for (const report of data.reports ?? []) {
- if (!report?.provider) continue;
- next[report.provider] = {
- label: report.label,
- source: report.source,
- updatedAt: typeof report.updatedAt === "number" ? report.updatedAt : Date.now(),
- quota: report.quota,
- };
- }
+ const next = freshQuotaReportRecord(prev) ?? {};
writeSessionListCache(quotasCacheKey, next);
return next;
});
})
- .catch(() => { /* keep last-good */ })
.finally(() => { if (!cancelled) setQuotasLoading(false); });
}, 0);
return () => {
diff --git a/gui/src/i18n/de.ts b/gui/src/i18n/de.ts
index fab3d30674..f9f68142e0 100644
--- a/gui/src/i18n/de.ts
+++ b/gui/src/i18n/de.ts
@@ -1502,6 +1502,14 @@ export const de: Record = {
"pws.dashboard.title": "Anbieterübersicht",
"pws.dashboard.subtitle": "Verwalten Sie alle Ihre Modellanbieter an einem Ort.",
"pws.dashboard.rateLimits": "RATE LIMITS",
+ "pws.capacity.estimate": "Pool-Schätzung anhand konfigurierter Gewichtungen",
+ "pws.capacity.currentAccount": "Aktuelles effektives Konto",
+ "pws.capacity.nextRecovery": "Nächste Kapazitätswiederherstellung",
+ "pws.capacity.recoveryShare": "+{percent} % Pool-Kapazität",
+ "pws.capacity.incomplete": "Unvollständige Abdeckung: {excluded} Konten ausgeschlossen, davon {unknown} mit unbekanntem Tarif",
+ "pws.capacity.partial": "Teilweise Fensterabdeckung: {count} Konten melden nicht jedes angezeigte Limitfenster",
+ "pws.capacity.windowPartial": "Teilweise",
+ "pws.capacity.windowPartialA11y": "{window}: unvollständige Kontoabdeckung",
"pws.dashboard.recentlyUsed": "KÜRZLICH VERWENDET",
"pws.dashboard.requests": "{count} Anfragen",
"pws.dashboard.checkedAgo": "Geprüft {time}",
diff --git a/gui/src/i18n/en.ts b/gui/src/i18n/en.ts
index 443b7bdbea..e2a0ba0dc9 100644
--- a/gui/src/i18n/en.ts
+++ b/gui/src/i18n/en.ts
@@ -1054,6 +1054,14 @@ export const en = {
"pws.dashboard.title": "Providers overview",
"pws.dashboard.subtitle": "Manage all your model providers in one place.",
"pws.dashboard.rateLimits": "RATE LIMITS",
+ "pws.capacity.estimate": "Configured-weight pool estimate",
+ "pws.capacity.currentAccount": "Current effective account",
+ "pws.capacity.nextRecovery": "Next capacity recovery",
+ "pws.capacity.recoveryShare": "+{percent}% pool capacity",
+ "pws.capacity.incomplete": "Incomplete coverage: {excluded} account(s) excluded, including {unknown} unknown plan(s)",
+ "pws.capacity.partial": "Partial window coverage: {count} account(s) do not report every displayed limit window",
+ "pws.capacity.windowPartial": "Partial",
+ "pws.capacity.windowPartialA11y": "{window}: incomplete account coverage",
"pws.dashboard.recentlyUsed": "RECENTLY USED",
"pws.dashboard.requests": "{count} requests",
"pws.dashboard.checkedAgo": "Checked {time}",
diff --git a/gui/src/i18n/ja.ts b/gui/src/i18n/ja.ts
index dbf10f1f47..6a94cb4145 100644
--- a/gui/src/i18n/ja.ts
+++ b/gui/src/i18n/ja.ts
@@ -1004,6 +1004,14 @@ export const ja: Record = {
"pws.dashboard.title": "プロバイダー概要",
"pws.dashboard.subtitle": "すべてのモデルプロバイダーを一か所で管理します。",
"pws.dashboard.rateLimits": "レート制限",
+ "pws.capacity.estimate": "設定済み重みによるプール推定",
+ "pws.capacity.currentAccount": "現在の有効アカウント",
+ "pws.capacity.nextRecovery": "次の容量回復",
+ "pws.capacity.recoveryShare": "+{percent}% のプール容量",
+ "pws.capacity.incomplete": "対象範囲が不完全です: {excluded} 件を除外(不明なプラン {unknown} 件)",
+ "pws.capacity.partial": "一部の期間の対象範囲が不完全です: {count} 件のアカウントでは表示中のすべての制限期間を取得できません",
+ "pws.capacity.windowPartial": "一部のみ",
+ "pws.capacity.windowPartialA11y": "{window}: アカウントの対象範囲が不完全です",
"pws.dashboard.recentlyUsed": "最近の使用",
"pws.dashboard.requests": "{count} リクエスト",
"pws.dashboard.checkedAgo": "{time} に確認",
diff --git a/gui/src/i18n/ko.ts b/gui/src/i18n/ko.ts
index 9e54984f96..276b648f40 100644
--- a/gui/src/i18n/ko.ts
+++ b/gui/src/i18n/ko.ts
@@ -1529,6 +1529,14 @@ export const ko: Record = {
"pws.dashboard.title": "프로바이더 개요",
"pws.dashboard.subtitle": "모든 모델 프로바이더를 한곳에서 관리합니다.",
"pws.dashboard.rateLimits": "사용량 제한",
+ "pws.capacity.estimate": "설정 가중치 기반 풀 추정치",
+ "pws.capacity.currentAccount": "현재 유효 계정",
+ "pws.capacity.nextRecovery": "다음 용량 회복",
+ "pws.capacity.recoveryShare": "+{percent}% 풀 용량",
+ "pws.capacity.incomplete": "불완전한 범위: {excluded}개 계정 제외, 알 수 없는 요금제 {unknown}개 포함",
+ "pws.capacity.partial": "일부 기간의 범위가 불완전합니다: {count}개 계정에서 표시된 모든 한도 기간을 확인할 수 없습니다",
+ "pws.capacity.windowPartial": "일부만",
+ "pws.capacity.windowPartialA11y": "{window}: 계정 범위가 불완전합니다",
"pws.dashboard.recentlyUsed": "최근 사용",
"pws.dashboard.requests": "{count}건 요청",
"pws.dashboard.checkedAgo": "{time} 전 확인",
diff --git a/gui/src/i18n/ru.ts b/gui/src/i18n/ru.ts
index 196945593f..740a607ec9 100644
--- a/gui/src/i18n/ru.ts
+++ b/gui/src/i18n/ru.ts
@@ -1046,6 +1046,14 @@ export const ru: Record = {
"pws.dashboard.title": "Обзор провайдеров",
"pws.dashboard.subtitle": "Управляйте всеми провайдерами моделей в одном месте.",
"pws.dashboard.rateLimits": "Лимиты запросов",
+ "pws.capacity.estimate": "Оценка пула по настроенным весам",
+ "pws.capacity.currentAccount": "Текущая активная учётная запись",
+ "pws.capacity.nextRecovery": "Следующее восстановление ёмкости",
+ "pws.capacity.recoveryShare": "+{percent}% ёмкости пула",
+ "pws.capacity.incomplete": "Неполное покрытие: исключено аккаунтов: {excluded}, в том числе с неизвестным планом: {unknown}",
+ "pws.capacity.partial": "Частичное покрытие окон: для {count} аккаунтов доступны не все показанные окна лимитов",
+ "pws.capacity.windowPartial": "Частично",
+ "pws.capacity.windowPartialA11y": "{window}: неполное покрытие аккаунтов",
"pws.dashboard.recentlyUsed": "Недавно использованные",
"pws.dashboard.requests": "{count} запросов",
"pws.dashboard.checkedAgo": "Проверено {time}",
diff --git a/gui/src/i18n/zh.ts b/gui/src/i18n/zh.ts
index 909141d840..b514b02fce 100644
--- a/gui/src/i18n/zh.ts
+++ b/gui/src/i18n/zh.ts
@@ -1522,6 +1522,14 @@ export const zh: Record = {
"pws.dashboard.title": "提供商概览",
"pws.dashboard.subtitle": "在一个地方管理所有模型提供商。",
"pws.dashboard.rateLimits": "速率限制",
+ "pws.capacity.estimate": "按配置权重估算的账户池",
+ "pws.capacity.currentAccount": "当前有效账户",
+ "pws.capacity.nextRecovery": "下一次容量恢复",
+ "pws.capacity.recoveryShare": "+{percent}% 账户池容量",
+ "pws.capacity.incomplete": "覆盖不完整:已排除 {excluded} 个账户,其中 {unknown} 个套餐未知",
+ "pws.capacity.partial": "部分窗口覆盖不完整:{count} 个账户未报告所有显示的限额窗口",
+ "pws.capacity.windowPartial": "部分",
+ "pws.capacity.windowPartialA11y": "{window}:账户覆盖不完整",
"pws.dashboard.recentlyUsed": "最近使用",
"pws.dashboard.requests": "{count} 个请求",
"pws.dashboard.checkedAgo": "{time} 前检查",
diff --git a/gui/src/provider-workspace/report.ts b/gui/src/provider-workspace/report.ts
index c38e982ad9..0907c2be81 100644
--- a/gui/src/provider-workspace/report.ts
+++ b/gui/src/provider-workspace/report.ts
@@ -11,41 +11,132 @@ export interface ProviderQuotaReportView {
source?: string;
updatedAt?: number;
quota?: unknown;
+ aggregation?: unknown;
}
-/** Narrow an unknown quota payload into the AccountQuota display shape (null when unusable). */
-export function accountQuotaFromReport(report?: ProviderQuotaReportView): AccountQuota | null {
- const quota = report?.quota;
+export interface CapacityWindowView {
+ usedPercent: number;
+ incomplete?: boolean;
+ excludedAccounts?: number;
+ nextRecoveryAt?: number;
+ nextRecoveryPercent?: number;
+}
+
+export interface ProviderCapacityAggregationView {
+ presentation: "aggregate" | "effective-account-fallback" | "coverage-only";
+ incomplete: boolean;
+ excludedAccounts: number;
+ unknownPlanAccounts: number;
+ partialWindowAccounts: number;
+ fiveHour?: CapacityWindowView;
+ weekly?: CapacityWindowView;
+ monthly?: CapacityWindowView;
+ customWindows?: Array;
+ currentAccount?: { plan?: string | null; quota: AccountQuota | null };
+}
+
+const finite = (value: unknown): number | undefined => (
+ typeof value === "number" && Number.isFinite(value) ? value : undefined
+);
+
+function quotaFromUnknown(quota: unknown, fallbackUpdatedAt?: number): AccountQuota | null {
if (!quota || typeof quota !== "object" || Array.isArray(quota)) return null;
const q = quota as Record;
- const num = (v: unknown): number | undefined => (typeof v === "number" && Number.isFinite(v) ? v : undefined);
const windows = Array.isArray(q.customWindows)
? (q.customWindows as unknown[]).flatMap(w => {
if (!w || typeof w !== "object") return [];
const row = w as Record;
- if (typeof row.label !== "string" || num(row.percent) === undefined) return [];
+ if (typeof row.label !== "string" || finite(row.percent) === undefined) return [];
return [{
label: row.label,
percent: row.percent as number,
- ...(num(row.resetAt) !== undefined ? { resetAt: row.resetAt as number } : {}),
+ ...(finite(row.resetAt) !== undefined ? { resetAt: row.resetAt as number } : {}),
}];
})
: [];
const out: AccountQuota = {
- ...(num(q.fiveHourPercent) !== undefined ? { fiveHourPercent: q.fiveHourPercent as number } : {}),
- ...(num(q.fiveHourResetAt) !== undefined ? { fiveHourResetAt: q.fiveHourResetAt as number } : {}),
- ...(num(q.weeklyPercent) !== undefined ? { weeklyPercent: q.weeklyPercent as number } : {}),
- ...(num(q.weeklyResetAt) !== undefined ? { weeklyResetAt: q.weeklyResetAt as number } : {}),
- ...(num(q.monthlyPercent) !== undefined ? { monthlyPercent: q.monthlyPercent as number } : {}),
- ...(num(q.monthlyResetAt) !== undefined ? { monthlyResetAt: q.monthlyResetAt as number } : {}),
+ ...(finite(q.fiveHourPercent) !== undefined ? { fiveHourPercent: q.fiveHourPercent as number } : {}),
+ ...(finite(q.fiveHourResetAt) !== undefined ? { fiveHourResetAt: q.fiveHourResetAt as number } : {}),
+ ...(finite(q.weeklyPercent) !== undefined ? { weeklyPercent: q.weeklyPercent as number } : {}),
+ ...(finite(q.weeklyResetAt) !== undefined ? { weeklyResetAt: q.weeklyResetAt as number } : {}),
+ ...(finite(q.monthlyPercent) !== undefined ? { monthlyPercent: q.monthlyPercent as number } : {}),
+ ...(finite(q.monthlyResetAt) !== undefined ? { monthlyResetAt: q.monthlyResetAt as number } : {}),
...(windows.length > 0 ? { customWindows: windows } : {}),
- updatedAt: num(q.updatedAt) ?? report?.updatedAt ?? Date.now(),
+ updatedAt: finite(q.updatedAt) ?? fallbackUpdatedAt ?? Date.now(),
};
- const hasSignal = out.fiveHourPercent !== undefined
+ return out.fiveHourPercent !== undefined
|| out.weeklyPercent !== undefined
|| out.monthlyPercent !== undefined
- || (out.customWindows?.length ?? 0) > 0;
- return hasSignal ? out : null;
+ || (out.customWindows?.length ?? 0) > 0
+ ? out
+ : null;
+}
+
+/** Narrow an unknown quota payload into the AccountQuota display shape (null when unusable). */
+export function accountQuotaFromReport(report?: ProviderQuotaReportView): AccountQuota | null {
+ return quotaFromUnknown(report?.quota, report?.updatedAt);
+}
+
+function capacityWindow(value: unknown): CapacityWindowView | undefined {
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
+ const row = value as Record;
+ const usedPercent = finite(row.usedPercent);
+ if (usedPercent === undefined) return undefined;
+ return {
+ usedPercent,
+ ...(typeof row.incomplete === "boolean" ? { incomplete: row.incomplete } : {}),
+ ...(finite(row.excludedAccounts) !== undefined ? { excludedAccounts: row.excludedAccounts as number } : {}),
+ ...(finite(row.nextRecoveryAt) !== undefined ? { nextRecoveryAt: row.nextRecoveryAt as number } : {}),
+ ...(finite(row.nextRecoveryPercent) !== undefined ? { nextRecoveryPercent: row.nextRecoveryPercent as number } : {}),
+ };
+}
+
+/** Strictly narrows optional weighted-pool metadata; legacy reports return null. */
+export function capacityAggregationFromReport(report?: ProviderQuotaReportView): ProviderCapacityAggregationView | null {
+ const value = report?.aggregation;
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
+ const row = value as Record;
+ if (row.kind !== "capacity-weighted-v1" || row.scope !== "routable-known") return null;
+ const excludedAccounts = finite(row.excludedAccounts);
+ const unknownPlanAccounts = finite(row.unknownPlanAccounts);
+ if (excludedAccounts === undefined || unknownPlanAccounts === undefined || typeof row.incomplete !== "boolean") return null;
+ const currentRaw = row.currentAccount && typeof row.currentAccount === "object" && !Array.isArray(row.currentAccount)
+ ? row.currentAccount as Record
+ : null;
+ const customWindows = Array.isArray(row.customWindows)
+ ? row.customWindows.flatMap(entry => {
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return [];
+ const custom = entry as Record;
+ const window = capacityWindow(custom);
+ return typeof custom.label === "string" && window ? [{ label: custom.label, ...window }] : [];
+ })
+ : [];
+ const fiveHour = capacityWindow(row.fiveHour);
+ const weekly = capacityWindow(row.weekly);
+ const monthly = capacityWindow(row.monthly);
+ const hasAggregateWindow = !!fiveHour || !!weekly || !!monthly || customWindows.length > 0;
+ const presentation = row.presentation === "aggregate"
+ || row.presentation === "effective-account-fallback"
+ || row.presentation === "coverage-only"
+ ? row.presentation
+ : hasAggregateWindow ? "aggregate" : "coverage-only";
+ return {
+ presentation,
+ incomplete: row.incomplete,
+ excludedAccounts,
+ unknownPlanAccounts,
+ partialWindowAccounts: finite(row.partialWindowAccounts) ?? 0,
+ ...(fiveHour ? { fiveHour } : {}),
+ ...(weekly ? { weekly } : {}),
+ ...(monthly ? { monthly } : {}),
+ ...(customWindows.length > 0 ? { customWindows } : {}),
+ ...(currentRaw ? {
+ currentAccount: {
+ ...(typeof currentRaw.plan === "string" || currentRaw.plan === null ? { plan: currentRaw.plan } : {}),
+ quota: quotaFromUnknown(currentRaw.quota),
+ },
+ } : {}),
+ };
}
/** Human label for a quota report source id (e.g. "cursor:period-usage"). */
diff --git a/gui/src/styles/provider-overview-dashboard.css b/gui/src/styles/provider-overview-dashboard.css
index ba11de9d06..09f00fd1e2 100644
--- a/gui/src/styles/provider-overview-dashboard.css
+++ b/gui/src/styles/provider-overview-dashboard.css
@@ -190,6 +190,66 @@
min-height: 64px;
}
+.pws-capacity-label,
+.pws-capacity-recovery,
+.pws-capacity-incomplete {
+ font-size: 0.7rem;
+}
+
+.pws-capacity-label {
+ color: var(--fg-muted, #888);
+ margin-bottom: 3px;
+}
+
+.pws-capacity-details {
+ display: grid;
+ gap: 7px;
+ margin-top: 8px;
+ padding-top: 7px;
+ border-top: 1px solid var(--border-soft);
+}
+
+.pws-capacity-recovery {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 12px;
+ color: var(--fg-muted, #888);
+}
+
+.pws-capacity-recovery span {
+ min-width: 0;
+ overflow-wrap: anywhere;
+}
+
+.pws-capacity-recovery strong {
+ color: var(--text);
+ white-space: nowrap;
+ margin-left: auto;
+}
+
+@container (max-width: 520px) {
+ .pws-capacity-recovery {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: 3px;
+ }
+
+ .pws-capacity-recovery strong {
+ margin-left: 0;
+ white-space: normal;
+ }
+}
+
+.pws-capacity-current {
+ padding-top: 2px;
+}
+
+.pws-capacity-incomplete {
+ color: var(--warning, #b7791f);
+}
+
.pws-dashboard-section--rate-limits,
.pws-dashboard-section--recent {
min-height: 180px;
diff --git a/gui/src/styles/provider-quota.css b/gui/src/styles/provider-quota.css
index b11ed3d45c..bc990d6820 100644
--- a/gui/src/styles/provider-quota.css
+++ b/gui/src/styles/provider-quota.css
@@ -44,7 +44,31 @@
font-weight: 600;
}
+.quota-stacked-limit-group {
+ display: inline-flex;
+ flex: 1 1 auto;
+ flex-wrap: wrap;
+ align-items: baseline;
+ gap: 4px;
+ min-width: 0;
+ overflow-wrap: anywhere;
+}
+
+.quota-window-partial {
+ flex: 0 0 auto;
+ padding: 1px 5px;
+ border: 1px solid color-mix(in srgb, var(--amber) 45%, transparent);
+ border-radius: 999px;
+ color: var(--amber);
+ font-size: 10px;
+ font-weight: 600;
+ line-height: 1.3;
+ white-space: nowrap;
+}
+
.quota-stacked-reset {
+ min-width: 0;
+ overflow-wrap: anywhere;
font-size: 12px;
}
diff --git a/gui/tests/provider-capacity-shell.test.tsx b/gui/tests/provider-capacity-shell.test.tsx
new file mode 100644
index 0000000000..315d47f857
--- /dev/null
+++ b/gui/tests/provider-capacity-shell.test.tsx
@@ -0,0 +1,440 @@
+import { afterEach, beforeEach, expect, test } from "bun:test";
+import { Window } from "happy-dom";
+import { act } from "react";
+import type { Root } from "react-dom/client";
+import ProviderWorkspaceShell from "../src/components/provider-workspace/ProviderWorkspaceShell";
+import { LanguageProvider } from "../src/i18n/provider";
+import { readSessionListCache, writeSessionListCache } from "../src/session-list-cache";
+
+const globals = ["document", "window", "navigator", "localStorage", "sessionStorage", "IS_REACT_ACT_ENVIRONMENT"] as const;
+let previous: Record<(typeof globals)[number], unknown>;
+let originalFetch: typeof globalThis.fetch;
+let win: Window;
+let host: HTMLElement;
+let root: Root | null = null;
+let quotaPayload: unknown;
+let rejectQuotaFetch = false;
+let quotaFetchOverride: (() => Promise) | null = null;
+
+const QUOTA_CACHE_KEY = "ocx.providers.quotas.v1:";
+const RECOVERY_AT = Date.UTC(2026, 7, 8, 4, 32);
+const providers = {
+ openai: { adapter: "openai-responses", authMode: "forward", baseUrl: "https://chatgpt.com/backend-api/codex" },
+} as never;
+
+type TestCapacityWindow = {
+ usedPercent: number;
+ includedAccounts: number;
+ excludedAccounts: number;
+ incomplete: boolean;
+ updatedAt: number;
+ nextRecoveryAt?: number;
+ nextRecoveryPercent?: number;
+};
+
+type AggregateTestPayload = {
+ reports: [{
+ provider: string;
+ label: string;
+ source: string;
+ updatedAt: number;
+ quota: { weeklyPercent: number; monthlyPercent?: number; updatedAt: number };
+ aggregation: {
+ kind: string;
+ scope: string;
+ presentation: string;
+ includedAccounts: number;
+ excludedAccounts: number;
+ unknownPlanAccounts: number;
+ missingQuotaAccounts: number;
+ pausedAccounts: number;
+ reauthAccounts: number;
+ staleQuotaAccounts: number;
+ partialWindowAccounts?: number;
+ incomplete: boolean;
+ weekly: TestCapacityWindow;
+ monthly?: TestCapacityWindow;
+ currentAccount: { isMain: boolean; plan: string; quota: { weeklyPercent: number; updatedAt: number } };
+ };
+ }];
+};
+
+function aggregatePayload(): AggregateTestPayload {
+ return {
+ reports: [{
+ provider: "openai",
+ label: "OpenAI (Codex login)",
+ source: "chatgpt:wham",
+ updatedAt: Date.now(),
+ quota: { weeklyPercent: 30.8, updatedAt: Date.now() },
+ aggregation: {
+ kind: "capacity-weighted-v1",
+ scope: "routable-known",
+ presentation: "aggregate",
+ includedAccounts: 2,
+ excludedAccounts: 1,
+ unknownPlanAccounts: 1,
+ missingQuotaAccounts: 0,
+ pausedAccounts: 0,
+ reauthAccounts: 0,
+ staleQuotaAccounts: 0,
+ incomplete: true,
+ weekly: {
+ usedPercent: 30.8,
+ includedAccounts: 2,
+ excludedAccounts: 1,
+ incomplete: true,
+ updatedAt: Date.now(),
+ nextRecoveryAt: RECOVERY_AT,
+ nextRecoveryPercent: 19.2,
+ },
+ currentAccount: { isMain: true, plan: "pro", quota: { weeklyPercent: 8, updatedAt: Date.now() } },
+ },
+ }],
+ };
+}
+
+function quotaResponse(body: unknown): Response {
+ return {
+ ok: true,
+ status: 200,
+ json: async () => body,
+ text: async () => JSON.stringify(body),
+ } as unknown as Response;
+}
+
+function aggregateWindowPayload(weeklyIncomplete: boolean, monthlyIncomplete: boolean) {
+ const now = Date.now();
+ const incomplete = weeklyIncomplete || monthlyIncomplete;
+ return {
+ reports: [{
+ provider: "openai",
+ label: "OpenAI (Codex login)",
+ source: "chatgpt:wham",
+ updatedAt: now,
+ quota: { weeklyPercent: 20, monthlyPercent: 40, updatedAt: now },
+ aggregation: {
+ kind: "capacity-weighted-v1",
+ scope: "routable-known",
+ presentation: "aggregate",
+ includedAccounts: 2,
+ excludedAccounts: 0,
+ unknownPlanAccounts: 0,
+ partialWindowAccounts: incomplete ? 1 : 0,
+ incomplete,
+ weekly: {
+ usedPercent: 20,
+ includedAccounts: weeklyIncomplete ? 1 : 2,
+ excludedAccounts: weeklyIncomplete ? 1 : 0,
+ incomplete: weeklyIncomplete,
+ updatedAt: now,
+ },
+ monthly: {
+ usedPercent: 40,
+ includedAccounts: monthlyIncomplete ? 1 : 2,
+ excludedAccounts: monthlyIncomplete ? 1 : 0,
+ incomplete: monthlyIncomplete,
+ updatedAt: now,
+ },
+ },
+ }],
+ };
+}
+
+beforeEach(() => {
+ previous = Object.fromEntries(globals.map(key => [key, Reflect.get(globalThis, key)])) as typeof previous;
+ originalFetch = globalThis.fetch;
+ win = new Window({ url: "http://localhost/" });
+ Object.defineProperty(win.navigator, "language", { configurable: true, value: "en-US" });
+ Object.defineProperty(win, "event", { configurable: true, writable: true, value: undefined });
+ Object.defineProperties(globalThis, {
+ document: { configurable: true, value: win.document },
+ window: { configurable: true, value: win },
+ navigator: { configurable: true, value: win.navigator },
+ localStorage: { configurable: true, value: win.localStorage },
+ sessionStorage: { configurable: true, value: win.sessionStorage },
+ });
+ (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
+ quotaPayload = aggregatePayload();
+ rejectQuotaFetch = false;
+ quotaFetchOverride = null;
+ Object.defineProperty(globalThis, "fetch", {
+ configurable: true,
+ value: async (input: string) => {
+ const url = String(input);
+ if (url.includes("/api/provider-quotas") && rejectQuotaFetch) throw new Error("quota unavailable");
+ if (url.includes("/api/provider-quotas") && quotaFetchOverride) return quotaFetchOverride();
+ const body = url.includes("/api/provider-quotas") ? quotaPayload : {};
+ return quotaResponse(body);
+ },
+ });
+ host = win.document.createElement("div") as unknown as HTMLElement;
+ win.document.body.appendChild(host as never);
+});
+
+afterEach(async () => {
+ if (root) {
+ const current = root;
+ await act(async () => { current.unmount(); });
+ root = null;
+ }
+ for (const key of globals) Object.defineProperty(globalThis, key, { configurable: true, value: previous[key] });
+ Object.defineProperty(globalThis, "fetch", { configurable: true, value: originalFetch });
+});
+
+async function mountShell(quotaRefreshEpoch = 0) {
+ const { createRoot } = await import("react-dom/client");
+ await act(async () => {
+ root ??= createRoot(host);
+ root.render(
+
+ {}}
+ onAddProvider={() => {}}
+ quotaRefreshEpoch={quotaRefreshEpoch}
+ />
+ ,
+ );
+ });
+ await act(async () => { await new Promise(resolve => setTimeout(resolve, 30)); });
+}
+
+test("provider quota fetch preserves aggregate capacity through shell state and render", async () => {
+ await mountShell();
+
+ const text = host.textContent ?? "";
+ expect(text).toContain("Configured-weight pool estimate");
+ expect(text).toContain("31% used");
+ expect(text).toContain("Current effective account · pro");
+ expect(text).toContain("8%");
+ expect(text).toContain("Incomplete coverage: 1 account(s) excluded, including 1 unknown plan(s)");
+ expect(text).toContain("Next capacity recovery");
+ expect(text).toContain("+19.2% pool capacity");
+ const expectedRecoveryAt = new Intl.DateTimeFormat("en", {
+ dateStyle: "medium",
+ timeStyle: "short",
+ }).format(new Date(RECOVERY_AT));
+ expect(text).toContain(expectedRecoveryAt);
+ expect(text).not.toMatch(/configured units|weighted units|units remaining|projected/i);
+});
+
+test("successful empty quota response removes cached providers and updates session cache", async () => {
+ const seeded = (aggregatePayload().reports[0]);
+ const { provider: _provider, ...cached } = seeded;
+ writeSessionListCache(QUOTA_CACHE_KEY, { openai: cached });
+ quotaPayload = { reports: [] };
+
+ await mountShell();
+
+ expect(host.textContent ?? "").not.toContain("Configured-weight pool estimate");
+ expect(readSessionListCache(QUOTA_CACHE_KEY)).toEqual({});
+});
+
+test("expired session quota is rejected and a failed fetch cannot keep it rendered", async () => {
+ const old = Date.now() - 31 * 60_000;
+ writeSessionListCache(QUOTA_CACHE_KEY, {
+ openai: {
+ label: "OpenAI (Codex login)",
+ source: "chatgpt:wham",
+ updatedAt: old,
+ quota: { weeklyPercent: 99, updatedAt: old },
+ aggregation: { ...aggregatePayload().reports[0].aggregation, presentation: "aggregate" },
+ },
+ });
+ rejectQuotaFetch = true;
+
+ await mountShell();
+
+ const text = host.textContent ?? "";
+ expect(text).not.toContain("Configured-weight pool estimate");
+ expect(text).not.toContain("99% used");
+ expect(readSessionListCache(QUOTA_CACHE_KEY)).toEqual({});
+});
+
+test("a cancelled superseded quota rejection cannot rewrite state or session cache", async () => {
+ let rejectFirst!: (reason?: unknown) => void;
+ const first = new Promise((_resolve, reject) => { rejectFirst = reject; });
+ const fresh = aggregatePayload();
+ fresh.reports[0].quota.weeklyPercent = 63;
+ fresh.reports[0].aggregation.weekly.usedPercent = 63;
+ let calls = 0;
+ quotaFetchOverride = () => {
+ calls += 1;
+ return calls === 1 ? first : Promise.resolve(quotaResponse(fresh));
+ };
+
+ await mountShell(0);
+ await mountShell(1);
+ expect(calls).toBe(2);
+ expect(host.textContent ?? "").toContain("63% used");
+ const cached = readSessionListCache(QUOTA_CACHE_KEY);
+ const writes: string[] = [];
+ const storage = win.sessionStorage as unknown as Storage;
+ const setItem = storage.setItem.bind(storage);
+ Object.defineProperty(storage, "setItem", {
+ configurable: true,
+ value: (key: string, value: string) => {
+ writes.push(key);
+ setItem(key, value);
+ },
+ });
+
+ await act(async () => {
+ rejectFirst(new Error("superseded"));
+ await new Promise(resolve => setTimeout(resolve, 0));
+ });
+
+ expect(writes).toEqual([]);
+ expect(readSessionListCache(QUOTA_CACHE_KEY)).toEqual(cached);
+ expect(host.textContent ?? "").toContain("63% used");
+});
+
+test("all-stale response renders coverage only without a numeric fallback", async () => {
+ const old = Date.now() - 31 * 60_000;
+ quotaPayload = {
+ reports: [{
+ provider: "openai",
+ label: "OpenAI (Codex login)",
+ source: "chatgpt:wham",
+ updatedAt: Date.now(),
+ quota: { updatedAt: Date.now() },
+ aggregation: {
+ kind: "capacity-weighted-v1",
+ scope: "routable-known",
+ presentation: "coverage-only",
+ includedAccounts: 0,
+ excludedAccounts: 2,
+ unknownPlanAccounts: 0,
+ incomplete: true,
+ partialWindowAccounts: 0,
+ currentAccount: { isMain: true, plan: "pro", quota: null },
+ },
+ }],
+ };
+
+ await mountShell();
+
+ const text = host.textContent ?? "";
+ expect(text).not.toContain("Configured-weight pool estimate");
+ expect(text).not.toContain("Current effective account");
+ expect(text).not.toContain("80% used");
+ expect(text).toContain("Incomplete coverage: 2 account(s) excluded");
+});
+
+test("coverage-only API report remains visible in the rate-limit overview", async () => {
+ quotaPayload = {
+ reports: [{
+ provider: "openai",
+ label: "OpenAI (Codex login)",
+ source: "chatgpt:wham",
+ updatedAt: Date.now(),
+ quota: { updatedAt: Date.now() },
+ aggregation: {
+ kind: "capacity-weighted-v1",
+ scope: "routable-known",
+ presentation: "coverage-only",
+ includedAccounts: 0,
+ excludedAccounts: 3,
+ unknownPlanAccounts: 1,
+ partialWindowAccounts: 0,
+ incomplete: true,
+ },
+ }],
+ };
+
+ await mountShell();
+
+ const text = host.textContent ?? "";
+ expect(text).toContain("OpenAI (Codex login)");
+ expect(text).toContain("Incomplete coverage: 3 account(s) excluded, including 1 unknown plan(s)");
+ expect(text).not.toContain("No rate-limit data yet");
+ expect(text).not.toMatch(/\d+(?:\.\d+)?% used/);
+});
+
+test("mixed-window coverage uses a distinct warning without whole-account exclusion", async () => {
+ const payload = aggregatePayload();
+ payload.reports[0].quota = { weeklyPercent: 25, monthlyPercent: 40, updatedAt: Date.now() };
+ payload.reports[0].aggregation.excludedAccounts = 0;
+ payload.reports[0].aggregation.unknownPlanAccounts = 0;
+ payload.reports[0].aggregation.partialWindowAccounts = 2;
+ payload.reports[0].aggregation.monthly = {
+ usedPercent: 40,
+ includedAccounts: 2,
+ excludedAccounts: 1,
+ incomplete: true,
+ updatedAt: Date.now(),
+ };
+ quotaPayload = payload;
+
+ await mountShell();
+
+ const text = host.textContent ?? "";
+ expect(text).toContain("Partial window coverage: 2 account(s) do not report every displayed limit window");
+ expect(text).not.toContain("Incomplete coverage: 0 account(s) excluded");
+});
+
+test("only the monthly aggregate window receives a localized partial marker", async () => {
+ quotaPayload = aggregateWindowPayload(false, true);
+ await mountShell();
+
+ const markers = [...host.querySelectorAll(".quota-window-partial")];
+ expect(markers).toHaveLength(1);
+ expect(markers[0]?.textContent).toBe("Partial");
+ expect(markers[0]?.getAttribute("role")).toBe("note");
+ expect(markers[0]?.getAttribute("aria-label")).toBe("30-day limit: incomplete account coverage");
+});
+
+test("only the weekly aggregate window receives a localized partial marker", async () => {
+ quotaPayload = aggregateWindowPayload(true, false);
+ await mountShell();
+
+ const markers = [...host.querySelectorAll(".quota-window-partial")];
+ expect(markers).toHaveLength(1);
+ expect(markers[0]?.getAttribute("aria-label")).toBe("Weekly limit: incomplete account coverage");
+});
+
+test("complete aggregate windows do not receive partial markers", async () => {
+ quotaPayload = aggregateWindowPayload(false, false);
+ await mountShell();
+
+ expect(host.querySelectorAll(".quota-window-partial")).toHaveLength(0);
+ expect(host.textContent ?? "").not.toContain("Partial window coverage");
+});
+
+test("five-hour and custom aggregate windows can be marked independently", async () => {
+ const now = Date.now();
+ quotaPayload = {
+ reports: [{
+ provider: "openai",
+ label: "OpenAI (Codex login)",
+ source: "chatgpt:wham",
+ updatedAt: now,
+ quota: {
+ fiveHourPercent: 10,
+ customWindows: [{ label: "Burst", percent: 30 }],
+ updatedAt: now,
+ },
+ aggregation: {
+ kind: "capacity-weighted-v1",
+ scope: "routable-known",
+ presentation: "aggregate",
+ includedAccounts: 2,
+ excludedAccounts: 0,
+ unknownPlanAccounts: 0,
+ partialWindowAccounts: 1,
+ incomplete: true,
+ fiveHour: { usedPercent: 10, includedAccounts: 2, excludedAccounts: 0, incomplete: false, updatedAt: now },
+ customWindows: [{ label: "Burst", usedPercent: 30, includedAccounts: 1, excludedAccounts: 1, incomplete: true, updatedAt: now }],
+ },
+ }],
+ };
+ await mountShell();
+
+ const markers = [...host.querySelectorAll(".quota-window-partial")];
+ expect(markers).toHaveLength(1);
+ expect(markers[0]?.getAttribute("aria-label")).toBe("Burst: incomplete account coverage");
+});
diff --git a/gui/tests/provider-capacity.test.ts b/gui/tests/provider-capacity.test.ts
new file mode 100644
index 0000000000..92a80c9de3
--- /dev/null
+++ b/gui/tests/provider-capacity.test.ts
@@ -0,0 +1,122 @@
+import { expect, test } from "bun:test";
+import { capacityAggregationFromReport } from "../src/provider-workspace/report";
+
+function selectorBlock(css: string, selector: string): string {
+ const start = css.indexOf(`${selector} {`);
+ expect(start).toBeGreaterThanOrEqual(0);
+ const end = css.indexOf("}", start);
+ expect(end).toBeGreaterThan(start);
+ return css.slice(start, end + 1);
+}
+
+test("legacy provider quota reports remain valid without aggregation metadata", () => {
+ expect(capacityAggregationFromReport({ quota: { weeklyPercent: 42 } })).toBeNull();
+});
+
+test("capacity metadata preserves estimate, raw current quota, recovery percent, and incomplete coverage", () => {
+ const aggregation = capacityAggregationFromReport({
+ quota: { weeklyPercent: 30.769230769, updatedAt: 123 },
+ aggregation: {
+ kind: "capacity-weighted-v1",
+ scope: "routable-known",
+ presentation: "aggregate",
+ incomplete: true,
+ excludedAccounts: 2,
+ unknownPlanAccounts: 1,
+ partialWindowAccounts: 0,
+ weekly: {
+ usedPercent: 30.769230769,
+ nextRecoveryAt: 1_800_000_010_000,
+ nextRecoveryPercent: 19.23076923,
+ },
+ currentAccount: {
+ plan: "pro",
+ quota: { weeklyPercent: 10, weeklyResetAt: 1_800_000_030, updatedAt: 123 },
+ },
+ },
+ });
+ expect(aggregation).toMatchObject({
+ presentation: "aggregate",
+ incomplete: true,
+ excludedAccounts: 2,
+ unknownPlanAccounts: 1,
+ partialWindowAccounts: 0,
+ weekly: { usedPercent: 30.769230769, nextRecoveryPercent: 19.23076923 },
+ currentAccount: { plan: "pro", quota: { weeklyPercent: 10 } },
+ });
+ expect(aggregation?.weekly).not.toHaveProperty("projectedUsedPercentAfterReset");
+});
+
+test("window coverage metadata keeps monthly, weekly, and complete states independent", () => {
+ const adapt = (weeklyIncomplete: boolean, monthlyIncomplete: boolean) => capacityAggregationFromReport({
+ quota: { weeklyPercent: 20, monthlyPercent: 40, updatedAt: 123 },
+ aggregation: {
+ kind: "capacity-weighted-v1",
+ scope: "routable-known",
+ presentation: "aggregate",
+ incomplete: weeklyIncomplete || monthlyIncomplete,
+ excludedAccounts: 0,
+ unknownPlanAccounts: 0,
+ partialWindowAccounts: weeklyIncomplete || monthlyIncomplete ? 1 : 0,
+ weekly: { usedPercent: 20, incomplete: weeklyIncomplete, excludedAccounts: weeklyIncomplete ? 1 : 0 },
+ monthly: { usedPercent: 40, incomplete: monthlyIncomplete, excludedAccounts: monthlyIncomplete ? 1 : 0 },
+ },
+ });
+
+ expect(adapt(false, true)).toMatchObject({
+ weekly: { incomplete: false, excludedAccounts: 0 },
+ monthly: { incomplete: true, excludedAccounts: 1 },
+ });
+ expect(adapt(true, false)).toMatchObject({
+ weekly: { incomplete: true, excludedAccounts: 1 },
+ monthly: { incomplete: false, excludedAccounts: 0 },
+ });
+ expect(adapt(false, false)).toMatchObject({
+ weekly: { incomplete: false, excludedAccounts: 0 },
+ monthly: { incomplete: false, excludedAccounts: 0 },
+ });
+});
+
+test("fallback and coverage-only metadata never become aggregate presentation", () => {
+ const fallback = capacityAggregationFromReport({
+ quota: { weeklyPercent: 80, updatedAt: 123 },
+ aggregation: {
+ kind: "capacity-weighted-v1",
+ scope: "routable-known",
+ presentation: "effective-account-fallback",
+ incomplete: true,
+ excludedAccounts: 2,
+ unknownPlanAccounts: 0,
+ },
+ });
+ expect(fallback?.presentation).toBe("effective-account-fallback");
+ const legacyCoverage = capacityAggregationFromReport({
+ aggregation: {
+ kind: "capacity-weighted-v1",
+ scope: "routable-known",
+ incomplete: true,
+ excludedAccounts: 2,
+ unknownPlanAccounts: 1,
+ },
+ });
+ expect(legacyCoverage?.presentation).toBe("coverage-only");
+});
+
+test("capacity recovery layout wraps and stacks in narrow provider panes", async () => {
+ const css = await Bun.file(new URL("../src/styles/provider-overview-dashboard.css", import.meta.url)).text();
+ const quotaCss = await Bun.file(new URL("../src/styles/provider-quota.css", import.meta.url)).text();
+ const recovery = selectorBlock(css, ".pws-capacity-recovery");
+ const recoveryText = selectorBlock(css, ".pws-capacity-recovery span");
+ expect(recovery).toContain("flex-wrap: wrap;");
+ expect(recoveryText).toContain("overflow-wrap: anywhere;");
+ expect(css).toContain("@container (max-width: 520px)");
+ expect(css.slice(css.indexOf("@container (max-width: 520px)"))).toContain("grid-template-columns: minmax(0, 1fr);");
+ const limitGroup = selectorBlock(quotaCss, ".quota-stacked-limit-group");
+ expect(limitGroup).toContain("flex-wrap: wrap;");
+ expect(limitGroup).toContain("overflow-wrap: anywhere;");
+});
+
+test("malformed or future aggregation contracts fail closed", () => {
+ expect(capacityAggregationFromReport({ aggregation: { kind: "capacity-weighted-v2" } })).toBeNull();
+ expect(capacityAggregationFromReport({ aggregation: { kind: "capacity-weighted-v1", scope: "routable-known" } })).toBeNull();
+});
diff --git a/src/codex/auth-api.ts b/src/codex/auth-api.ts
index fa0472267a..8f9e257ed7 100644
--- a/src/codex/auth-api.ts
+++ b/src/codex/auth-api.ts
@@ -750,6 +750,10 @@ export function clearCodexQuotaPrimeState(): void {
primeInFlight = null;
}
+export function effectiveCodexAuthAccountId(config: OcxConfig): string {
+ return getEffectiveActiveCodexAccountId(config) ?? MAIN_CODEX_ACCOUNT_ID;
+}
+
export async function listCodexAuthAccounts(config: OcxConfig, forceRefresh = false): Promise {
const runtimeConfig = getRuntimeConfig(config);
const poolAccounts = (runtimeConfig.codexAccounts ?? []).filter(isSelectableCodexPoolAccount);
@@ -830,7 +834,12 @@ export async function listCodexAuthAccounts(config: OcxConfig, forceRefresh = fa
paused: isCodexAccountPaused(runtimeConfig, MAIN_CODEX_ACCOUNT_ID),
hasCredential: hasMainCredential,
needsReauth: mainNeedsReauth,
- quota: mainInfo.quota ? { ...quotaForPlan({ ...mainInfo.quota, updatedAt: Date.now() }, mainInfo.plan) } : null,
+ quota: mainInfo.quota ? {
+ ...quotaForPlan({
+ ...mainInfo.quota,
+ updatedAt: getAccountQuota(MAIN_CODEX_ACCOUNT_ID)?.updatedAt ?? Date.now(),
+ }, mainInfo.plan),
+ } : null,
...oauthAccountHealthFields("codex", MAIN_CODEX_ACCOUNT_ID, mainHealth),
};
return [main, ...withQuota];
diff --git a/src/providers/codex-capacity.ts b/src/providers/codex-capacity.ts
new file mode 100644
index 0000000000..7ced2ce9bb
--- /dev/null
+++ b/src/providers/codex-capacity.ts
@@ -0,0 +1,288 @@
+export const CODEX_CONFIGURED_CAPACITY_WEIGHTS = {
+ plus: 1,
+ business: 1,
+ prolite: 5,
+ pro: 20,
+} as const;
+
+/** Match the provider-report last-good freshness bound. */
+export const CODEX_CAPACITY_MAX_QUOTA_AGE_MS = 30 * 60_000;
+
+export type CodexCapacityQuota = {
+ fiveHourPercent?: number;
+ fiveHourResetAt?: number;
+ weeklyPercent?: number;
+ weeklyResetAt?: number;
+ monthlyPercent?: number;
+ monthlyResetAt?: number;
+ customWindows?: Array<{ label: string; percent: number; resetAt?: number }>;
+ updatedAt: number;
+};
+
+export interface CodexCapacityAccount {
+ isMain: boolean;
+ active?: boolean;
+ plan?: string | null;
+ paused: boolean;
+ needsReauth?: boolean;
+ quota: CodexCapacityQuota | null;
+}
+
+export interface CodexCapacityWindowAggregation {
+ usedPercent: number;
+ includedAccounts: number;
+ excludedAccounts: number;
+ incomplete: boolean;
+ /** Internal calculation evidence; stripped from the management API response. */
+ totalWeight?: number;
+ /** Internal calculation evidence; stripped from the management API response. */
+ consumedWeight?: number;
+ /** Internal calculation evidence; stripped from the management API response. */
+ remainingWeight?: number;
+ updatedAt: number;
+ nextRecoveryAt?: number;
+ nextRecoveryPercent?: number;
+}
+
+export interface CodexCapacityAggregation {
+ kind: "capacity-weighted-v1";
+ scope: "routable-known";
+ includedAccounts: number;
+ excludedAccounts: number;
+ unknownPlanAccounts: number;
+ missingQuotaAccounts: number;
+ pausedAccounts: number;
+ reauthAccounts: number;
+ staleQuotaAccounts: number;
+ partialWindowAccounts: number;
+ incomplete: boolean;
+ presentation?: "aggregate" | "effective-account-fallback" | "coverage-only";
+ fiveHour?: CodexCapacityWindowAggregation;
+ weekly?: CodexCapacityWindowAggregation;
+ monthly?: CodexCapacityWindowAggregation;
+ customWindows?: Array;
+ currentAccount?: {
+ isMain: boolean;
+ plan?: string | null;
+ quota: CodexCapacityQuota | null;
+ };
+}
+
+export interface CodexCapacityResult {
+ quota: CodexCapacityQuota | null;
+ aggregation: CodexCapacityAggregation | null;
+ currentAccount?: CodexCapacityAggregation["currentAccount"];
+}
+
+type MutableWindow = {
+ totalWeight: number;
+ consumedWeight: number;
+ includedAccounts: number;
+ recoveries: Map;
+ oldestUpdatedAt: number;
+};
+
+function configuredWeight(plan: string | null | undefined): number | undefined {
+ const normalized = plan?.trim().toLowerCase();
+ return normalized && Object.hasOwn(CODEX_CONFIGURED_CAPACITY_WEIGHTS, normalized)
+ ? CODEX_CONFIGURED_CAPACITY_WEIGHTS[normalized as keyof typeof CODEX_CONFIGURED_CAPACITY_WEIGHTS]
+ : undefined;
+}
+
+function normalizedPercent(value: unknown): number | undefined {
+ return typeof value === "number" && Number.isFinite(value)
+ ? Math.max(0, Math.min(100, value))
+ : undefined;
+}
+
+function futureResetMs(value: unknown, now: number): number | undefined {
+ if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
+ const milliseconds = value > 10_000_000_000 ? value : value * 1000;
+ return milliseconds > now ? milliseconds : undefined;
+}
+
+function hasKnownQuotaWindow(quota: CodexCapacityQuota | null): quota is CodexCapacityQuota {
+ if (!quota) return false;
+ return normalizedPercent(quota.fiveHourPercent) !== undefined
+ || normalizedPercent(quota.weeklyPercent) !== undefined
+ || normalizedPercent(quota.monthlyPercent) !== undefined
+ || !!quota.customWindows?.some(window => normalizedPercent(window.percent) !== undefined);
+}
+
+function currentQuotaForDisplay(account: CodexCapacityAccount, now: number): CodexCapacityQuota | null {
+ const quota = account.quota;
+ const fresh = !!quota
+ && Number.isFinite(quota.updatedAt)
+ && now - quota.updatedAt <= CODEX_CAPACITY_MAX_QUOTA_AGE_MS;
+ return !account.paused && !account.needsReauth && fresh && hasKnownQuotaWindow(quota) ? quota : null;
+}
+
+function addWindow(
+ windows: Map,
+ key: string,
+ weight: number,
+ percent: number,
+ resetAt: number | undefined,
+ updatedAt: number,
+): void {
+ const window = windows.get(key) ?? {
+ totalWeight: 0,
+ consumedWeight: 0,
+ includedAccounts: 0,
+ recoveries: new Map(),
+ oldestUpdatedAt: updatedAt,
+ };
+ const consumed = weight * percent / 100;
+ window.totalWeight += weight;
+ window.consumedWeight += consumed;
+ window.includedAccounts += 1;
+ window.oldestUpdatedAt = Math.min(window.oldestUpdatedAt, updatedAt);
+ if (resetAt !== undefined && consumed > 0) {
+ window.recoveries.set(resetAt, (window.recoveries.get(resetAt) ?? 0) + consumed);
+ }
+ windows.set(key, window);
+}
+
+function finalizeWindow(window: MutableWindow, totalAccounts: number): CodexCapacityWindowAggregation {
+ const nextRecoveryAt = [...window.recoveries.keys()].sort((a, b) => a - b)[0];
+ const recovered = nextRecoveryAt === undefined ? undefined : window.recoveries.get(nextRecoveryAt);
+ return {
+ usedPercent: window.consumedWeight / window.totalWeight * 100,
+ includedAccounts: window.includedAccounts,
+ excludedAccounts: totalAccounts - window.includedAccounts,
+ incomplete: window.includedAccounts < totalAccounts,
+ totalWeight: window.totalWeight,
+ consumedWeight: window.consumedWeight,
+ remainingWeight: window.totalWeight - window.consumedWeight,
+ updatedAt: window.oldestUpdatedAt,
+ ...(nextRecoveryAt !== undefined ? { nextRecoveryAt } : {}),
+ ...(recovered !== undefined ? { nextRecoveryPercent: recovered / window.totalWeight * 100 } : {}),
+ };
+}
+
+/** Display-only configured-weight estimate. It never participates in account selection or routing. */
+export function aggregateCodexPoolCapacity(
+ accounts: readonly CodexCapacityAccount[],
+ now = Date.now(),
+): CodexCapacityResult {
+ const current = accounts.find(account => account.active)
+ ?? accounts.find(account => account.isMain)
+ ?? accounts[0];
+ const currentAccount = current ? {
+ isMain: current.isMain,
+ ...(current.plan !== undefined ? { plan: current.plan } : {}),
+ quota: currentQuotaForDisplay(current, now),
+ } : undefined;
+ const windows = new Map();
+ const included = new Set();
+ const contributions = new Map>();
+ let unknownPlanAccounts = 0;
+ let missingQuotaAccounts = 0;
+ let pausedAccounts = 0;
+ let reauthAccounts = 0;
+ let staleQuotaAccounts = 0;
+
+ for (const account of accounts) {
+ const weight = configuredWeight(account.plan);
+ if (weight === undefined) unknownPlanAccounts += 1;
+ if (account.paused) pausedAccounts += 1;
+ if (account.needsReauth) reauthAccounts += 1;
+ const quota = account.quota;
+ const quotaFresh = !!quota
+ && Number.isFinite(quota.updatedAt)
+ && now - quota.updatedAt <= CODEX_CAPACITY_MAX_QUOTA_AGE_MS;
+ if (quota && !quotaFresh) staleQuotaAccounts += 1;
+ const standard = quota ? [
+ ["fiveHour", quota.fiveHourPercent, quota.fiveHourResetAt],
+ ["weekly", quota.weeklyPercent, quota.weeklyResetAt],
+ ["monthly", quota.monthlyPercent, quota.monthlyResetAt],
+ ] as const : [];
+ const custom = quota?.customWindows ?? [];
+ const hasQuota = hasKnownQuotaWindow(quota);
+ if (!hasQuota) missingQuotaAccounts += 1;
+ if (account.paused || account.needsReauth || weight === undefined || !quota || !hasQuota || !quotaFresh) continue;
+
+ let contributed = false;
+ const contributionKeys = new Set();
+ for (const [key, rawPercent, rawReset] of standard) {
+ const percent = normalizedPercent(rawPercent);
+ if (percent === undefined) continue;
+ addWindow(windows, key, weight, percent, futureResetMs(rawReset, now), quota.updatedAt);
+ contributionKeys.add(key);
+ contributed = true;
+ }
+ for (const customWindow of custom) {
+ const percent = normalizedPercent(customWindow.percent);
+ if (percent === undefined) continue;
+ addWindow(windows, `custom:${customWindow.label}`, weight, percent, futureResetMs(customWindow.resetAt, now), quota.updatedAt);
+ contributionKeys.add(`custom:${customWindow.label}`);
+ contributed = true;
+ }
+ if (contributed) {
+ included.add(account);
+ contributions.set(account, contributionKeys);
+ }
+ }
+
+ if (windows.size === 0) {
+ if (accounts.length === 0) return { quota: null, aggregation: null, ...(currentAccount ? { currentAccount } : {}) };
+ const aggregation: CodexCapacityAggregation = {
+ kind: "capacity-weighted-v1",
+ scope: "routable-known",
+ includedAccounts: 0,
+ excludedAccounts: accounts.length,
+ unknownPlanAccounts,
+ missingQuotaAccounts,
+ pausedAccounts,
+ reauthAccounts,
+ staleQuotaAccounts,
+ partialWindowAccounts: 0,
+ incomplete: true,
+ ...(currentAccount ? { currentAccount } : {}),
+ };
+ return { quota: null, aggregation, ...(currentAccount ? { currentAccount } : {}) };
+ }
+ const fiveHour = windows.get("fiveHour") ? finalizeWindow(windows.get("fiveHour")!, accounts.length) : undefined;
+ const weekly = windows.get("weekly") ? finalizeWindow(windows.get("weekly")!, accounts.length) : undefined;
+ const monthly = windows.get("monthly") ? finalizeWindow(windows.get("monthly")!, accounts.length) : undefined;
+ const customWindows = [...windows.entries()].flatMap(([key, window]) => key.startsWith("custom:")
+ ? [{ label: key.slice("custom:".length), ...finalizeWindow(window, accounts.length) }]
+ : []);
+ const quota: CodexCapacityQuota = {
+ ...(fiveHour ? { fiveHourPercent: fiveHour.usedPercent } : {}),
+ ...(weekly ? { weeklyPercent: weekly.usedPercent } : {}),
+ ...(monthly ? { monthlyPercent: monthly.usedPercent } : {}),
+ ...(customWindows.length > 0 ? {
+ customWindows: customWindows.map(window => ({ label: window.label, percent: window.usedPercent })),
+ } : {}),
+ updatedAt: Math.min(
+ ...[fiveHour, weekly, monthly, ...customWindows]
+ .flatMap(window => window ? [window.updatedAt] : []),
+ ),
+ };
+ const visibleWindowKeys = [...windows.keys()];
+ const partialWindowAccounts = accounts.filter(account => {
+ const keys = contributions.get(account);
+ return !!keys && visibleWindowKeys.some(key => !keys.has(key));
+ }).length;
+ const excludedAccounts = accounts.length - included.size;
+ const aggregation: CodexCapacityAggregation = {
+ kind: "capacity-weighted-v1",
+ scope: "routable-known",
+ includedAccounts: included.size,
+ excludedAccounts,
+ unknownPlanAccounts,
+ missingQuotaAccounts,
+ pausedAccounts,
+ reauthAccounts,
+ staleQuotaAccounts,
+ partialWindowAccounts,
+ incomplete: excludedAccounts > 0 || partialWindowAccounts > 0,
+ ...(fiveHour ? { fiveHour } : {}),
+ ...(weekly ? { weekly } : {}),
+ ...(monthly ? { monthly } : {}),
+ ...(customWindows.length > 0 ? { customWindows } : {}),
+ ...(currentAccount ? { currentAccount } : {}),
+ };
+ return { quota, aggregation, ...(currentAccount ? { currentAccount } : {}) };
+}
diff --git a/src/providers/quota.ts b/src/providers/quota.ts
index 131bc1358e..836d17869f 100644
--- a/src/providers/quota.ts
+++ b/src/providers/quota.ts
@@ -1,4 +1,5 @@
-import { fetchMainAccountInfo, listCodexAuthAccounts } from "../codex/auth-api";
+import { createHash } from "node:crypto";
+import { effectiveCodexAuthAccountId, fetchMainAccountInfo, listCodexAuthAccounts } from "../codex/auth-api";
import { MAIN_CODEX_ACCOUNT_ID } from "../codex/main-account";
import { resolveEnvValue } from "../config";
import { getValidAccessToken, getValidAccessTokenForAccount } from "../oauth";
@@ -12,6 +13,12 @@ import {
sweepExpiredOnWrite,
type GenerationContext,
} from "../lib/state-store-sweeper";
+import {
+ aggregateCodexPoolCapacity,
+ CODEX_CAPACITY_MAX_QUOTA_AGE_MS,
+ type CodexCapacityAggregation,
+ type CodexCapacityQuota,
+} from "./codex-capacity";
/** Match oauth/index REFRESH_SKEW_MS — use stored access without refresh when still fresh. */
const ACCOUNT_TOKEN_SKEW_MS = 60_000;
@@ -21,7 +28,7 @@ const REQUEST_TIMEOUT_MS = 8_000;
const KIMI_CODE_BASE_URL = "https://api.kimi.com/coding/v1";
const KIMI_CODE_USAGE_URL = `${KIMI_CODE_BASE_URL}/usages`;
/** Keep a failed probe's previous row at most this long before dropping it. */
-const LAST_GOOD_MAX_AGE_MS = 30 * 60_000;
+const LAST_GOOD_MAX_AGE_MS = CODEX_CAPACITY_MAX_QUOTA_AGE_MS;
export interface ProviderQuotaWindow {
label: string;
@@ -47,6 +54,7 @@ export interface ProviderQuotaReport {
quota: ProviderQuota;
updatedAt: number;
reverseEngineered?: boolean;
+ aggregation?: CodexCapacityAggregation;
}
export interface ProviderQuotaResponse {
@@ -70,7 +78,89 @@ function cacheKey(config: OcxConfig): string {
.map(([name, provider]) => `${name}:${provider.adapter}:${provider.authMode ?? "key"}:${providerCodexAccountMode(name, provider) ?? "none"}:${provider.disabled === true ? "off" : "on"}:${provider.baseUrl}`)
.sort()
.join("|");
- return `${config.defaultProvider}|${config.activeCodexAccountId ?? ""}|${providers}`;
+ return `${config.defaultProvider}|${providers}`;
+}
+
+type CodexAuthAccountsPromise = ReturnType;
+
+function hasCodexPoolProvider(config: OcxConfig): boolean {
+ return Object.entries(config.providers).some(([name, provider]) => (
+ provider.disabled !== true
+ && isBuiltInChatGptForwardProvider(name, provider)
+ && providerCodexAccountMode(name, provider) !== "direct"
+ ));
+}
+
+function quotaSignatureValue(quota: CodexCapacityQuota | null): unknown {
+ if (!quota) return null;
+ return {
+ fiveHourPercent: quota.fiveHourPercent,
+ fiveHourResetAt: quota.fiveHourResetAt,
+ weeklyPercent: quota.weeklyPercent,
+ weeklyResetAt: quota.weeklyResetAt,
+ monthlyPercent: quota.monthlyPercent,
+ monthlyResetAt: quota.monthlyResetAt,
+ updatedAt: quota.updatedAt,
+ customWindows: [...(quota.customWindows ?? [])]
+ .map(window => ({ label: window.label, percent: window.percent, resetAt: window.resetAt }))
+ .sort((a, b) => a.label.localeCompare(b.label)),
+ };
+}
+
+/** Hash only presentation-relevant state; account ids and email addresses never enter the key. */
+function cacheKeyWithAggregationState(
+ config: OcxConfig,
+ prefetchedAccounts?: CodexAuthAccountsPromise,
+): string | Promise {
+ const base = cacheKey(config);
+ if (!hasCodexPoolProvider(config)) return base;
+ return (async () => {
+ try {
+ const activeId = effectiveCodexAuthAccountId(config);
+ const rows = (await (prefetchedAccounts ?? listCodexAuthAccounts(config, false))).map(account => ({
+ isMain: account.isMain,
+ active: account.id === activeId,
+ plan: account.plan?.trim().toLowerCase() ?? null,
+ paused: account.paused,
+ needsReauth: account.needsReauth === true,
+ quota: quotaSignatureValue(account.quota as CodexCapacityQuota | null),
+ }));
+ const canonicalRows = rows.map(row => JSON.stringify(row)).sort();
+ const digest = createHash("sha256").update(JSON.stringify(canonicalRows)).digest("hex").slice(0, 24);
+ return `${base}|codex-pool:${digest}`;
+ } catch {
+ return `${base}|codex-pool:unavailable`;
+ }
+ })();
+}
+
+function publicCapacityWindow(window: import("./codex-capacity").CodexCapacityWindowAggregation) {
+ const { totalWeight: _totalWeight, consumedWeight: _consumedWeight, remainingWeight: _remainingWeight, ...safe } = window;
+ return safe;
+}
+
+/** Management API metadata intentionally omits configured/weighted unit counts. */
+function publicCapacityAggregation(
+ aggregation: CodexCapacityAggregation,
+ presentation: NonNullable,
+): CodexCapacityAggregation {
+ const safeCurrentAccount = presentation === "coverage-only" && aggregation.currentAccount
+ ? { ...aggregation.currentAccount, quota: null }
+ : aggregation.currentAccount;
+ return {
+ ...aggregation,
+ presentation,
+ ...(safeCurrentAccount ? { currentAccount: safeCurrentAccount } : {}),
+ ...(aggregation.fiveHour ? { fiveHour: publicCapacityWindow(aggregation.fiveHour) } : {}),
+ ...(aggregation.weekly ? { weekly: publicCapacityWindow(aggregation.weekly) } : {}),
+ ...(aggregation.monthly ? { monthly: publicCapacityWindow(aggregation.monthly) } : {}),
+ ...(aggregation.customWindows ? {
+ customWindows: aggregation.customWindows.map(window => ({
+ label: window.label,
+ ...publicCapacityWindow(window),
+ })),
+ } : {}),
+ };
}
function hasQuotaRows(quota: ProviderQuota | null | undefined): quota is ProviderQuota {
@@ -123,7 +213,12 @@ function isBuiltInChatGptForwardProvider(name: string, provider: OcxProviderConf
return name === OPENAI_CODEX_PROVIDER_ID && isCanonicalOpenAiForwardProvider(provider);
}
-function report(provider: string, source: string, quota: ProviderQuota): ProviderQuotaReport | null {
+function report(
+ provider: string,
+ source: string,
+ quota: ProviderQuota,
+ aggregation?: CodexCapacityAggregation,
+): ProviderQuotaReport | null {
if (!hasQuotaRows(quota)) return null;
return {
provider,
@@ -131,6 +226,7 @@ function report(provider: string, source: string, quota: ProviderQuota): Provide
source,
quota,
updatedAt: quota.updatedAt,
+ ...(aggregation ? { aggregation } : {}),
};
}
@@ -139,19 +235,59 @@ async function fetchChatGptForwardQuota(
provider: string,
providerConfig: OcxProviderConfig,
forceRefresh: boolean,
+ prefetchedAccounts?: CodexAuthAccountsPromise,
): Promise {
if (providerCodexAccountMode(provider, providerConfig) === "direct") {
const main = await fetchMainAccountInfo(forceRefresh);
const quota = main.quota ? { ...main.quota, updatedAt: Date.now() } as ProviderQuota : null;
return quota ? report(provider, "chatgpt:wham", quota) : null;
}
- const accounts = await listCodexAuthAccounts(config, forceRefresh);
- const activeId = config.activeCodexAccountId || MAIN_CODEX_ACCOUNT_ID;
- const active = accounts.find(account => account.id === activeId)
+ const accounts = await (prefetchedAccounts ?? listCodexAuthAccounts(config, forceRefresh));
+ const activeId = effectiveCodexAuthAccountId(config);
+ const capacityAccounts = accounts.map(account => ({ ...account, active: account.id === activeId }));
+ const active = capacityAccounts.find(account => account.active)
?? accounts.find(account => account.id === MAIN_CODEX_ACCOUNT_ID)
?? accounts[0];
- const quota = active?.quota ? { ...active.quota, updatedAt: active.quota.updatedAt ?? Date.now() } as ProviderQuota : null;
- return quota ? report(provider, "chatgpt:wham", quota) : null;
+ const now = Date.now();
+ const capacity = aggregateCodexPoolCapacity(capacityAccounts, now);
+ if (capacity.aggregation && capacity.quota) {
+ return report(
+ provider,
+ "chatgpt:wham",
+ capacity.quota as ProviderQuota,
+ publicCapacityAggregation(capacity.aggregation, "aggregate"),
+ );
+ }
+ const activeUsable = !!active && !active.paused && active.needsReauth !== true;
+ const quota = activeUsable && active?.quota
+ ? { ...active.quota, updatedAt: active.quota.updatedAt ?? Date.now() } as CodexCapacityQuota
+ : null;
+ const quotaFresh = !!quota
+ && Number.isFinite(quota.updatedAt)
+ && now - quota.updatedAt < CODEX_CAPACITY_MAX_QUOTA_AGE_MS;
+ if (quota && quotaFresh) {
+ const fallback = report(
+ provider,
+ "chatgpt:wham",
+ quota as ProviderQuota,
+ capacity.aggregation
+ ? publicCapacityAggregation(capacity.aggregation, "effective-account-fallback")
+ : undefined,
+ );
+ return fallback;
+ }
+ if (capacity.aggregation) {
+ const updatedAt = Date.now();
+ return {
+ provider,
+ label: providerLabel(provider),
+ source: "chatgpt:wham",
+ quota: { updatedAt },
+ updatedAt,
+ aggregation: publicCapacityAggregation(capacity.aggregation, "coverage-only"),
+ };
+ }
+ return null;
}
function centsValue(value: unknown): number | undefined {
@@ -371,6 +507,13 @@ export function reconcileProviderAccountQuotaRows(context: GenerationContext): n
return removed;
}
+/** Test-only reset so a direct reconcile call in one file cannot leak across files. */
+export function resetProviderQuotaReconcileStateForTests(): void {
+ lastReconciledGeneration = 0;
+ liveAccountQuotaKeys = new Set();
+ liveProviderQuotaKeys = new Set();
+}
+
/** Drop cached per-account rows (all, or just one provider's). */
export function clearAccountQuotaCache(provider?: string): void {
if (!provider) {
@@ -896,10 +1039,13 @@ async function maybeFetchProviderQuota(
provider: OcxProviderConfig,
config: OcxConfig,
forceRefresh: boolean,
+ prefetchedCodexAccounts?: CodexAuthAccountsPromise,
): Promise {
if (provider.disabled === true) return null;
try {
- if (isBuiltInChatGptForwardProvider(name, provider)) return fetchChatGptForwardQuota(config, name, provider, forceRefresh);
+ if (isBuiltInChatGptForwardProvider(name, provider)) {
+ return fetchChatGptForwardQuota(config, name, provider, forceRefresh, prefetchedCodexAccounts);
+ }
if (provider.authMode === "oauth" && name === "xai") return fetchXaiQuota(name);
if (provider.authMode === "oauth" && name === "anthropic") return fetchAnthropicQuota(name);
if (provider.authMode === "oauth" && name === "cursor") return fetchCursorQuota(name);
@@ -917,7 +1063,13 @@ async function maybeFetchProviderQuota(
}
export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh = false): Promise {
- const key = cacheKey(config);
+ // A forced Pool refresh must share one account-list probe between the pre-signature and
+ // provider fetch. The commit-time signature still re-reads current state to reject races.
+ const prefetchedCodexAccounts = forceRefresh && hasCodexPoolProvider(config)
+ ? listCodexAuthAccounts(config, true)
+ : undefined;
+ const keyCandidate = cacheKeyWithAggregationState(config, prefetchedCodexAccounts);
+ const key = typeof keyCandidate === "string" ? keyCandidate : await keyCandidate;
const writerGeneration = captureConfigGeneration();
const now = Date.now();
// The cache fast path must not extend a preserved last-good row past its 30-minute bound:
@@ -934,7 +1086,9 @@ export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh
const promise = (async (): Promise => {
const previous = cache && cache.key === key ? cache.response.reports : [];
const fresh = (await Promise.all(
- Object.entries(config.providers).map(([name, provider]) => maybeFetchProviderQuota(name, provider, config, forceRefresh)),
+ Object.entries(config.providers).map(([name, provider]) => (
+ maybeFetchProviderQuota(name, provider, config, forceRefresh, prefetchedCodexAccounts)
+ )),
)).filter((item): item is ProviderQuotaReport => item !== null);
// Keep bounded last-good rows when a probe fails (e.g. transient upstream flake); never
@@ -952,8 +1106,12 @@ export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh
const response = { generatedAt: Date.now(), reports: [...byProvider.values()] };
// Commit only when this probe still holds authority (no clear/force superseded it).
if (epoch === invalidationEpoch) {
- const reports = response.reports.filter(item => mayCommitProviderQuotaKey(item.provider, writerGeneration));
- cache = { key, ts: Date.now(), response: { ...response, reports } };
+ const commitKeyCandidate = cacheKeyWithAggregationState(config);
+ const commitKey = typeof commitKeyCandidate === "string" ? commitKeyCandidate : await commitKeyCandidate;
+ if (epoch === invalidationEpoch && commitKey === key) {
+ const reports = response.reports.filter(item => mayCommitProviderQuotaKey(item.provider, writerGeneration));
+ cache = { key, ts: Date.now(), response: { ...response, reports } };
+ }
}
return response;
})();
diff --git a/tests/provider-account-quota.test.ts b/tests/provider-account-quota.test.ts
index a8dafae677..634c663ca2 100644
--- a/tests/provider-account-quota.test.ts
+++ b/tests/provider-account-quota.test.ts
@@ -11,6 +11,7 @@ import {
fetchProviderQuotaReports,
getCachedProviderAccountQuota,
reconcileProviderAccountQuotaRows,
+ resetProviderQuotaReconcileStateForTests,
supportsPerAccountQuota,
} from "../src/providers/quota";
@@ -49,6 +50,7 @@ afterEach(() => {
rmSync(opencodexHome, { recursive: true, force: true });
clearAccountQuotaCache();
clearProviderQuotaCache();
+ resetProviderQuotaReconcileStateForTests();
});
describe("fetchProviderAccountQuotas", () => {
diff --git a/tests/provider-capacity.test.ts b/tests/provider-capacity.test.ts
new file mode 100644
index 0000000000..e45afb45b7
--- /dev/null
+++ b/tests/provider-capacity.test.ts
@@ -0,0 +1,220 @@
+import { describe, expect, test } from "bun:test";
+import { aggregateCodexPoolCapacity, CODEX_CAPACITY_MAX_QUOTA_AGE_MS, type CodexCapacityAccount } from "../src/providers/codex-capacity";
+
+const NOW = 1_800_000_000_000;
+const account = (
+ plan: string | null,
+ weeklyPercent: number | undefined,
+ options: Partial & { weeklyResetAt?: number; monthlyPercent?: number; monthlyResetAt?: number } = {},
+): CodexCapacityAccount => ({
+ isMain: false,
+ plan,
+ paused: false,
+ quota: weeklyPercent === undefined && options.monthlyPercent === undefined ? null : {
+ ...(weeklyPercent !== undefined ? { weeklyPercent } : {}),
+ ...(options.weeklyResetAt !== undefined ? { weeklyResetAt: options.weeklyResetAt } : {}),
+ ...(options.monthlyPercent !== undefined ? { monthlyPercent: options.monthlyPercent } : {}),
+ ...(options.monthlyResetAt !== undefined ? { monthlyResetAt: options.monthlyResetAt } : {}),
+ updatedAt: NOW,
+ },
+ ...options,
+});
+
+describe("configured-weight Codex pool capacity", () => {
+ test("Pro + Prolite + Plus produces the issue #874 estimate and recovery share", () => {
+ const result = aggregateCodexPoolCapacity([
+ account("pro", 10, { isMain: true, active: true, weeklyResetAt: NOW + 30_000 }),
+ account("prolite", 100, { weeklyResetAt: NOW + 10_000 }),
+ account("plus", 100, { weeklyResetAt: NOW + 20_000 }),
+ ], NOW);
+ expect(result.quota?.weeklyPercent).toBeCloseTo(30.769230769, 8);
+ expect(result.aggregation?.weekly).toMatchObject({ totalWeight: 26, consumedWeight: 8, remainingWeight: 18 });
+ expect(result.aggregation?.weekly?.nextRecoveryAt).toBe(NOW + 10_000);
+ expect(result.aggregation?.weekly?.nextRecoveryPercent).toBeCloseTo(19.23076923, 8);
+ expect(result.aggregation?.currentAccount?.quota?.weeklyPercent).toBe(10);
+ });
+
+ test("observed 8/100/100 and refreshed 9/100/100 remain exact weighted regressions", () => {
+ const rows = (mainPercent: number) => [
+ account("pro", mainPercent, { isMain: true, active: true }),
+ account("prolite", 100),
+ account("prolite", 100),
+ ];
+ expect(aggregateCodexPoolCapacity(rows(8), NOW).quota?.weeklyPercent).toBeCloseTo(38.66666667, 8);
+ expect(aggregateCodexPoolCapacity(rows(9), NOW).quota?.weeklyPercent).toBeCloseTo(39.33333333, 8);
+ });
+
+ test("same-time resets group partial consumed capacity and expose only recovery percent", () => {
+ const result = aggregateCodexPoolCapacity([
+ account("pro", 25, { weeklyResetAt: NOW + 10_000 }),
+ account("prolite", 40, { weeklyResetAt: NOW + 10_000 }),
+ account("plus", 100, { weeklyResetAt: NOW + 20_000 }),
+ ], NOW);
+ expect(result.aggregation?.weekly?.nextRecoveryPercent).toBeCloseTo(7 / 26 * 100, 8);
+ expect(result.aggregation?.weekly).not.toHaveProperty("projectedUsedPercentAfterReset");
+ });
+
+ test("the next recovery skips earlier zero-consumption resets", () => {
+ const result = aggregateCodexPoolCapacity([
+ account("pro", 0, { weeklyResetAt: NOW + 10_000 }),
+ account("prolite", 40, { weeklyResetAt: NOW + 20_000 }),
+ ], NOW);
+ expect(result.aggregation?.weekly?.nextRecoveryAt).toBe(NOW + 20_000);
+ expect(result.aggregation?.weekly?.nextRecoveryPercent).toBeCloseTo(2 / 25 * 100, 8);
+ });
+
+ test("unknown, missing, paused, and reauth rows are excluded with incomplete coverage", () => {
+ const result = aggregateCodexPoolCapacity([
+ account("pro", 10, { active: true, isMain: true }),
+ account("team", 50),
+ account("plus", undefined),
+ account("prolite", 20, { paused: true }),
+ account("business", 30, { needsReauth: true }),
+ ], NOW);
+ expect(result.aggregation).toMatchObject({
+ includedAccounts: 1,
+ excludedAccounts: 4,
+ unknownPlanAccounts: 1,
+ missingQuotaAccounts: 1,
+ pausedAccounts: 1,
+ reauthAccounts: 1,
+ incomplete: true,
+ });
+ expect(result.quota?.weeklyPercent).toBe(10);
+ });
+
+ test("weekly and monthly windows aggregate independently", () => {
+ const result = aggregateCodexPoolCapacity([
+ account("pro", 20),
+ account("prolite", undefined, { monthlyPercent: 60 }),
+ account("plus", 100, { monthlyPercent: 10 }),
+ ], NOW);
+ expect(result.aggregation?.weekly?.totalWeight).toBe(21);
+ expect(result.aggregation?.monthly?.totalWeight).toBe(6);
+ expect(result.aggregation).toMatchObject({
+ includedAccounts: 3,
+ excludedAccounts: 0,
+ partialWindowAccounts: 2,
+ incomplete: true,
+ });
+ expect(result.aggregation?.weekly).toMatchObject({ includedAccounts: 2, excludedAccounts: 1, incomplete: true });
+ expect(result.aggregation?.monthly).toMatchObject({ includedAccounts: 2, excludedAccounts: 1, incomplete: true });
+ expect(result.quota?.weeklyPercent).toBeCloseTo(23.8095238, 7);
+ expect(result.quota?.monthlyPercent).toBeCloseTo(51.6666667, 7);
+ });
+
+ test("expired resets are ignored and all-excluded pools retain effective-account fallback truth", () => {
+ const expired = aggregateCodexPoolCapacity([
+ account("pro", 10, { weeklyResetAt: NOW - 1, active: true, isMain: true }),
+ ], NOW);
+ expect(expired.aggregation?.weekly).not.toHaveProperty("nextRecoveryAt");
+ const fallback = aggregateCodexPoolCapacity([
+ account("team", 70, { active: true, isMain: true }),
+ account("go", 80),
+ ], NOW);
+ expect(fallback.aggregation).toMatchObject({
+ includedAccounts: 0,
+ excludedAccounts: 2,
+ unknownPlanAccounts: 2,
+ incomplete: true,
+ });
+ expect(fallback.currentAccount?.quota?.weeklyPercent).toBe(70);
+ });
+
+ test("mixed-age rows exclude stale capacity and use the oldest included reading", () => {
+ const oldestIncluded = account("plus", 40);
+ oldestIncluded.quota = { ...oldestIncluded.quota!, updatedAt: NOW - 20_000 };
+ const newerIncluded = account("business", 20);
+ newerIncluded.quota = { ...newerIncluded.quota!, updatedAt: NOW - 5_000 };
+ const staleHighWeight = account("pro", 100);
+ staleHighWeight.quota = {
+ ...staleHighWeight.quota!,
+ updatedAt: NOW - CODEX_CAPACITY_MAX_QUOTA_AGE_MS - 1,
+ };
+
+ const result = aggregateCodexPoolCapacity([oldestIncluded, newerIncluded, staleHighWeight], NOW);
+ expect(result.quota?.weeklyPercent).toBeCloseTo(30, 8);
+ expect(result.quota?.updatedAt).toBe(NOW - 20_000);
+ expect(result.aggregation?.weekly?.updatedAt).toBe(NOW - 20_000);
+ expect(result.aggregation).toMatchObject({
+ includedAccounts: 2,
+ excludedAccounts: 1,
+ staleQuotaAccounts: 1,
+ incomplete: true,
+ });
+ });
+
+ test("a stale effective secondary quota is hidden while a fresh main account still aggregates", () => {
+ const main = account("plus", 20, { isMain: true });
+ const staleActive = account("pro", 90, { active: true });
+ staleActive.quota = {
+ ...staleActive.quota!,
+ updatedAt: NOW - CODEX_CAPACITY_MAX_QUOTA_AGE_MS - 1,
+ };
+
+ const result = aggregateCodexPoolCapacity([main, staleActive], NOW);
+ expect(result.quota?.weeklyPercent).toBe(20);
+ expect(result.aggregation).toMatchObject({
+ includedAccounts: 1,
+ excludedAccounts: 1,
+ staleQuotaAccounts: 1,
+ currentAccount: { plan: "pro", quota: null },
+ });
+ });
+
+ test("prototype and unknown plan names never become configured weights", () => {
+ const result = aggregateCodexPoolCapacity([
+ account("plus", 20),
+ account("constructor", 100),
+ account("toString", 100),
+ account("valueOf", 100),
+ account("unknown", 100),
+ ], NOW);
+ expect(result.quota?.weeklyPercent).toBe(20);
+ expect(result.aggregation).toMatchObject({
+ includedAccounts: 1,
+ excludedAccounts: 4,
+ unknownPlanAccounts: 4,
+ });
+ expect(Number.isFinite(result.quota?.weeklyPercent)).toBe(true);
+ });
+
+ test("all-stale rows expose incomplete coverage without an aggregate window", () => {
+ const rows = [account("pro", 80, { active: true, isMain: true }), account("prolite", 20)];
+ for (const row of rows) {
+ row.quota = { ...row.quota!, updatedAt: NOW - CODEX_CAPACITY_MAX_QUOTA_AGE_MS - 1 };
+ }
+ const result = aggregateCodexPoolCapacity(rows, NOW);
+ expect(result.quota).toBeNull();
+ expect(result.aggregation).toMatchObject({
+ includedAccounts: 0,
+ excludedAccounts: 2,
+ staleQuotaAccounts: 2,
+ incomplete: true,
+ });
+ expect(result.aggregation?.weekly).toBeUndefined();
+ });
+
+ test("all exclusion reasons retain a coverage envelope without a quota window", () => {
+ const stale = account("pro", 90);
+ stale.quota = { ...stale.quota!, updatedAt: NOW - CODEX_CAPACITY_MAX_QUOTA_AGE_MS - 1 };
+ const result = aggregateCodexPoolCapacity([
+ account("team", 10, { active: true, isMain: true }),
+ account("plus", undefined),
+ account("prolite", 20, { paused: true }),
+ account("business", 30, { needsReauth: true }),
+ stale,
+ ], NOW);
+ expect(result.quota).toBeNull();
+ expect(result.aggregation).toMatchObject({
+ includedAccounts: 0,
+ excludedAccounts: 5,
+ unknownPlanAccounts: 1,
+ missingQuotaAccounts: 1,
+ pausedAccounts: 1,
+ reauthAccounts: 1,
+ staleQuotaAccounts: 1,
+ incomplete: true,
+ });
+ });
+});
diff --git a/tests/provider-quota.test.ts b/tests/provider-quota.test.ts
index 11e22aaa46..d1dd80c265 100644
--- a/tests/provider-quota.test.ts
+++ b/tests/provider-quota.test.ts
@@ -2,7 +2,10 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
-import { clearAccountQuota } from "../src/codex/quota";
+import { clearAccountQuota, updateAccountQuota } from "../src/codex/quota";
+import { clearAccountNeedsReauth, markAccountNeedsReauth } from "../src/codex/account-runtime-state";
+import { clearMainAccountInfoCache } from "../src/codex/auth-api";
+import { clearCodexUpstreamHealth } from "../src/codex/routing";
import { saveCodexAccountCredential } from "../src/codex/account-store";
import { saveCredential } from "../src/oauth/store";
import { clearProviderQuotaCache, fetchProviderQuotaReports } from "../src/providers/quota";
@@ -70,6 +73,7 @@ beforeEach(() => {
tokens: { access_token: "chatgpt-main-access", account_id: "chatgpt-main-account" },
}));
clearAccountQuota();
+ clearCodexUpstreamHealth();
clearProviderQuotaCache();
});
@@ -480,7 +484,7 @@ describe("fetchProviderQuotaReports", () => {
expect(expired.reports).toEqual([]);
});
- test("pool mode reports the active added account", async () => {
+ test("pool mode reports a weighted estimate while preserving the effective account raw quota", async () => {
saveCodexAccountCredential("added", {
accessToken: "added-access",
refreshToken: "added-refresh",
@@ -488,18 +492,214 @@ describe("fetchProviderQuotaReports", () => {
chatgptAccountId: "added-chatgpt-id",
});
const config = testConfig();
- config.codexAccounts = [{ id: "added", email: "a@example.test", isMain: false }];
+ config.codexAccounts = [{ id: "added", email: "a@example.test", plan: "prolite", isMain: false }];
config.activeCodexAccountId = "added";
globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => {
const headers = init?.headers as Record | undefined;
const percent = headers?.["ChatGPT-Account-Id"] === "added-chatgpt-id" ? 77 : 11;
return new Response(JSON.stringify({
+ plan_type: headers?.["ChatGPT-Account-Id"] === "added-chatgpt-id" ? "prolite" : "plus",
rate_limit: { secondary_window: { used_percent: percent, reset_at: 1_789_000_000 } },
}), { status: 200, headers: { "content-type": "application/json" } });
}) as typeof fetch;
const result = await fetchProviderQuotaReports(config, true);
- expect(result.reports.find(row => row.provider === "openai")?.quota.weeklyPercent).toBe(77);
+ const openai = result.reports.find(row => row.provider === "openai");
+ expect(openai?.quota.weeklyPercent).toBe(66);
+ expect(openai?.aggregation).toMatchObject({
+ includedAccounts: 2,
+ excludedAccounts: 0,
+ incomplete: false,
+ currentAccount: { plan: "prolite", quota: { weeklyPercent: 77 } },
+ });
+ expect(JSON.stringify(openai?.aggregation)).not.toMatch(/(?:total|consumed|remaining)Weight|projectedUsedPercent/i);
+ });
+
+ test("one forced Pool refresh probes each account once", async () => {
+ saveCodexAccountCredential("added", {
+ accessToken: "added-access", refreshToken: "added-refresh",
+ expiresAt: Date.now() + 3600_000, chatgptAccountId: "added-chatgpt-id",
+ });
+ const config = testConfig();
+ config.providers = { openai: config.providers.openai };
+ config.codexAccounts = [{ id: "added", email: "a@example.test", plan: "prolite", isMain: false }];
+ const calls = new Map();
+ globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => {
+ const accountId = (init?.headers as Record | undefined)?.["ChatGPT-Account-Id"] ?? "main";
+ calls.set(accountId, (calls.get(accountId) ?? 0) + 1);
+ return new Response(JSON.stringify({
+ plan_type: accountId === "added-chatgpt-id" ? "prolite" : "plus",
+ rate_limit: { secondary_window: { used_percent: 25, reset_at: 1_999_000_000 } },
+ }), { status: 200, headers: { "content-type": "application/json" } });
+ }) as typeof fetch;
+
+ await fetchProviderQuotaReports(config, true);
+
+ expect(calls).toEqual(new Map([["chatgpt-main-account", 1], ["added-chatgpt-id", 1]]));
+ });
+
+ test("all-excluded pool still returns a coverage-only OpenAI report", async () => {
+ rmSync(join(codexHome, "auth.json"), { force: true });
+ clearMainAccountInfoCache();
+ const config = testConfig();
+ config.providers = { openai: config.providers.openai };
+ config.codexAccounts = [{ id: "missing", email: "missing@example.test", plan: "plus", isMain: false }];
+
+ const result = await fetchProviderQuotaReports(config);
+ const openai = result.reports.find(row => row.provider === "openai");
+ expect(openai).toBeDefined();
+ expect(openai?.quota).toEqual({ updatedAt: openai?.updatedAt });
+ expect(openai?.aggregation).toMatchObject({
+ presentation: "coverage-only",
+ includedAccounts: 0,
+ excludedAccounts: 2,
+ reauthAccounts: 2,
+ incomplete: true,
+ });
+ });
+
+ test("stale effective-account quota becomes coverage-only and is never restamped as numeric fallback", async () => {
+ saveCodexAccountCredential("added", {
+ accessToken: "added-access", refreshToken: "added-refresh",
+ expiresAt: Date.now() + 3600_000, chatgptAccountId: "added-chatgpt-id",
+ });
+ const config = testConfig();
+ config.providers = { openai: config.providers.openai };
+ config.codexAccounts = [{ id: "added", email: "a@example.test", plan: "prolite", isMain: false }];
+ config.activeCodexAccountId = "added";
+ globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => {
+ const added = (init?.headers as Record | undefined)?.["ChatGPT-Account-Id"] === "added-chatgpt-id";
+ return new Response(JSON.stringify({
+ plan_type: added ? "prolite" : "plus",
+ rate_limit: { secondary_window: { used_percent: added ? 77 : 11, reset_at: 1_999_000_000 } },
+ }), { status: 200, headers: { "content-type": "application/json" } });
+ }) as typeof fetch;
+ await fetchProviderQuotaReports(config, true);
+ clearProviderQuotaCache();
+
+ const realDateNow = Date.now;
+ const future = realDateNow() + 31 * 60_000;
+ try {
+ Date.now = () => future;
+ globalThis.fetch = (async () => new Response("unavailable", { status: 500 })) as typeof fetch;
+ const expired = await fetchProviderQuotaReports(config);
+ const openai = expired.reports.find(row => row.provider === "openai");
+ expect(openai?.aggregation).toMatchObject({
+ presentation: "coverage-only",
+ includedAccounts: 0,
+ staleQuotaAccounts: 1,
+ missingQuotaAccounts: 1,
+ unknownPlanAccounts: 1,
+ incomplete: true,
+ currentAccount: { plan: "prolite", quota: null },
+ });
+ expect(openai?.quota).toEqual({ updatedAt: future });
+ expect(openai?.quota).not.toHaveProperty("weeklyPercent");
+ const cached = await fetchProviderQuotaReports(config);
+ expect(cached.reports[0]?.quota).not.toHaveProperty("weeklyPercent");
+ } finally {
+ Date.now = realDateNow;
+ }
+ });
+
+ test("ordinary fetch reflects pausing a non-active pool account", async () => {
+ saveCodexAccountCredential("added", {
+ accessToken: "added-access", refreshToken: "added-refresh",
+ expiresAt: Date.now() + 3600_000, chatgptAccountId: "added-chatgpt-id",
+ });
+ const config = testConfig();
+ config.providers = { openai: config.providers.openai };
+ config.codexAccounts = [{ id: "added", email: "a@example.test", plan: "prolite", isMain: false }];
+ globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => {
+ const added = (init?.headers as Record | undefined)?.["ChatGPT-Account-Id"] === "added-chatgpt-id";
+ return new Response(JSON.stringify({
+ plan_type: added ? "prolite" : "plus",
+ rate_limit: { secondary_window: { used_percent: added ? 77 : 11, reset_at: 1_999_000_000 } },
+ }), { status: 200, headers: { "content-type": "application/json" } });
+ }) as typeof fetch;
+
+ expect((await fetchProviderQuotaReports(config, true)).reports[0]?.quota.weeklyPercent).toBe(66);
+ config.pausedCodexAccountIds = ["added"];
+ const paused = (await fetchProviderQuotaReports(config)).reports[0];
+ expect(paused?.quota.weeklyPercent).toBe(11);
+ expect(paused?.aggregation).toMatchObject({ includedAccounts: 1, excludedAccounts: 1, incomplete: true });
+ });
+
+ test("ordinary fetch separates plan, quota, and effective-account cache states", async () => {
+ saveCodexAccountCredential("added", {
+ accessToken: "added-access", refreshToken: "added-refresh",
+ expiresAt: Date.now() + 3600_000, chatgptAccountId: "added-chatgpt-id",
+ });
+ const config = testConfig();
+ config.providers = { openai: config.providers.openai };
+ config.codexAccounts = [{ id: "added", email: "a@example.test", plan: "prolite", isMain: false }];
+ globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => {
+ const added = (init?.headers as Record | undefined)?.["ChatGPT-Account-Id"] === "added-chatgpt-id";
+ return new Response(JSON.stringify({
+ plan_type: added ? "prolite" : "plus",
+ rate_limit: { secondary_window: { used_percent: added ? 77 : 11, reset_at: 1_999_000_000 } },
+ }), { status: 200, headers: { "content-type": "application/json" } });
+ }) as typeof fetch;
+
+ await fetchProviderQuotaReports(config, true);
+ config.codexAccounts[0]!.plan = "pro";
+ expect((await fetchProviderQuotaReports(config)).reports[0]?.aggregation?.weekly?.usedPercent).toBeCloseTo((20 * 77 + 11) / 21, 8);
+ config.activeCodexAccountId = "added";
+ expect((await fetchProviderQuotaReports(config)).reports[0]?.aggregation?.currentAccount).toMatchObject({ plan: "pro", quota: { weeklyPercent: 77 } });
+ updateAccountQuota("added", 20, 1_999_000_000);
+ expect((await fetchProviderQuotaReports(config)).reports[0]?.aggregation?.weekly?.usedPercent).toBeCloseTo((20 * 20 + 11) / 21, 8);
+ });
+
+ test("ordinary fetch reflects runtime reauthentication state", async () => {
+ saveCodexAccountCredential("added", {
+ accessToken: "added-access", refreshToken: "added-refresh",
+ expiresAt: Date.now() + 3600_000, chatgptAccountId: "added-chatgpt-id",
+ });
+ const config = testConfig();
+ config.providers = { openai: config.providers.openai };
+ config.codexAccounts = [{ id: "added", email: "a@example.test", plan: "prolite", isMain: false }];
+ globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => {
+ const added = (init?.headers as Record | undefined)?.["ChatGPT-Account-Id"] === "added-chatgpt-id";
+ return new Response(JSON.stringify({
+ plan_type: added ? "prolite" : "plus",
+ rate_limit: { secondary_window: { used_percent: added ? 77 : 11, reset_at: 1_999_000_000 } },
+ }), { status: 200, headers: { "content-type": "application/json" } });
+ }) as typeof fetch;
+
+ await fetchProviderQuotaReports(config, true);
+ markAccountNeedsReauth("added");
+ const reauth = (await fetchProviderQuotaReports(config)).reports[0];
+ expect(reauth?.aggregation).toMatchObject({ includedAccounts: 1, reauthAccounts: 1, incomplete: true });
+ clearAccountNeedsReauth("added");
+ });
+
+ test("ordinary fetch reflects pool account add and remove", async () => {
+ saveCodexAccountCredential("added", {
+ accessToken: "added-access", refreshToken: "added-refresh",
+ expiresAt: Date.now() + 3600_000, chatgptAccountId: "added-chatgpt-id",
+ });
+ const config = testConfig();
+ config.providers = { openai: config.providers.openai };
+ config.codexAccounts = [{ id: "added", email: "a@example.test", plan: "prolite", isMain: false }];
+ globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => {
+ const id = (init?.headers as Record | undefined)?.["ChatGPT-Account-Id"];
+ const plan = id === "added-chatgpt-id" ? "prolite" : id === "second-chatgpt-id" ? "business" : "plus";
+ const percent = id === "added-chatgpt-id" ? 77 : id === "second-chatgpt-id" ? 33 : 11;
+ return new Response(JSON.stringify({
+ plan_type: plan,
+ rate_limit: { secondary_window: { used_percent: percent, reset_at: 1_999_000_000 } },
+ }), { status: 200, headers: { "content-type": "application/json" } });
+ }) as typeof fetch;
+
+ expect((await fetchProviderQuotaReports(config, true)).reports[0]?.aggregation?.includedAccounts).toBe(2);
+ saveCodexAccountCredential("second", {
+ accessToken: "second-access", refreshToken: "second-refresh",
+ expiresAt: Date.now() + 3600_000, chatgptAccountId: "second-chatgpt-id",
+ });
+ config.codexAccounts.push({ id: "second", email: "b@example.test", plan: "business", isMain: false });
+ expect((await fetchProviderQuotaReports(config)).reports[0]?.aggregation?.includedAccounts).toBe(3);
+ config.codexAccounts = config.codexAccounts.filter(account => account.id !== "second");
+ expect((await fetchProviderQuotaReports(config)).reports[0]?.aggregation?.includedAccounts).toBe(2);
});
test("direct mode reports main without reading or repairing the added-account store", async () => {
@@ -747,6 +947,49 @@ describe("fetchProviderQuotaReports", () => {
expect(cached.reports[0]?.quota.monthlyPercent).toBe(90);
});
+ test("effective-account change during a pool probe cannot cache under the new signature", async () => {
+ saveCodexAccountCredential("added", {
+ accessToken: "added-access", refreshToken: "added-refresh",
+ expiresAt: Date.now() + 3600_000, chatgptAccountId: "added-chatgpt-id",
+ });
+ const config = testConfig();
+ config.providers = { openai: config.providers.openai };
+ config.codexAccounts = [{ id: "added", email: "a@example.test", plan: "prolite", isMain: false }];
+ const responseFor = (init?: RequestInit) => {
+ const added = (init?.headers as Record | undefined)?.["ChatGPT-Account-Id"] === "added-chatgpt-id";
+ return new Response(JSON.stringify({
+ plan_type: added ? "prolite" : "plus",
+ rate_limit: { secondary_window: { used_percent: added ? 77 : 11, reset_at: 1_999_000_000 } },
+ }), { status: 200, headers: { "content-type": "application/json" } });
+ };
+ globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => responseFor(init)) as typeof fetch;
+ await fetchProviderQuotaReports(config, true);
+ clearProviderQuotaCache();
+
+ let release!: () => void;
+ const gate = new Promise(resolve => { release = resolve; });
+ let startedResolve!: () => void;
+ const started = new Promise(resolve => { startedResolve = resolve; });
+ let startedProbe = false;
+ globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => {
+ if (!startedProbe) {
+ startedProbe = true;
+ startedResolve();
+ }
+ await gate;
+ return responseFor(init);
+ }) as typeof fetch;
+
+ const racing = fetchProviderQuotaReports(config, true);
+ await started;
+ config.activeCodexAccountId = "added";
+ release();
+ const racedResponse = await racing;
+ const next = await fetchProviderQuotaReports(config);
+ expect(next).not.toBe(racedResponse);
+ expect(next.reports[0]?.aggregation?.currentAccount).toMatchObject({ plan: "prolite", quota: { weeklyPercent: 77 } });
+ });
+
test("last-good rows survive a transient failure with original timestamps, are replaced by fresh rows, expire past the cap, and a disabled provider yields no rows", async () => {
await saveCredential("cursor", { access: "cursor-access-secret", refresh: "cursor-refresh-secret", expires: Date.now() + 3600_000 });
let mode: "ok" | "fail" = "ok";