From 63c8ec5d7c8d5830bd514f812081fb31804564a8 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Thu, 26 Mar 2026 17:18:27 -0400 Subject: [PATCH] fix(cliproxy): preserve auth-file source fallback --- src/cliproxy/stats-transformer.ts | 14 +- tests/unit/cliproxy/stats-transformer.test.ts | 193 ++++++++++++------ 2 files changed, 146 insertions(+), 61 deletions(-) diff --git a/src/cliproxy/stats-transformer.ts b/src/cliproxy/stats-transformer.ts index e8f355f4..74c10aa0 100644 --- a/src/cliproxy/stats-transformer.ts +++ b/src/cliproxy/stats-transformer.ts @@ -13,7 +13,7 @@ interface BuildCliproxyStatsOptions { } interface ResolvedAuthFile { - provider: string; + provider?: string; source?: string; } @@ -32,13 +32,19 @@ function buildAuthIndexLookup( const lookup = new Map(); for (const authFile of authFiles ?? []) { - if (authFile.auth_index === undefined || authFile.auth_index === null || !authFile.provider) { + if (authFile.auth_index === undefined || authFile.auth_index === null) { + continue; + } + + const provider = authFile.provider ? normalizeProvider(authFile.provider) : undefined; + const source = authFile.email?.trim() || authFile.name?.trim() || undefined; + if (!provider && !source) { continue; } lookup.set(String(authFile.auth_index), { - provider: normalizeProvider(authFile.provider), - source: authFile.email?.trim() || authFile.name?.trim() || undefined, + provider, + source, }); } diff --git a/tests/unit/cliproxy/stats-transformer.test.ts b/tests/unit/cliproxy/stats-transformer.test.ts index 2d74a224..a58482be 100644 --- a/tests/unit/cliproxy/stats-transformer.test.ts +++ b/tests/unit/cliproxy/stats-transformer.test.ts @@ -1,10 +1,51 @@ import { describe, expect, it } from 'bun:test'; import type { CliproxyManagementAuthFile, + CliproxyRequestDetail, CliproxyUsageApiResponse, } from '../../../src/cliproxy/stats-fetcher'; import { buildCliproxyStatsFromUsageResponse } from '../../../src/cliproxy/stats-transformer'; +function createDetail(overrides: Partial = {}): CliproxyRequestDetail { + return { + timestamp: '2025-03-26T10:00:00.000Z', + source: 'shared@example.com', + auth_index: 'shared-auth-index', + tokens: { + input_tokens: 10, + output_tokens: 5, + reasoning_tokens: 0, + cached_tokens: 0, + total_tokens: 15, + }, + failed: false, + ...overrides, + }; +} + +function createInternallyBucketedUsage( + details: CliproxyRequestDetail[] +): CliproxyUsageApiResponse { + return { + usage: { + total_requests: details.length, + success_count: details.filter((detail) => !detail.failed).length, + failure_count: details.filter((detail) => detail.failed).length, + apis: { + 'ccs-internal-managed': { + total_requests: details.length, + models: { + 'gpt-5': { + total_requests: details.length, + details, + }, + }, + }, + }, + }, + }; +} + describe('buildCliproxyStatsFromUsageResponse', () => { it('keeps duplicate emails isolated by provider', () => { const usage: CliproxyUsageApiResponse = { @@ -126,64 +167,32 @@ describe('buildCliproxyStatsFromUsageResponse', () => { }); it('resolves canonical providers from auth_index when usage is internally bucketed', () => { - const usage: CliproxyUsageApiResponse = { - usage: { - total_requests: 3, - success_count: 2, - failure_count: 1, - apis: { - 'ccs-internal-managed': { - total_requests: 3, - models: { - 'gpt-5': { - total_requests: 3, - details: [ - { - timestamp: '2026-03-26T10:00:00.000Z', - source: 'shared@example.com', - auth_index: 'codex-1', - tokens: { - input_tokens: 10, - output_tokens: 5, - reasoning_tokens: 0, - cached_tokens: 0, - total_tokens: 15, - }, - failed: false, - }, - { - timestamp: '2026-03-26T10:01:00.000Z', - source: 'shared@example.com', - auth_index: 'gemini-1', - tokens: { - input_tokens: 12, - output_tokens: 7, - reasoning_tokens: 0, - cached_tokens: 0, - total_tokens: 19, - }, - failed: false, - }, - { - timestamp: '2026-03-26T10:02:00.000Z', - source: 'shared@example.com', - auth_index: 'agy-1', - tokens: { - input_tokens: 8, - output_tokens: 2, - reasoning_tokens: 0, - cached_tokens: 0, - total_tokens: 10, - }, - failed: true, - }, - ], - }, - }, - }, + const usage = createInternallyBucketedUsage([ + createDetail({ auth_index: 'codex-1' }), + createDetail({ + timestamp: '2025-03-26T10:01:00.000Z', + auth_index: 'gemini-1', + tokens: { + input_tokens: 12, + output_tokens: 7, + reasoning_tokens: 0, + cached_tokens: 0, + total_tokens: 19, }, - }, - }; + }), + createDetail({ + timestamp: '2025-03-26T10:02:00.000Z', + auth_index: 'agy-1', + tokens: { + input_tokens: 8, + output_tokens: 2, + reasoning_tokens: 0, + cached_tokens: 0, + total_tokens: 10, + }, + failed: true, + }), + ]); const authFiles: CliproxyManagementAuthFile[] = [ { auth_index: 'codex-1', provider: 'codex', email: 'shared@example.com' }, { auth_index: 'gemini-1', provider: 'gemini-cli', email: 'shared@example.com' }, @@ -209,4 +218,74 @@ describe('buildCliproxyStatsFromUsageResponse', () => { }); expect(stats.requestsByProvider).toEqual({ codex: 1, gemini: 1, agy: 1 }); }); + + it('falls back to the usage provider when auth_index lookup cannot resolve a provider', () => { + const usage = createInternallyBucketedUsage([createDetail({ auth_index: 'codex-1' })]); + const scenarios: Array<{ label: string; authFiles: CliproxyManagementAuthFile[] }> = [ + { label: 'empty authFiles', authFiles: [] }, + { + label: 'missing auth_index match', + authFiles: [{ auth_index: 'other-auth-index', provider: 'codex', email: 'shared@example.com' }], + }, + { + label: 'matching auth_index without provider metadata', + authFiles: [{ auth_index: 'codex-1', email: 'shared@example.com' }], + }, + ]; + + for (const scenario of scenarios) { + const stats = buildCliproxyStatsFromUsageResponse(usage, { authFiles: scenario.authFiles }); + + expect(stats.accountStats['ccs-internal-managed:shared@example.com'], scenario.label).toMatchObject({ + provider: 'ccs-internal-managed', + source: 'shared@example.com', + successCount: 1, + failureCount: 0, + }); + expect(stats.requestsByProvider, scenario.label).toEqual({ 'ccs-internal-managed': 1 }); + } + }); + + it('falls back to auth-file source metadata when detail source is blank and supports mixed auth_index outcomes', () => { + const usage = createInternallyBucketedUsage([ + createDetail({ source: ' ', auth_index: 'codex-1' }), + createDetail({ + timestamp: '2025-03-26T10:01:00.000Z', + source: 'unmatched@example.com', + auth_index: 'missing-auth-index', + }), + createDetail({ + timestamp: '2025-03-26T10:02:00.000Z', + source: ' ', + auth_index: 'providerless-auth-index', + failed: true, + }), + ]); + const authFiles: CliproxyManagementAuthFile[] = [ + { auth_index: 'codex-1', provider: 'codex', email: 'fallback@example.com' }, + { auth_index: 'providerless-auth-index', email: 'providerless-fallback@example.com' }, + ]; + + const stats = buildCliproxyStatsFromUsageResponse(usage, { authFiles }); + + expect(stats.accountStats['codex:fallback@example.com']).toMatchObject({ + provider: 'codex', + source: 'fallback@example.com', + successCount: 1, + failureCount: 0, + }); + expect(stats.accountStats['ccs-internal-managed:unmatched@example.com']).toMatchObject({ + provider: 'ccs-internal-managed', + source: 'unmatched@example.com', + successCount: 1, + failureCount: 0, + }); + expect(stats.accountStats['ccs-internal-managed:providerless-fallback@example.com']).toMatchObject({ + provider: 'ccs-internal-managed', + source: 'providerless-fallback@example.com', + successCount: 0, + failureCount: 1, + }); + expect(stats.requestsByProvider).toEqual({ codex: 1, 'ccs-internal-managed': 2 }); + }); });