SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
1.8 KB · 62 lines typescript
Raw Blame History
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