/** * MODULE CHARTER: Per-Request Accounting Lifecycle & Price Port Bridge (accounting-state.ts) * * 1. Domain Boundary & Responsibilities: * - Coordinates the per-request accounting lifecycle for all caller-visible proxy routes. * - Accumulates token metrics across multiple candidate attempts, failover walks, and streaming responses. * - Decouples pricing metadata and model catalog resolution from low-level storage mutation logic. * * 2. Request Accounting Lifecycle: * - Initiation: `createRequestAccountingState()` establishes request identity, timing, and caller attribution. * - Attempt Recording: Each backend egress records attempt start, headers arrival, streaming deltas, or premature error. * - Completion: Upon stream finish or error, `commit()` resolves final token usage, prices attempts via the price port, * and submits immutable `AccountingEvent` records to the registered `AccountingRecorder`. * * 3. Price Port Decoupling: * - Pricing logic is encapsulated via `AccountingPricePort` functional interfaces. * - Tier models and pricing rates are resolved from `tier-data.js` and `metadata.js` at runtime without locking the store. * - Missing or unpriced models gracefully emit unpriced token facts without halting accounting pipelines. * * 4. Caller Attribution & Path Classification: * - Routes are filtered via `isCallerVisibleAccountingPath()` to exclude health, models, and administrative endpoints. * - Attributions cleanly distinguish relay-held infrastructure actions from caller-operated interactions. */ import type { ServerResponse } from "node:http"; import type { Config } from "./config.js"; import type { ModelCatalog } from "./catalog.js"; import { type AccountingAttempt, type AccountingPricePort, type AccountingRecorder, type TokenFactsInput } from "./accounting.js"; import { type UsageAccumulator } from "./usage-observer.js"; import type { ResolvedAttempt } from "./resolved-attempt.js"; import type { Reshaper } from "./reshaper.js"; export type ModelCallRecorder = (providerKey: string, modelId: string, callResult: { ok: boolean; latencyMs: number; completionTokens?: number; }) => void; export type ProxyAccountingAttribution = "relay_held" | "caller_operated" | "unknown"; export type ProxyAccountingFailureKind = "timeout" | "provider_error" | "auth_error" | "rate_limit" | "aborted" | "protocol" | "unknown"; export declare function isCallerVisibleAccountingPath(method: string | undefined, pathname: string): boolean; export declare function recordEarlyTerminalAccounting(recorder: AccountingRecorder, startedAt: number, client: string): void; export declare function serveAccountingAttribution(attempt: ResolvedAttempt): ProxyAccountingAttribution; export declare function repairAccountingAttribution(state: ResolvedAttempt["credential"]["state"]): ProxyAccountingAttribution; export declare function accountingTokens(usage: UsageAccumulator, estimatedInputTokens: number, includeEstimatedOutput: boolean): TokenFactsInput; export declare function buildAccountingPricePort(catalog: ModelCatalog, cfg: Config): AccountingPricePort; export declare class RequestAccountingState { private readonly estimatedInputTokens; private readonly pricePort?; private readonly onServedLatency?; private readonly request; private readonly active; private readonly committed; private readonly attemptDeployments; private responseTerminal; private finalized; private successfulServe; private committedServeAttribution; private lastFailure; private lastAttribution; constructor(recorder: AccountingRecorder, response: ServerResponse, startedAt: number, estimatedInputTokens: number, client: string, pricePort?: AccountingPricePort | undefined, onServedLatency?: ((provider: string, model: string, ms: number, tokens: number | undefined) => void) | undefined); startServe(attempt: ResolvedAttempt, startedAt: number): AccountingAttempt | null; startRepair(options: { readonly resolvedAttempt: ResolvedAttempt | null; readonly credentialState: ResolvedAttempt["credential"]["state"]; readonly provider: string | null; readonly model: string | null; readonly credentialId: string | null; readonly startedAt: number; }): AccountingAttempt | null; markCommitted(attempt: AccountingAttempt | null, at: number): void; complete(attempt: AccountingAttempt | null, outcome: "success" | "error" | "cancelled", failureKind: ProxyAccountingFailureKind | null, usage: UsageAccumulator, endedAt?: number, abandonedByRelay?: boolean): void; private start; isRequestClosed(): boolean; private finalizeIfReady; } export declare function withRepairAccounting(reshaper: Reshaper, accounting: RequestAccountingState | null): Reshaper;