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

View File

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