mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 02:11:28 +00:00
fix(cliproxy): preserve auth-file source fallback
This commit is contained in:
@@ -13,7 +13,7 @@ interface BuildCliproxyStatsOptions {
|
||||
}
|
||||
|
||||
interface ResolvedAuthFile {
|
||||
provider: string;
|
||||
provider?: string;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
@@ -32,13 +32,19 @@ function buildAuthIndexLookup(
|
||||
const lookup = new Map<string, ResolvedAuthFile>();
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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> = {}): 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 });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user