Files
OmniRoute/open-sse/handlers/chatCore/nonStreamingUsageStats.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

91 lines
3.5 KiB
TypeScript

/**
* chatCore non-streaming usage-stats persistence (Quality Gate v2 / Fase 9 — chatCore god-file
* decomposition, #3501 — response-handling slice of executeProviderRequest).
*
* Extracted from handleChatCore's non-streaming success path: records per-request usage analytics
* for a successful non-streaming response — an optional trace console line, the fire-and-forget
* `saveRequestUsage` row, and the per-api-key billable-token counter. Side-effect only (no handler
* state is mutated, nothing is returned); best-effort, every write swallows its own errors. The
* per-request context is threaded via `ctx` so the call site stays byte-identical; behaviour is
* unchanged.
*/
import { saveRequestUsage } from "@/lib/usageDb";
import { formatUsageLog } from "@/lib/usage/tokenAccounting";
import { COLORS } from "../../utils/stream.ts";
import { recordTokenUsage } from "../../services/tokenLimitCounter.ts";
import { computeBillableTokens } from "./upstreamTimeouts.ts";
import { type EffectiveServiceTier } from "./serviceTier.ts";
export type RecordNonStreamingUsageStatsContext = {
traceEnabled: boolean;
provider: string | null | undefined;
connectionId: string | null | undefined;
model: string | null | undefined;
startTime: number;
apiKeyInfo: { id?: string | null; name?: string | null } | null | undefined;
effectiveServiceTier: EffectiveServiceTier;
isCombo: boolean;
comboStrategy: string | null | undefined;
endpoint?: string | null | undefined;
};
function logUsageTrace(
usage: object,
provider: string | null | undefined,
connectionId: string | null | undefined
): void {
const msg = `[${new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit" })}] 📊 [USAGE] ${provider?.toUpperCase()} | ${formatUsageLog(usage)}${connectionId ? ` | account=${connectionId.slice(0, 8)}...` : ""}`;
console.log(`${COLORS.green}${msg}${COLORS.reset}`);
}
function persistUsageRow(usage: object, ctx: RecordNonStreamingUsageStatsContext): void {
const { provider, connectionId, model, startTime, apiKeyInfo, effectiveServiceTier } = ctx;
saveRequestUsage({
provider: provider || "unknown",
model: model || "unknown",
tokens: usage,
status: "200",
success: true,
latencyMs: Date.now() - startTime,
timeToFirstTokenMs: Date.now() - startTime,
errorCode: null,
timestamp: new Date().toISOString(),
connectionId: connectionId || undefined,
apiKeyId: apiKeyInfo?.id || undefined,
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);
});
}
function recordBillableTokens(
usage: object,
apiKeyInfo: RecordNonStreamingUsageStatsContext["apiKeyInfo"],
provider: string | null | undefined,
model: string | null | undefined
): void {
if (!apiKeyInfo?.id) return;
try {
const billable = computeBillableTokens(usage);
if (billable > 0)
recordTokenUsage(apiKeyInfo.id, provider || "unknown", model || "unknown", billable);
} catch {
// never block the response on counter recording
}
}
export function recordNonStreamingUsageStats(
usage: unknown,
ctx: RecordNonStreamingUsageStatsContext
): void {
if (!usage || typeof usage !== "object") return;
if (ctx.traceEnabled) logUsageTrace(usage, ctx.provider, ctx.connectionId);
persistUsageRow(usage, ctx);
recordBillableTokens(usage, ctx.apiKeyInfo, ctx.provider, ctx.model);
}