1/** 2 * AI module — usage ledger sink (Phase 12, TASK-0121). 3 * 4 * The gateway's usage sink: called (fire-and-forget, never awaited by the 5 * request path) after EVERY completed or failed-after-consume gateway call. 6 * 7 * **Why this is a logger, not a writer (recorded decision):** the durable 8 * `ai_messages` rows are written by the feature services on the request 9 * path — the chat service persists the visible assistant message (content + 10 * token counts + tool calls) in the user's conversation; the feature 11 * services (writing / RAG / generate) persist their hidden per-call 12 * conversation row. If the sink ALSO wrote a row, every call would be 13 * double-counted in the monthly rollup (the budget guard sums 14 * `ai_messages` — a double ledger would 402 workspaces twice as fast). 15 * 16 * The sink therefore gives the gateway's metering an observability home 17 * (AGENTS rule 26 — usage of the AI surface is an important operation): a 18 * structured line per call (capability, model, provider, tokens, status, 19 * latency). The durable ledger is `ai_messages` (one row per call, written 20 * by the services); the nightly `usage.rollup` worker job reconciles it for 21 * dashboards. 22 */ 23import { Inject, Injectable } from "@nestjs/common"; 24import type { UsageRecord } from "@factory/ai"; 25import { PrismaService } from "../../common/prisma.service.js"; 26import { logger } from "../../common/logger.js"; 27 28export const AI_LEDGER = "AI_LEDGER"; 29 30@Injectable() 31export class AiLedgerService { 32 constructor(@Inject(PrismaService) private readonly prisma: PrismaService) {} 33 34 /** 35 * The gateway's sink (see module doc). Observability only — the durable 36 * row is the service's own `ai_messages` write for the same call. 37 */ 38 async record(record: UsageRecord): Promise<void> { 39 logger.info( 40 `ai usage ${record.kind} ${record.model}/${record.provider} in=${record.tokensIn} out=${record.tokensOut} ${record.latencyMs}ms ${record.status} (gateway metering; the durable row is the service's ai_messages write)`, 41 { 42 event: "ai.usage", 43 workspaceId: record.workspaceId, 44 ...(record.userId ? { userId: record.userId } : {}), 45 ...(record.capability ? { capability: record.capability } : {}), 46 kind: record.kind, 47 model: record.model, 48 provider: record.provider, 49 tokensIn: record.tokensIn, 50 tokensOut: record.tokensOut, 51 latencyMs: record.latencyMs, 52 status: record.status 53 } 54 ); 55 } 56} 57