From b71fe5dd6ec975ac13ea1f33bd00d51df8ac80dc Mon Sep 17 00:00:00 2001 From: "Kai (Tam Nhu) Tran" <61256810+kaitranntt@users.noreply.github.com> Date: Sat, 23 May 2026 16:55:55 -0400 Subject: [PATCH] fix(codex-quota): guard feature label type in quota windows (#1346) --- .../__tests__/quota-fetcher-codex.test.ts | 20 +++++++++++++++++++ src/cliproxy/quota/quota-fetcher-codex.ts | 6 +++++- ui/src/lib/utils.ts | 4 ++-- .../unit/ui/lib/codex-quota-breakdown.test.ts | 5 +++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/cliproxy/quota/__tests__/quota-fetcher-codex.test.ts b/src/cliproxy/quota/__tests__/quota-fetcher-codex.test.ts index 284f9b38..7a913713 100644 --- a/src/cliproxy/quota/__tests__/quota-fetcher-codex.test.ts +++ b/src/cliproxy/quota/__tests__/quota-fetcher-codex.test.ts @@ -342,6 +342,26 @@ describe('Codex Quota Fetcher', () => { expect(windows.find((w) => w.category === 'additional')).toBeUndefined(); }); + it('should coerce non-string additional limit_name values to fallback label', () => { + const response = { + additional_rate_limits: [ + { + limit_name: { unexpected: true }, + rate_limit: { + primary_window: { used_percent: 10, reset_after_seconds: 3600 }, + }, + }, + ], + }; + + const windows = buildCodexQuotaWindows(response as never); + + expect(windows).toHaveLength(1); + expect(windows[0].category).toBe('additional'); + expect(windows[0].featureLabel).toBe('Additional'); + expect(windows[0].label).toBe('Additional (Primary)'); + }); + it('should accept camelCase additionalRateLimits and rateLimit fields', () => { const response = { additionalRateLimits: [ diff --git a/src/cliproxy/quota/quota-fetcher-codex.ts b/src/cliproxy/quota/quota-fetcher-codex.ts index 82cf99ed..78ab225b 100644 --- a/src/cliproxy/quota/quota-fetcher-codex.ts +++ b/src/cliproxy/quota/quota-fetcher-codex.ts @@ -380,7 +380,11 @@ function buildCodexQuotaWindows(payload: CodexUsageResponse): CodexQuotaWindow[] const entryRateLimit = entry.rate_limit || entry.rateLimit; if (!entryRateLimit) continue; - const featureLabel = entry.limit_name || entry.limitName || 'Additional'; + const rawFeatureLabel = entry.limit_name ?? entry.limitName; + const featureLabel = + typeof rawFeatureLabel === 'string' && rawFeatureLabel.trim().length > 0 + ? rawFeatureLabel.trim() + : 'Additional'; addWindow( `${featureLabel} (Primary)`, entryRateLimit.primary_window || entryRateLimit.primaryWindow, diff --git a/ui/src/lib/utils.ts b/ui/src/lib/utils.ts index 00a936f7..e064e810 100644 --- a/ui/src/lib/utils.ts +++ b/ui/src/lib/utils.ts @@ -384,8 +384,8 @@ function getCodexWindowKindFromLabel(label: string): CodexWindowKind { * "OmniReview" -> "OmniReview" * "" -> "" */ -export function prettifyCodexFeatureLabel(featureLabel: string): string { - const trimmed = (featureLabel || '').trim(); +export function prettifyCodexFeatureLabel(featureLabel: unknown): string { + const trimmed = typeof featureLabel === 'string' ? featureLabel.trim() : ''; if (!trimmed) return ''; const stripped = trimmed.replace(/^GPT-[\d.]+-Codex-/i, ''); if (stripped !== trimmed && stripped.length > 0) { diff --git a/ui/tests/unit/ui/lib/codex-quota-breakdown.test.ts b/ui/tests/unit/ui/lib/codex-quota-breakdown.test.ts index 99c6c6e1..d7631901 100644 --- a/ui/tests/unit/ui/lib/codex-quota-breakdown.test.ts +++ b/ui/tests/unit/ui/lib/codex-quota-breakdown.test.ts @@ -37,6 +37,11 @@ describe('prettifyCodexFeatureLabel', () => { expect(prettifyCodexFeatureLabel(' ')).toBe(''); expect(prettifyCodexFeatureLabel(' GPT-5.3-Codex-Spark ')).toBe('Codex Spark'); }); + + it('returns empty string for non-string input', () => { + expect(prettifyCodexFeatureLabel({ label: 'Spark' })).toBe(''); + expect(prettifyCodexFeatureLabel(123)).toBe(''); + }); }); describe('getCodexWindowKind', () => {