Merge pull request #1113 from yousiki/fix/codex-additional-rate-limits

fix(cliproxy): parse Codex additional_rate_limits for Spark quota
This commit is contained in:
Kai (Tam Nhu) Tran
2026-04-28 13:04:16 -04:00
committed by GitHub
9 changed files with 805 additions and 68 deletions
+104 -24
View File
@@ -39,8 +39,10 @@ interface CodexUsageResponse {
planType?: string;
rate_limit?: CodexRateLimitWindow;
rateLimit?: CodexRateLimitWindow;
code_review_rate_limit?: CodexRateLimitWindow;
codeReviewRateLimit?: CodexRateLimitWindow;
code_review_rate_limit?: CodexRateLimitWindow | null;
codeReviewRateLimit?: CodexRateLimitWindow | null;
additional_rate_limits?: CodexAdditionalRateLimit[] | null;
additionalRateLimits?: CodexAdditionalRateLimit[] | null;
}
/** Rate limit window from API */
@@ -51,6 +53,19 @@ interface CodexRateLimitWindow {
secondaryWindow?: CodexWindowData;
}
/**
* Additional rate limit entry from API (introduced for features like GPT-5.3 Codex Spark).
* Each entry surfaces its own primary/secondary windows under a feature-specific limit name.
*/
interface CodexAdditionalRateLimit {
limit_name?: string;
limitName?: string;
metered_feature?: string;
meteredFeature?: string;
rate_limit?: CodexRateLimitWindow;
rateLimit?: CodexRateLimitWindow;
}
/** Individual window data */
interface CodexWindowData {
used_percent?: number;
@@ -92,7 +107,11 @@ function getCodexWindowKind(label: string): CodexWindowKind {
function getUnknownCodexWindowLabels(windows: CodexQuotaWindow[]): string[] {
const unknownLabels = windows
.filter((window) => getCodexWindowKind(window.label) === 'unknown')
.filter((window) => {
// Windows with explicit category metadata are always classified.
if (window.category) return false;
return getCodexWindowKind(window.label) === 'unknown';
})
.map((window) => window.label)
.filter((label): label is string => typeof label === 'string' && label.trim().length > 0);
return Array.from(new Set(unknownLabels));
@@ -106,7 +125,8 @@ function shouldLogCodexWindowWarnings(verbose: boolean): boolean {
/**
* Build explicit 5h + weekly usage summary from raw Codex windows.
* Falls back to shortest/longest reset windows if API labels change.
* Prefers explicit `category`/`cadence` metadata when present.
* Falls back to label sniffing for legacy cached windows.
*/
export function buildCodexCoreUsageSummary(windows: CodexQuotaWindow[]): CodexCoreUsageSummary {
if (!windows || windows.length === 0) {
@@ -117,20 +137,36 @@ export function buildCodexCoreUsageSummary(windows: CodexQuotaWindow[]): CodexCo
let weeklyWindow: CodexQuotaWindow | null = null;
const nonCodeReviewWindows: CodexQuotaWindow[] = [];
for (const window of windows) {
const kind = getCodexWindowKind(window.label);
if (kind === 'usage-5h') {
if (!fiveHourWindow) fiveHourWindow = window;
nonCodeReviewWindows.push(window);
continue;
// Determine if any window carries category metadata. If so, prefer category-based
// selection so 'additional' windows (e.g. Spark) do not pollute the main usage summary.
const hasCategoryMetadata = windows.some((window) => Boolean(window.category));
if (hasCategoryMetadata) {
for (const window of windows) {
if (window.category === 'usage') {
if (window.cadence === '5h' && !fiveHourWindow) fiveHourWindow = window;
else if (window.cadence === 'weekly' && !weeklyWindow) weeklyWindow = window;
nonCodeReviewWindows.push(window);
}
// 'code-review' and 'additional' windows are intentionally excluded from the main
// usage summary — they represent feature-specific quotas, not core usage.
}
if (kind === 'usage-weekly') {
if (!weeklyWindow) weeklyWindow = window;
nonCodeReviewWindows.push(window);
continue;
}
if (kind === 'unknown') {
nonCodeReviewWindows.push(window);
} else {
for (const window of windows) {
const kind = getCodexWindowKind(window.label);
if (kind === 'usage-5h') {
if (!fiveHourWindow) fiveHourWindow = window;
nonCodeReviewWindows.push(window);
continue;
}
if (kind === 'usage-weekly') {
if (!weeklyWindow) weeklyWindow = window;
nonCodeReviewWindows.push(window);
continue;
}
if (kind === 'unknown') {
nonCodeReviewWindows.push(window);
}
}
}
@@ -270,9 +306,18 @@ function buildCodexQuotaWindows(payload: CodexUsageResponse): CodexQuotaWindow[]
// Get rate limit object (handles both cases)
const rateLimit = payload.rate_limit || payload.rateLimit;
const codeReviewRateLimit = payload.code_review_rate_limit || payload.codeReviewRateLimit;
const additionalRateLimits = payload.additional_rate_limits || payload.additionalRateLimits;
// Helper to extract window data
const addWindow = (label: string, windowData: CodexWindowData | undefined): void => {
const addWindow = (
label: string,
windowData: CodexWindowData | undefined,
meta: {
category: NonNullable<CodexQuotaWindow['category']>;
cadence: NonNullable<CodexQuotaWindow['cadence']>;
featureLabel?: string;
}
): void => {
if (!windowData) return;
// Clamp usedPercent to [0, 100] range
@@ -287,33 +332,68 @@ function buildCodexQuotaWindows(payload: CodexUsageResponse): CodexQuotaWindow[]
resetAt = new Date(Date.now() + resetAfterSeconds * 1000).toISOString();
}
windows.push({
const window: CodexQuotaWindow = {
label,
usedPercent,
remainingPercent: Math.max(0, 100 - usedPercent),
resetAfterSeconds,
resetAt,
});
category: meta.category,
cadence: meta.cadence,
};
if (meta.featureLabel) {
window.featureLabel = meta.featureLabel;
}
windows.push(window);
};
// Add main rate limit windows
if (rateLimit) {
addWindow('Primary', rateLimit.primary_window || rateLimit.primaryWindow);
addWindow('Secondary', rateLimit.secondary_window || rateLimit.secondaryWindow);
addWindow('Primary', rateLimit.primary_window || rateLimit.primaryWindow, {
category: 'usage',
cadence: '5h',
});
addWindow('Secondary', rateLimit.secondary_window || rateLimit.secondaryWindow, {
category: 'usage',
cadence: 'weekly',
});
}
// Add code review rate limit windows
if (codeReviewRateLimit) {
addWindow(
'Code Review (Primary)',
codeReviewRateLimit.primary_window || codeReviewRateLimit.primaryWindow
codeReviewRateLimit.primary_window || codeReviewRateLimit.primaryWindow,
{ category: 'code-review', cadence: '5h', featureLabel: 'Code Review' }
);
addWindow(
'Code Review (Secondary)',
codeReviewRateLimit.secondary_window || codeReviewRateLimit.secondaryWindow
codeReviewRateLimit.secondary_window || codeReviewRateLimit.secondaryWindow,
{ category: 'code-review', cadence: 'weekly', featureLabel: 'Code Review' }
);
}
// Add additional rate limit windows (e.g. GPT-5.3 Codex Spark)
if (Array.isArray(additionalRateLimits)) {
for (const entry of additionalRateLimits) {
if (!entry) continue;
const entryRateLimit = entry.rate_limit || entry.rateLimit;
if (!entryRateLimit) continue;
const featureLabel = entry.limit_name || entry.limitName || 'Additional';
addWindow(
`${featureLabel} (Primary)`,
entryRateLimit.primary_window || entryRateLimit.primaryWindow,
{ category: 'additional', cadence: '5h', featureLabel }
);
addWindow(
`${featureLabel} (Secondary)`,
entryRateLimit.secondary_window || entryRateLimit.secondaryWindow,
{ category: 'additional', cadence: 'weekly', featureLabel }
);
}
}
return windows;
}
+14 -2
View File
@@ -29,10 +29,10 @@ export interface QuotaErrorMetadata {
}
/**
* Codex quota window (primary, secondary, code review)
* Codex quota window (primary, secondary, code review, additional)
*/
export interface CodexQuotaWindow {
/** Window label: "Primary", "Secondary", "Code Review (Primary)", "Code Review (Secondary)" */
/** Window label: "Primary", "Secondary", "Code Review (Primary)", "Code Review (Secondary)", or "<feature> (Primary|Secondary)" */
label: string;
/** Percentage used (0-100) */
usedPercent: number;
@@ -42,6 +42,18 @@ export interface CodexQuotaWindow {
resetAfterSeconds: number | null;
/** ISO timestamp when quota resets, null if unknown */
resetAt: string | null;
/**
* Window category indicating the bucket this window belongs to.
* Optional for back-compat with cached data emitted before this field existed.
* - 'usage' -> standard rate_limit usage windows
* - 'code-review' -> code_review_rate_limit windows
* - 'additional' -> additional_rate_limits[] windows (e.g. GPT-5.3 Codex Spark)
*/
category?: 'usage' | 'code-review' | 'additional';
/** Cadence of the window: '5h' = primary, 'weekly' = secondary. Optional for legacy data. */
cadence?: '5h' | 'weekly';
/** Raw upstream label (e.g. 'GPT-5.3-Codex-Spark', 'Code Review'); absent for plain usage windows. */
featureLabel?: string;
}
/** Core Codex usage window (5h/weekly) extracted from raw windows */
+66 -14
View File
@@ -238,7 +238,10 @@ function getCodexWindowKind(label: string): CodexWindowKind {
return 'unknown';
}
type CodexWindowSummary = Pick<CodexQuotaResult['windows'][number], 'label' | 'resetAfterSeconds'>;
type CodexWindowSummary = Pick<
CodexQuotaResult['windows'][number],
'label' | 'resetAfterSeconds' | 'category' | 'cadence' | 'featureLabel'
>;
function inferCodeReviewCadence(
window: CodexWindowSummary,
@@ -272,12 +275,46 @@ function inferCodeReviewCadence(
return diffToWeekly <= diffTo5h ? 'weekly' : '5h';
}
/**
* Strip a leading "GPT-X.Y-Codex-" prefix from a feature label and turn the
* remainder into a Codex-prefixed display name. Other labels pass through unchanged.
*/
function prettifyCodexFeatureLabel(featureLabel: string): string {
const trimmed = featureLabel.trim();
if (!trimmed) return 'Additional';
const stripped = trimmed.replace(/^GPT-[\d.]+-Codex-/i, '');
if (stripped !== trimmed && stripped.length > 0) {
return `Codex ${stripped}`;
}
return trimmed;
}
function getCodexWindowDisplayLabel(
window: CodexWindowSummary,
allWindows: CodexWindowSummary[] = []
): string {
const context = allWindows.length > 0 ? allWindows : [window];
// Prefer explicit category metadata when present (post-2026-04 windows).
if (window.category === 'usage') {
if (window.cadence === '5h') return '5h usage limit';
if (window.cadence === 'weekly') return 'Weekly usage limit';
}
if (window.category === 'additional') {
const pretty = prettifyCodexFeatureLabel(window.featureLabel || window.label || 'Additional');
if (window.cadence === '5h') return `${pretty} (5h)`;
if (window.cadence === 'weekly') return `${pretty} (weekly)`;
return pretty;
}
if (window.category === 'code-review') {
if (window.cadence === '5h') return 'Code review (5h)';
if (window.cadence === 'weekly') return 'Code review (weekly)';
return 'Code review';
}
// Legacy fallback: classify via label sniffing for cached windows without metadata.
switch (getCodexWindowKind(window.label)) {
case 'usage-5h':
return '5h usage limit';
@@ -304,20 +341,35 @@ function getCodexCoreUsageWindows(windows: CodexQuotaResult['windows']): {
let weeklyWindow: CodexQuotaResult['windows'][number] | null = null;
const nonCodeReviewWindows: CodexQuotaResult['windows'] = [];
for (const window of windows) {
const kind = getCodexWindowKind(window.label);
if (kind === 'usage-5h') {
if (!fiveHourWindow) fiveHourWindow = window;
nonCodeReviewWindows.push(window);
continue;
// Prefer explicit category metadata when present so 'additional' windows
// (e.g. GPT-5.3 Codex Spark) do not displace core usage windows in the summary.
const hasCategoryMetadata = windows.some((window) => Boolean(window.category));
if (hasCategoryMetadata) {
for (const window of windows) {
if (window.category === 'usage') {
if (window.cadence === '5h' && !fiveHourWindow) fiveHourWindow = window;
else if (window.cadence === 'weekly' && !weeklyWindow) weeklyWindow = window;
nonCodeReviewWindows.push(window);
}
// 'code-review' and 'additional' are excluded from the core usage summary.
}
if (kind === 'usage-weekly') {
if (!weeklyWindow) weeklyWindow = window;
nonCodeReviewWindows.push(window);
continue;
}
if (kind === 'unknown') {
nonCodeReviewWindows.push(window);
} else {
for (const window of windows) {
const kind = getCodexWindowKind(window.label);
if (kind === 'usage-5h') {
if (!fiveHourWindow) fiveHourWindow = window;
nonCodeReviewWindows.push(window);
continue;
}
if (kind === 'usage-weekly') {
if (!weeklyWindow) weeklyWindow = window;
nonCodeReviewWindows.push(window);
continue;
}
if (kind === 'unknown') {
nonCodeReviewWindows.push(window);
}
}
}