From d7387478be84e1740fbbca296574187620f86cf1 Mon Sep 17 00:00:00 2001 From: R <53855466+cb8010d6@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:11:09 +0800 Subject: [PATCH] fix(codex): keep Spark five-hour quota model-scoped --- src/codex/auth-api.ts | 9 ++++--- src/codex/quota.ts | 23 ++++++++++------ src/types/config.ts | 2 +- tests/codex-integration/codex-routing.test.ts | 11 +++++--- .../codex-spark-visibility.test.ts | 27 ++++++++++++++----- 5 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/codex/auth-api.ts b/src/codex/auth-api.ts index 2e2a775867..1a1e66d88b 100644 --- a/src/codex/auth-api.ts +++ b/src/codex/auth-api.ts @@ -241,12 +241,15 @@ function codexAccountPersistenceConflict( } /** - * The exact label `parseUsageQuota` emits for the Codex Spark window (quota.ts). + * The exact labels `parseUsageQuota` emits for the Codex Spark windows (quota.ts). * Matching on the label rather than on "is a custom window" is load-bearing: the same array * carries Cursor's First-party models / API usage, Anthropic's Fable / Opus / Sonnet, * Antigravity's Gem / Cla, Kimi's subscription credits and a dozen dynamic provider meters. */ -const CODEX_SPARK_WINDOW_LABEL = "GPT-5.3-Codex-Spark Weekly"; +const CODEX_SPARK_WINDOW_LABELS = new Set([ + "GPT-5.3-Codex-Spark 5h", + "GPT-5.3-Codex-Spark Weekly", +]); /** * Drop the Spark window unless the operator asked for it (default hidden). @@ -264,7 +267,7 @@ export function withSparkVisibility window.label !== CODEX_SPARK_WINDOW_LABEL); + const kept = quota.customWindows.filter(window => !CODEX_SPARK_WINDOW_LABELS.has(window.label)); if (kept.length === quota.customWindows.length) return quota; // An empty list is dropped rather than serialized: an absent field and an empty array should // not be two different ways of saying "no custom windows" on the wire. diff --git a/src/codex/quota.ts b/src/codex/quota.ts index 0648e4a717..d125f09f93 100644 --- a/src/codex/quota.ts +++ b/src/codex/quota.ts @@ -795,6 +795,10 @@ export function parseUsageQuota(data: WhamUsageResponse): Omit !!window); + const sparkShort = sparkWindows.find(window => { + const percent = normalizeUsagePercent(window.used_percent); + return percent !== undefined && isExplicitShortWindow(window); + }); const sparkWeekly = sparkWindows.find(window => { const percent = normalizeUsagePercent(window.used_percent); const seconds = window.limit_window_seconds; @@ -803,16 +807,19 @@ export function parseUsageQuota(data: WhamUsageResponse): Omit= WEEKLY_WINDOW_MIN_SECONDS); }); - const sparkPercent = normalizeUsagePercent(sparkWeekly?.used_percent); - if (sparkPercent !== undefined) { - const sparkWindow: { label: string; percent: number; resetAt?: number } = { - label: "GPT-5.3-Codex-Spark Weekly", - percent: sparkPercent, - }; - const resetAt = normalizeResetAt(sparkWeekly?.reset_at); + const sparkCustomWindows: Array<{ label: string; percent: number; resetAt?: number }> = []; + for (const [label, window] of [ + ["GPT-5.3-Codex-Spark 5h", sparkShort], + ["GPT-5.3-Codex-Spark Weekly", sparkWeekly], + ] as const) { + const percent = normalizeUsagePercent(window?.used_percent); + if (percent === undefined) continue; + const sparkWindow: { label: string; percent: number; resetAt?: number } = { label, percent }; + const resetAt = normalizeResetAt(window?.reset_at); if (resetAt !== undefined) sparkWindow.resetAt = resetAt; - quota.customWindows = [sparkWindow]; + sparkCustomWindows.push(sparkWindow); } + if (sparkCustomWindows.length > 0) quota.customWindows = sparkCustomWindows; if (resetCredits !== undefined) quota.resetCredits = resetCredits; return hasKnownQuotaValue(quota) || resetCredits !== undefined ? quota : null; diff --git a/src/types/config.ts b/src/types/config.ts index 0b0a2b2b98..fc9a55a8fa 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -753,7 +753,7 @@ export interface OcxConfig { */ codexAccountPickerEnabled?: boolean; /** - * Show the GPT-5.3-Codex-Spark weekly window on Codex quota surfaces. Default false. + * Show the GPT-5.3-Codex-Spark 5-hour and weekly windows on Codex quota surfaces. Default false. * * Spark is a single-model window that reads 0% for most operators, and on a multi-account * pool it doubles the bar count for information almost nobody acts on. Hidden by default and diff --git a/tests/codex-integration/codex-routing.test.ts b/tests/codex-integration/codex-routing.test.ts index 11774ef474..85ecb3a3fd 100644 --- a/tests/codex-integration/codex-routing.test.ts +++ b/tests/codex-integration/codex-routing.test.ts @@ -1746,8 +1746,9 @@ describe("codex routing", () => { }); }); - test("WHAM preserves the 5h, weekly, and Spark weekly windows", () => { + test("WHAM keeps general and Spark windows separate", () => { expect(parseUsageQuota({ + plan_type: "pro", rate_limit: { primary_window: { used_percent: 11, reset_at: 1, limit_window_seconds: 5 * 60 * 60 }, secondary_window: { used_percent: 22, reset_at: 2, limit_window_seconds: 7 * 24 * 60 * 60 }, @@ -1756,7 +1757,8 @@ describe("codex routing", () => { limit_name: "GPT-5.3-Codex-Spark", metered_feature: "codex_bengalfox", rate_limit: { - primary_window: { used_percent: 33, reset_at: 3, limit_window_seconds: 7 * 24 * 60 * 60 }, + primary_window: { used_percent: 33, reset_at: 3, limit_window_seconds: 5 * 60 * 60 }, + secondary_window: { used_percent: 44, reset_at: 4, limit_window_seconds: 7 * 24 * 60 * 60 }, }, }], })).toEqual({ @@ -1765,7 +1767,10 @@ describe("codex routing", () => { shortWindowSeconds: 5 * 60 * 60, weeklyPercent: 22, weeklyResetAt: 2, - customWindows: [{ label: "GPT-5.3-Codex-Spark Weekly", percent: 33, resetAt: 3 }], + customWindows: [ + { label: "GPT-5.3-Codex-Spark 5h", percent: 33, resetAt: 3 }, + { label: "GPT-5.3-Codex-Spark Weekly", percent: 44, resetAt: 4 }, + ], }); }); diff --git a/tests/codex-integration/codex-spark-visibility.test.ts b/tests/codex-integration/codex-spark-visibility.test.ts index c5dfe1c8a7..f97073b142 100644 --- a/tests/codex-integration/codex-spark-visibility.test.ts +++ b/tests/codex-integration/codex-spark-visibility.test.ts @@ -9,6 +9,7 @@ import type { OcxConfig } from "../../src/types"; import { removeTreeWithRetry } from "../helpers/remove-tree"; const SPARK = "GPT-5.3-Codex-Spark Weekly"; +const SPARK_SHORT = "GPT-5.3-Codex-Spark 5h"; const originalHome = process.env.OPENCODEX_HOME; let home = ""; @@ -33,7 +34,7 @@ afterEach(() => { }); /** - * Codex Spark is a single-model weekly window. It reads 0% for most operators and, on a + * Codex Spark is a single-model quota with 5-hour and weekly windows. It reads 0% for most operators and, on a * multi-account pool, doubles the bar count on every card for information almost nobody acts * on — so it is hidden unless the operator asks for it. * @@ -46,7 +47,10 @@ describe("Codex Spark quota visibility", () => { saveConfig(baseConfig()); const stored = { weeklyPercent: 11, - customWindows: [{ label: SPARK, percent: 33, resetAt: 3 }], + customWindows: [ + { label: SPARK_SHORT, percent: 33, resetAt: 2 }, + { label: SPARK, percent: 34, resetAt: 3 }, + ], updatedAt: Date.now(), }; const projected = withSparkVisibility(stored); @@ -54,7 +58,7 @@ describe("Codex Spark quota visibility", () => { // saying the same thing on the wire. expect(projected.customWindows).toBeUndefined(); // The source object is untouched — routing and capacity still see the window. - expect(stored.customWindows).toHaveLength(1); + expect(stored.customWindows).toHaveLength(2); expect(projected.weeklyPercent).toBe(11); }); @@ -62,17 +66,26 @@ describe("Codex Spark quota visibility", () => { saveConfig(baseConfig(true)); loadConfig(); const projected = withSparkVisibility({ - customWindows: [{ label: SPARK, percent: 33, resetAt: 3 }], + customWindows: [ + { label: SPARK_SHORT, percent: 33, resetAt: 2 }, + { label: SPARK, percent: 34, resetAt: 3 }, + ], updatedAt: Date.now(), }); - expect(projected.customWindows).toEqual([{ label: SPARK, percent: 33, resetAt: 3 }]); + expect(projected.customWindows).toEqual([ + { label: SPARK_SHORT, percent: 33, resetAt: 2 }, + { label: SPARK, percent: 34, resetAt: 3 }, + ]); }); test("an explicit false hides it", () => { saveConfig(baseConfig(false)); loadConfig(); const projected = withSparkVisibility({ - customWindows: [{ label: SPARK, percent: 33 }], + customWindows: [ + { label: SPARK_SHORT, percent: 33 }, + { label: SPARK, percent: 34 }, + ], updatedAt: Date.now(), }); expect(projected.customWindows).toBeUndefined(); @@ -87,6 +100,7 @@ describe("Codex Spark quota visibility", () => { customWindows: [ { label: "First-party models", percent: 40 }, { label: "API usage", percent: 12 }, + { label: SPARK_SHORT, percent: 32 }, { label: SPARK, percent: 33 }, { label: "Fable", percent: 7 }, { label: "Total subscription credits", percent: 90 }, @@ -112,4 +126,3 @@ describe("Codex Spark quota visibility", () => { expect(withSparkVisibility(null)).toBeNull(); }); }); -