From 89a927228c3e30f330f313ab8b2af37199892afd Mon Sep 17 00:00:00 2001 From: luvs01 <27862058+luvs01@users.noreply.github.com> Date: Sun, 20 Sep 2026 16:24:14 +0900 Subject: [PATCH] fix(usage): restrict legacy -main provider collapse to Codex labels A -main suffix was stripped from ANY provider name, so a configured provider like openrouter-main silently merged into openrouter in usage summaries and pricing fallback. Restrict the compatibility mapping to the known Codex provider labels (openai/chatgpt/openai-multi-main), matching codexAccountProviders. --- src/providers/label.ts | 11 ++++++++--- tests/usage/usage-cost.test.ts | 6 +++--- tests/usage/usage-provider-label.test.ts | 10 ++++++++-- tests/usage/usage-summary.test.ts | 10 ++++++++++ 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/providers/label.ts b/src/providers/label.ts index 38b472a1a05..10f1a09e705 100644 --- a/src/providers/label.ts +++ b/src/providers/label.ts @@ -5,6 +5,8 @@ export function canonicalUsageProviderLabel(provider: string): string { return provider === "chatgpt" || provider === "openai-multi" ? "openai" : provider; } +const LEGACY_MAIN_ACCOUNT_PROVIDER_LABELS = new Set(["openai-main", "chatgpt-main", "openai-multi-main"]); + export function usesApiKeyAccount(provider: Pick): boolean { return provider.authMode === "key" || (provider.authMode === undefined && !!provider._apiKeyAttempt?.reference); @@ -29,11 +31,14 @@ export function baseProviderLabel(provider: string): string { const cut = provider.lastIndexOf("-"); if (cut <= 0) return canonicalUsageProviderLabel(provider); const suffix = provider.slice(cut + 1); - // `-main` is the legacy log label for the main Codex account (MAIN_CODEX_ACCOUNT_ID). New entries - // log under the base provider name, but historical `-main` entries must still collapse. + // `-main` was the legacy log label for the main Codex account (MAIN_CODEX_ACCOUNT_ID). Restrict + // that compatibility mapping to the known Codex provider labels so configured providers whose + // names naturally end in `-main` remain distinct. // ChatGPT auth-pool and OpenAI passthrough are the same Codex/OpenAI usage surface, so display // summaries normalize them to one `openai` row after recognized main/pool suffixes are removed. - if (suffix === "main") return canonicalUsageProviderLabel(provider.slice(0, cut)); + if (LEGACY_MAIN_ACCOUNT_PROVIDER_LABELS.has(provider)) { + return canonicalUsageProviderLabel(provider.slice(0, cut)); + } return CODEX_ACCOUNT_LOG_LABEL_RE.test(suffix) ? canonicalUsageProviderLabel(provider.slice(0, cut)) : provider; } diff --git a/tests/usage/usage-cost.test.ts b/tests/usage/usage-cost.test.ts index dffe93f2ab2..5eebe22de26 100644 --- a/tests/usage/usage-cost.test.ts +++ b/tests/usage/usage-cost.test.ts @@ -1510,13 +1510,13 @@ describe("Codex account pricing identity", () => { } }); - test("only recognized historical phex and main suffixes retain the existing fallback", () => { + test("only recognized historical phex suffixes retain the existing fallback", () => { refreshUserCostOverlays(config([])); const custom = { ...row, provider: "legacy" }; - for (const provider of ["legacy-pabcdef", "legacy-main"]) { + for (const provider of ["legacy-pabcdef"]) { expect(resolveMatchedPrice(provider, modelId, [custom], [])?.cost4).toEqual(RATE); } - for (const provider of ["legacy-unknown", "legacy-pABCDEF", "legacy-pabcde", "legacy-oabcdef", "legacy-__main__"]) { + for (const provider of ["legacy-unknown", "legacy-pABCDEF", "legacy-pabcde", "legacy-oabcdef", "legacy-__main__", "legacy-main"]) { expect(resolveMatchedPrice(provider, modelId, [custom], [])).toBeNull(); } }); diff --git a/tests/usage/usage-provider-label.test.ts b/tests/usage/usage-provider-label.test.ts index 66fefefe9a9..e5a12bdaee9 100644 --- a/tests/usage/usage-provider-label.test.ts +++ b/tests/usage/usage-provider-label.test.ts @@ -25,9 +25,15 @@ describe("baseProviderLabel", () => { expect(baseProviderLabel("anthropic-pabc123")).toBe("anthropic"); }); - test("strips the legacy -main suffix so historical main-account rows aggregate", () => { + test("keeps configured provider names ending in -main distinct", () => { + expect(baseProviderLabel("openrouter-main")).toBe("openrouter-main"); + expect(baseProviderLabel("azure-main")).toBe("azure-main"); + }); + + test("strips the legacy -main suffix from the known Codex provider labels", () => { expect(baseProviderLabel("openai-main")).toBe("openai"); - expect(baseProviderLabel("codex-main")).toBe("codex"); + expect(baseProviderLabel("chatgpt-main")).toBe("openai"); + expect(baseProviderLabel("openai-multi-main")).toBe("openai"); }); test("keeps suffixes that do not match the pool log-label shape", () => { diff --git a/tests/usage/usage-summary.test.ts b/tests/usage/usage-summary.test.ts index c109d861f4d..066797f7911 100644 --- a/tests/usage/usage-summary.test.ts +++ b/tests/usage/usage-summary.test.ts @@ -1186,6 +1186,16 @@ describe("summarizeUsage", () => { ]); }); + test("keeps configured providers ending in -main in distinct usage rows", () => { + const entries: PersistedUsageEntry[] = [ + entry({ ts: FIXED_NOW - 1, provider: "openrouter", model: "shared-model", usageStatus: "reported", usage: { inputTokens: 4, outputTokens: 1 }, totalTokens: 5 }), + entry({ ts: FIXED_NOW - 2, provider: "openrouter-main", model: "shared-model", usageStatus: "reported", usage: { inputTokens: 2, outputTokens: 1 }, totalTokens: 3 }), + ]; + const sum = summarizeUsage(entries, "30d", FIXED_NOW); + expect(sum.providers.map(provider => provider.provider).sort()).toEqual(["openrouter", "openrouter-main"]); + expect(sum.models.map(model => model.provider).sort()).toEqual(["openrouter", "openrouter-main"]); + }); + test("keeps one logical combo request while attributing both physical attempts", () => { const combo = entry({ ts: FIXED_NOW - 1,