TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { newId, logger } from '@rareindex/shared';2import type { CostContext, Usage } from './types.js';34export interface CostEvent {5 provider: string;6 model: string;7 role: string;8 usage: Usage;9 usdEst: number;10 units?: number;11 context?: CostContext;12}1314export type CostSink = (event: CostEvent) => Promise<void> | void;1516let sink: CostSink | null = null;17const buffer: CostEvent[] = [];1819/** Install the persistence sink (the default DB sink is installed lazily by `installDbCostSink`). */20export function setCostSink(fn: CostSink | null): void {21 sink = fn;22}2324export async function recordCost(event: CostEvent): Promise<void> {25 buffer.push(event);26 if (buffer.length > 500) buffer.shift();27 if (!sink) return;28 try {29 await sink(event);30 } catch (err) {31 logger.warn({ err: err instanceof Error ? err.message : String(err) }, 'ai cost sink failed');32 }33}3435/** In-memory tail of recent AI cost events (diagnostics/tests). */36export function recentCosts(): readonly CostEvent[] {37 return buffer;38}3940/** Persist AI spend into the shared `costs` table (§169). Imports the DB lazily to keep this package light. */41export async function installDbCostSink(): Promise<void> {42 const { getDb, costs } = await import('@rareindex/database');43 setCostSink(async (e) => {44 await getDb()45 .insert(costs)46 .values({47 id: newId('event'),48 occurredAt: new Date(),49 kind: 'ai',50 provider: e.provider,51 connectorId: e.context?.connectorId ?? null,52 categorySlug: e.context?.categorySlug ?? null,53 endpoint: e.context?.endpoint ?? e.role,54 userId: e.context?.userId ?? null,55 units: e.units ?? e.usage.inputTokens + e.usage.outputTokens,56 credits: 0,57 usdEst: e.usdEst,58 metadata: { model: e.model, role: e.role, usage: e.usage, ...(e.context?.metadata ?? {}) },59 });60 });61}62