fix(usage): render codex usage when rate-limit response has null secondary window
Some Codex plans (e.g. Plus) return only a weekly window in `primary` with `secondary: null`. applyCodexUsageToAccount dereferenced `secondary.usedPercent` unconditionally, throwing a TypeError that refreshActiveCodexUsage swallowed at debug level, so usage never applied and the dashboard row stayed blank (-1). Guard the null secondary, and when a lone window is weekly (windowDurationMins > 1440) route it into the 7d slot leaving 5h unknown. Accounts that report both windows are unchanged. Adds a regression test for the secondary:null case.
This commit is contained in:
@@ -14,8 +14,8 @@ import { logger } from './logger.js';
|
||||
export interface CodexRateLimit {
|
||||
limitId?: string;
|
||||
limitName: string | null;
|
||||
primary: { usedPercent: number; resetsAt: string | number };
|
||||
secondary: { usedPercent: number; resetsAt: string | number };
|
||||
primary: { usedPercent: number; resetsAt: string | number; windowDurationMins?: number };
|
||||
secondary: { usedPercent: number; resetsAt: string | number; windowDurationMins?: number } | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,8 +291,8 @@ export function applyCodexUsageToAccount(
|
||||
account: accountIndex + 1,
|
||||
buckets: usage.map((l) => ({
|
||||
id: l.limitId,
|
||||
h5: l.primary.usedPercent,
|
||||
d7: l.secondary.usedPercent,
|
||||
h5: l.primary?.usedPercent ?? null,
|
||||
d7: l.secondary?.usedPercent ?? null,
|
||||
})),
|
||||
},
|
||||
`Codex account #${accountIndex + 1}: ${usage.length} rate-limit bucket(s)`,
|
||||
@@ -312,13 +312,31 @@ export function applyCodexUsageToAccount(
|
||||
return;
|
||||
}
|
||||
|
||||
const pct = Math.round(effective.primary.usedPercent);
|
||||
const d7Pct = Math.round(effective.secondary.usedPercent);
|
||||
// Historical shape: primary = 5h window, secondary = 7d window. Some plans
|
||||
// (e.g. Plus) return only ONE window in `primary` (a weekly/7d window) with
|
||||
// `secondary: null`. Guard the null so we don't throw, and route a lone
|
||||
// weekly window into the 7d slot so usage still renders.
|
||||
// Store raw ISO timestamps (not pre-formatted strings) so the exhaustion
|
||||
// gate can compute "minutes until reset" later. The dashboard render path
|
||||
// formats these at display time via `formatResetRemaining`.
|
||||
const resetIso = toIsoMaybe(effective.primary.resetsAt);
|
||||
const resetD7Iso = toIsoMaybe(effective.secondary.resetsAt);
|
||||
let pct = Math.round(effective.primary.usedPercent);
|
||||
let resetIso = toIsoMaybe(effective.primary.resetsAt);
|
||||
let d7Pct = effective.secondary
|
||||
? Math.round(effective.secondary.usedPercent)
|
||||
: -1;
|
||||
let resetD7Iso = effective.secondary
|
||||
? toIsoMaybe(effective.secondary.resetsAt)
|
||||
: undefined;
|
||||
if (
|
||||
!effective.secondary &&
|
||||
(effective.primary.windowDurationMins ?? 0) > 1440
|
||||
) {
|
||||
// Lone window is weekly (> 1 day) — show it as 7d, leave 5h unknown.
|
||||
d7Pct = pct;
|
||||
resetD7Iso = resetIso;
|
||||
pct = -1;
|
||||
resetIso = undefined;
|
||||
}
|
||||
updateCodexAccountUsage(pct, resetIso, accountIndex, d7Pct, resetD7Iso);
|
||||
logger.info(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user