import type { SwitchboardMeta } from "./switchboard-types" import { prisma } from "@/lib/prisma" /** * Persist one row per AI call so member spend is queryable. * * Deliberately not awaited by callers: an insert failure must never turn a working * AI response into an error for the user. Failures are logged and dropped. */ export function recordAiCall( userId: string, feature: string, meta: SwitchboardMeta | null ): void { void prisma.aiCall .create({ data: { userId, feature, modelId: meta?.model_id ?? null, provider: meta?.provider ?? null, costUsd: meta?.cost_usd ?? null, latencyMs: meta?.latency_ms ?? null, failover: meta?.failover ?? false, }, }) .catch((error) => { console.warn("[switchboard] failed to record ai call:", error) }) } /** * One line per gateway call so the cost and the model actually used are visible in * the server log. Called from inside the provider, so every feature gets it for free. */ export function logSwitchboardMeta( feature: string, meta: SwitchboardMeta | null ): void { if (!meta) { console.warn(`[switchboard] feature=${feature} no meta block in response`) return } console.log( `[switchboard] feature=${feature} model=${meta.model_id} ` + `provider=${meta.provider} locality=${meta.locality} category=${meta.category} ` + `cost=${meta.cost_usd ?? "?"} latency_ms=${meta.latency_ms} request_id=${meta.request_id}` ) // Both of these silently degrade output quality, so they warn rather than log. if (meta.failover) { console.warn( `[switchboard] feature=${feature} FAILOVER intended=${meta.intended_model} ` + `actual=${meta.model_id} reason=${meta.reason}` ) } if (meta.context_overflow) { console.warn( `[switchboard] feature=${feature} CONTEXT OVERFLOW model=${meta.model_id} ` + `- the provider may have truncated this request` ) } }