feat(db): track API endpoint dimension on usage_history

Add an `endpoint` column to `usage_history` (migration 103) so the proxy
records which API entry point a request came through — typically
`/v1/chat/completions`, `/v1/messages`, or `/v1/responses`. Every
`saveRequestUsage(...)` call site in `open-sse/handlers/chatCore.ts`
(success, stream, failure) and the codex-responses-ws route now plumbs
the endpoint through, and a new `getEndpointUsageRows(...)` analytics
query in `src/lib/db/usageAnalytics.ts` aggregates per
endpoint × provider × model with NULL → 'unknown' folding for backward
compatibility with rows from before the migration.

The aggregation reads directly from `usage_history` (matching the
`getAutoRoutingVariantBreakdown` pattern) so the existing unified-source
CTE — and the `daily_usage_summary` rollup it joins — stay untouched.

TDD: tests/unit/usage-endpoint-dimension.test.ts covers persistence
round-trip, NULL fallback, and the sinceIso filter (3/3 passing).

Co-authored-by: toanalien <toanalien@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/152
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-22 17:00:22 -03:00
parent 8251e7b8fb
commit 342cd87e12
10 changed files with 218 additions and 3 deletions

View File

@@ -433,6 +433,7 @@ export async function handleChatCore({
statusCode,
errorCode,
latencyMs: Date.now() - startTime,
endpoint: endpointPath,
})
).catch(() => {});
};
@@ -3422,6 +3423,7 @@ export async function handleChatCore({
effectiveServiceTier,
isCombo,
comboStrategy,
endpoint: endpointPath,
});
// Translate response to client's expected format (usually OpenAI)
@@ -3806,6 +3808,7 @@ export async function handleChatCore({
effectiveServiceTier,
isCombo,
comboStrategy,
endpoint: endpointPath,
});
persistAttemptLogs({

View File

@@ -19,6 +19,7 @@ export function buildFailureUsageRecord(opts: {
statusCode: number;
errorCode: string | null | undefined;
latencyMs: number;
endpoint?: string | null | undefined;
}) {
return {
provider: opts.provider || "unknown",
@@ -35,5 +36,6 @@ export function buildFailureUsageRecord(opts: {
apiKeyName: opts.apiKeyInfo?.name || undefined,
serviceTier: opts.effectiveServiceTier,
comboStrategy: opts.isCombo ? opts.comboStrategy || undefined : undefined,
endpoint: opts.endpoint || undefined,
};
}

View File

@@ -27,6 +27,7 @@ export type RecordNonStreamingUsageStatsContext = {
effectiveServiceTier: EffectiveServiceTier;
isCombo: boolean;
comboStrategy: string | null | undefined;
endpoint?: string | null | undefined;
};
function logUsageTrace(
@@ -55,6 +56,7 @@ function persistUsageRow(usage: object, ctx: RecordNonStreamingUsageStatsContext
apiKeyName: apiKeyInfo?.name || undefined,
serviceTier: effectiveServiceTier,
comboStrategy: ctx.isCombo ? ctx.comboStrategy || undefined : undefined,
endpoint: ctx.endpoint || undefined,
}).catch((err) => {
console.error("Failed to save usage stats:", err.message);
});

View File

@@ -27,6 +27,7 @@ export type RecordStreamingUsageStatsContext = {
effectiveServiceTier: EffectiveServiceTier;
isCombo: boolean;
comboStrategy: string | null | undefined;
endpoint?: string | null | undefined;
};
function persistStreamingUsageRow(usage: object, ctx: RecordStreamingUsageStatsContext): void {
@@ -46,6 +47,7 @@ function persistStreamingUsageRow(usage: object, ctx: RecordStreamingUsageStatsC
apiKeyName: ctx.apiKeyInfo?.name || undefined,
serviceTier: ctx.effectiveServiceTier,
comboStrategy: ctx.isCombo ? ctx.comboStrategy || undefined : undefined,
endpoint: ctx.endpoint || undefined,
}).catch((err) => {
console.error("Failed to save usage stats:", err.message);
});