fix: scan all Codex limits for max 5h/7d, persist d7 reset times

- Iterate all rate limits per account instead of just display[0]
- Track and persist 7-day reset time separately (resetD7At)
- Show both 5h and 7d cached usage for inactive Codex accounts
This commit is contained in:
Eyejoker
2026-03-24 01:28:48 +09:00
parent c7a3a56386
commit 9e091df6f6
2 changed files with 61 additions and 21 deletions

View File

@@ -29,9 +29,13 @@ interface CodexAccount {
lastUsagePct?: number;
lastUsageD7Pct?: number;
resetAt?: string;
resetD7At?: string;
}
function parseJwtAuth(idToken: string): { planType: string; expiresAt: string | null } {
function parseJwtAuth(idToken: string): {
planType: string;
expiresAt: string | null;
} {
try {
const parts = idToken.split('.');
if (parts.length < 2) return { planType: '?', expiresAt: null };
@@ -108,6 +112,7 @@ function saveCodexState(): void {
usagePcts: accounts.map((a) => a.lastUsagePct ?? null),
usageD7Pcts: accounts.map((a) => a.lastUsageD7Pct ?? null),
resetAts: accounts.map((a) => a.resetAt ?? null),
resetD7Ats: accounts.map((a) => a.resetD7At ?? null),
};
fs.writeFileSync(STATE_FILE, JSON.stringify(state));
} catch {
@@ -149,19 +154,25 @@ function loadCodexState(): void {
}
}
if (Array.isArray(state.usageD7Pcts)) {
for (let i = 0; i < Math.min(state.usageD7Pcts.length, accounts.length); i++) {
if (typeof state.usageD7Pcts[i] === 'number') accounts[i].lastUsageD7Pct = state.usageD7Pcts[i];
for (
let i = 0;
i < Math.min(state.usageD7Pcts.length, accounts.length);
i++
) {
if (typeof state.usageD7Pcts[i] === 'number')
accounts[i].lastUsageD7Pct = state.usageD7Pcts[i];
}
}
if (Array.isArray(state.resetAts)) {
for (
let i = 0;
i < Math.min(state.resetAts.length, accounts.length);
i++
) {
for (let i = 0; i < Math.min(state.resetAts.length, accounts.length); i++) {
if (state.resetAts[i]) accounts[i].resetAt = state.resetAts[i];
}
}
if (Array.isArray(state.resetD7Ats)) {
for (let i = 0; i < Math.min(state.resetD7Ats.length, accounts.length); i++) {
if (state.resetD7Ats[i]) accounts[i].resetD7At = state.resetD7Ats[i];
}
}
logger.info(
{ currentIndex, accountCount: accounts.length },
'Codex rotation state restored',
@@ -275,6 +286,7 @@ export function updateCodexAccountUsage(
resetAt?: string,
accountIndex?: number,
d7Pct?: number,
resetD7At?: string,
): void {
if (accounts.length === 0) return;
const idx = accountIndex ?? currentIndex;
@@ -283,6 +295,7 @@ export function updateCodexAccountUsage(
acct.lastUsagePct = usagePct;
if (d7Pct != null) acct.lastUsageD7Pct = d7Pct;
if (resetAt) acct.resetAt = resetAt;
if (resetD7At) acct.resetD7At = resetD7At;
saveCodexState();
}
}
@@ -309,6 +322,7 @@ export function getAllCodexAccounts(): {
cachedUsagePct?: number;
cachedUsageD7Pct?: number;
resetAt?: string;
resetD7At?: string;
}[] {
const now = Date.now();
return accounts.map((a, i) => ({
@@ -320,5 +334,6 @@ export function getAllCodexAccounts(): {
cachedUsagePct: a.lastUsagePct,
cachedUsageD7Pct: a.lastUsageD7Pct,
resetAt: a.resetAt,
resetD7At: a.resetD7At,
}));
}

View File

@@ -646,17 +646,17 @@ async function buildUsageContent(): Promise<string> {
}
} else {
// Show cached usage from last scan
const pct =
acct.cachedUsagePct != null ? acct.cachedUsagePct : -1;
const pct = acct.cachedUsagePct != null ? acct.cachedUsagePct : -1;
const d7pct =
acct.cachedUsageD7Pct != null ? acct.cachedUsageD7Pct : -1;
const reset = acct.resetAt || '';
const d7reset = acct.resetD7At || '';
rows.push({
name: `${label} ${acct.planType}`,
h5pct: pct,
h5reset: reset,
d7pct,
d7reset: '',
d7reset,
});
}
}
@@ -794,20 +794,42 @@ async function refreshAllCodexAccountUsage(): Promise<void> {
(l) => l.primary.usedPercent > 0 || l.secondary.usedPercent > 0,
);
const display = relevant.length > 0 ? relevant : usage.slice(0, 1);
if (display.length > 0) {
const pct = Math.round(display[0].primary.usedPercent);
const resetVal = display[0].primary.resetsAt;
const resetStr = resetVal ? formatResetRemaining(resetVal) : undefined;
const d7Pct = Math.round(display[0].secondary.usedPercent);
updateCodexAccountUsage(pct, resetStr, acct.index, d7Pct);
if (usage.length > 0) {
// Find max primary (5h) and secondary (7d) across all limits
let maxH5 = 0;
let maxD7 = 0;
let h5Reset: string | number | undefined;
let d7Reset: string | number | undefined;
for (const limit of usage) {
if (limit.primary.usedPercent > maxH5) {
maxH5 = limit.primary.usedPercent;
h5Reset = limit.primary.resetsAt;
}
if (limit.secondary.usedPercent > maxD7) {
maxD7 = limit.secondary.usedPercent;
d7Reset = limit.secondary.resetsAt;
}
}
const pct = Math.round(maxH5);
const d7Pct = Math.round(maxD7);
const resetStr = h5Reset
? formatResetRemaining(h5Reset)
: undefined;
const resetD7Str = d7Reset
? formatResetRemaining(d7Reset)
: undefined;
updateCodexAccountUsage(pct, resetStr, acct.index, d7Pct, resetD7Str);
logger.info(
{ account: acct.index + 1, usagePct: pct, reset: resetStr },
`Codex account #${acct.index + 1} usage: ${pct}%`,
{ account: acct.index + 1, h5: pct, d7: d7Pct, reset: resetStr },
`Codex account #${acct.index + 1} usage: 5h=${pct}% 7d=${d7Pct}%`,
);
}
}
} catch (err) {
logger.debug({ err, account: acct.index + 1 }, 'Failed to fetch usage for Codex account');
logger.debug(
{ err, account: acct.index + 1 },
'Failed to fetch usage for Codex account',
);
}
}
}
@@ -881,7 +903,10 @@ export async function startUnifiedDashboard(
setInterval(refreshUsageCache, opts.usageUpdateInterval);
// Full scan of all Codex accounts on startup + hourly
void refreshAllCodexAccountUsage();
setInterval(() => void refreshAllCodexAccountUsage(), CODEX_FULL_SCAN_INTERVAL);
setInterval(
() => void refreshAllCodexAccountUsage(),
CODEX_FULL_SCAN_INTERVAL,
);
}
logger.info(