Files
OmniRoute/open-sse/handlers/chatCore/failureUsage.ts
Diego Rodrigues de Sa e Souza 342cd87e12 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
2026-06-24 00:46:39 -03:00

42 lines
1.6 KiB
TypeScript

/**
* chatCore failed-request usage record builder (Quality Gate v2 / Fase 9 — chatCore god-file
* decomposition, #3501).
*
* Pure core of handleChatCore's persistFailureUsage closure: builds the usage-history entry for a
* failed request (zeroed tokens/timing, success:false, the unknown/undefined fallbacks, and the
* combo-strategy gate). The handler keeps the impure parts byte-identically: it computes
* `latencyMs` (Date.now() - startTime) and fires the fire-and-forget saveRequestUsage(...).catch().
*/
export function buildFailureUsageRecord(opts: {
provider: string | null | undefined;
model: string | null | undefined;
connectionId: string | null | undefined;
apiKeyInfo: { id?: string; name?: string } | null | undefined;
effectiveServiceTier: string;
isCombo: boolean;
comboStrategy: string | null | undefined;
statusCode: number;
errorCode: string | null | undefined;
latencyMs: number;
endpoint?: string | null | undefined;
}) {
return {
provider: opts.provider || "unknown",
model: opts.model || "unknown",
tokens: { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, reasoning: 0 },
status: String(opts.statusCode),
success: false,
latencyMs: opts.latencyMs,
timeToFirstTokenMs: 0,
errorCode: opts.errorCode || String(opts.statusCode),
timestamp: new Date().toISOString(),
connectionId: opts.connectionId || undefined,
apiKeyId: opts.apiKeyInfo?.id || undefined,
apiKeyName: opts.apiKeyInfo?.name || undefined,
serviceTier: opts.effectiveServiceTier,
comboStrategy: opts.isCombo ? opts.comboStrategy || undefined : undefined,
endpoint: opts.endpoint || undefined,
};
}