@bymax-one/nest-ai-tokens 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,677 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+
3
+ /**
4
+ * @fileoverview Catalog of the AI providers this library recognizes out of the
5
+ * box. Custom OpenAI-compatible providers register their own id at runtime via
6
+ * `providerPresets.openaiCompatible(id)`, so the public {@link ProviderId} type
7
+ * (see `types/catalogs.ts`) widens this closed list with `(string & {})`.
8
+ * @layer shared
9
+ */
10
+ /**
11
+ * The provider identifiers shipped with the library. Frozen tuple so the derived
12
+ * {@link KnownProviderId} union stays in sync with the runtime values.
13
+ */
14
+ declare const PROVIDER_IDS: readonly ["openai", "azure-openai", "anthropic", "gemini", "vertex", "mistral", "bedrock", "openrouter", "deepseek", "xai", "groq"];
15
+ /** A provider id known to the library at build time (excludes custom ids). */
16
+ type KnownProviderId = (typeof PROVIDER_IDS)[number];
17
+
18
+ /**
19
+ * @fileoverview Catalog of AI operation kinds a usage record can describe. The
20
+ * operation is part of the price-resolution key; `'responses'` bills identically
21
+ * to `'chat'` and shares its price rows (see spec §5.1).
22
+ * @layer shared
23
+ */
24
+ /** Every operation kind the library can meter and price. */
25
+ declare const AI_OPERATIONS: readonly ["chat", "responses", "embeddings", "image", "video", "audio", "rerank", "moderation"];
26
+ /** A logical AI operation kind. */
27
+ type AiOperation = (typeof AI_OPERATIONS)[number];
28
+
29
+ /**
30
+ * @fileoverview Catalog of response service tiers. Providers may silently
31
+ * downgrade a request (OpenAI returns `service_tier: "default"` when priority
32
+ * capacity is exhausted), so pricing keys off the tier reported in the RESPONSE,
33
+ * which is what the normalizers read (see spec §5.1).
34
+ * @layer shared
35
+ */
36
+ /**
37
+ * Service tier of the actual response. `'batch'` is the Batch API (~50% discount);
38
+ * `'flex'` is OpenAI flex processing (batch rates, synchronous); `'priority'` is
39
+ * paid premium; `'standard'` is the default.
40
+ */
41
+ declare const SERVICE_TIERS: readonly ["standard", "batch", "flex", "priority"];
42
+ /** A response service tier. */
43
+ type ServiceTier = (typeof SERVICE_TIERS)[number];
44
+
45
+ /**
46
+ * @fileoverview Catalog of the token categories that are rated independently.
47
+ * Cache reads/writes are first-class because a naive input/output-only model
48
+ * over-bills cached traffic by up to 10× (see spec §5.4).
49
+ * @layer shared
50
+ */
51
+ /** Every token category that carries its own per-million rate. */
52
+ declare const TOKEN_CATEGORIES: readonly ["input", "output", "cacheRead", "cacheWrite5m", "cacheWrite1h", "reasoning", "audioIn", "audioOut", "imageIn", "imageOut"];
53
+ /** A single rated token category. */
54
+ type TokenCategory = (typeof TOKEN_CATEGORIES)[number];
55
+
56
+ /**
57
+ * @fileoverview Catalog of wallet entry kinds. Balances are the signed sum of
58
+ * these append-only entries (see spec §9.1).
59
+ * @layer shared
60
+ */
61
+ /** Every kind of entry that can appear in a wallet's append-only ledger. */
62
+ declare const WALLET_ENTRY_TYPES: readonly ["grant", "debit", "refund", "adjustment", "expiry"];
63
+ /** A wallet entry kind. */
64
+ type WalletEntryType = (typeof WALLET_ENTRY_TYPES)[number];
65
+
66
+ /**
67
+ * @fileoverview Canonical catalog types that the rest of the library is generic
68
+ * over: provider/operation/tier unions, the metering scope, the rating mode, and
69
+ * the pure normalizer/preset contracts. Framework-free.
70
+ * @layer shared
71
+ */
72
+
73
+ /**
74
+ * A provider identifier. Widened with `(string & {})` so custom
75
+ * OpenAI-compatible providers can register their own id while known ids keep
76
+ * editor autocomplete.
77
+ */
78
+ type ProviderId = KnownProviderId | (string & {});
79
+ /** The payer subject whose wallet/budget a call consumes. */
80
+ interface MeteringScope {
81
+ /** `'key'` scopes spend their owner's wallet — they cannot own money. */
82
+ type: 'tenant' | 'team' | 'user' | 'key';
83
+ id: string;
84
+ }
85
+
86
+ /**
87
+ * @fileoverview The immutable ledger row for one AI call (or its hold), its
88
+ * lifecycle status, the ledger query filter, and the append insert type.
89
+ * @layer shared
90
+ */
91
+
92
+ /** Lifecycle status of a usage record (see spec §8.3). */
93
+ type UsageStatus = 'pending' | 'posted' | 'reversed' | 'released';
94
+ /**
95
+ * One append-only ledger entry for a single AI call (or its hold). All money is
96
+ * integer nano-USD; token counts are plain `number`.
97
+ */
98
+ interface UsageRecord {
99
+ id: string;
100
+ tenantId: string;
101
+ /** The PAYER — the subject whose wallet/budget is consumed. */
102
+ scope: MeteringScope;
103
+ /** Optional distinct beneficiary (reporting dimension only). */
104
+ beneficiary?: MeteringScope;
105
+ /** Optional actor who triggered the call (audit). */
106
+ requestedBy?: string;
107
+ provider: ProviderId;
108
+ /** Model reported by the response (pricing follows this). */
109
+ model: string;
110
+ /** Model the host requested, when supplied (drift audit; §6.6). */
111
+ requestedModel?: string;
112
+ operation: AiOperation;
113
+ serviceTier: ServiceTier;
114
+ /** Caller-supplied logical operation, e.g. `'workout.generate'`. */
115
+ feature: string;
116
+ /** Free-form cost-attribution labels (≤ 10), e.g. `['team:research']`. */
117
+ tags: string[];
118
+ inputTokens: number;
119
+ outputTokens: number;
120
+ cacheReadTokens: number;
121
+ cacheWrite5mTokens: number;
122
+ cacheWrite1hTokens: number;
123
+ reasoningTokens: number;
124
+ audioInTokens: number;
125
+ audioOutTokens: number;
126
+ imageInTokens: number;
127
+ imageOutTokens: number;
128
+ totalTokens: number;
129
+ /** Non-token line items actually rated, e.g. `{ web_search_requests: 2 }`. */
130
+ extraUnits?: Record<string, number>;
131
+ /** `null` for provider-reported mode or a priceMissing record. */
132
+ priceVersionId: string | null;
133
+ /** Provider cost (tokens + surcharges). */
134
+ rawCostNanoUsd: bigint;
135
+ /** The non-token share of `rawCostNanoUsd`. */
136
+ surchargeNanoUsd: bigint;
137
+ /** After markup — what the customer pays. */
138
+ billedCostNanoUsd: bigint;
139
+ /** The resolved 4-dp markup value actually applied. */
140
+ markupMultiplier: number;
141
+ /** `'USD'` in v0.1 (§7.4). */
142
+ currency: string;
143
+ /** Rated at 0 in non-strict mode; a backfill target. */
144
+ priceMissing: boolean;
145
+ status: UsageStatus;
146
+ /** Set on a posted record when a compensating record reversed it (§8.5). */
147
+ reversedByRecordId?: string;
148
+ /** Set on a compensating record, pointing back at the record it negates. */
149
+ reversesRecordId?: string;
150
+ /** Unique per `(tenantId, idempotencyKey)`. */
151
+ idempotencyKey: string;
152
+ /** Ties an invoice line back to app logs. */
153
+ correlationId?: string;
154
+ /** Provider request id. */
155
+ requestId?: string;
156
+ /** Platform-absorbed — never consumes wallet/budget. */
157
+ isSystemCost: boolean;
158
+ /** e.g. `'workout_generation_retry'`. */
159
+ systemCostCategory?: string;
160
+ /** True when this record consumed wallet/budget (reconciliation predicate §10.7). */
161
+ enforced: boolean;
162
+ /** Previous chain hash (optional per-tenant hash chain, §8.6). */
163
+ prevHash?: string;
164
+ /** This record's chain hash (optional per-tenant hash chain, §8.6). */
165
+ hash?: string;
166
+ /** When the LLM call happened. */
167
+ occurredAt: Date;
168
+ createdAt: Date;
169
+ /** Pending-row settlements only. */
170
+ updatedAt: Date;
171
+ }
172
+ /**
173
+ * Caller-supplied fields for `ILedgerStore.append`. The store computes
174
+ * `id`/`hash`/`prevHash`/`createdAt`/`updatedAt`.
175
+ */
176
+ type NewUsageRecord = Omit<UsageRecord, 'id' | 'prevHash' | 'hash' | 'createdAt' | 'updatedAt'>;
177
+ /** Query filter for the ledger store. */
178
+ interface LedgerFilter {
179
+ tenantId: string;
180
+ scope?: MeteringScope;
181
+ beneficiary?: MeteringScope;
182
+ feature?: string;
183
+ features?: string[];
184
+ provider?: ProviderId;
185
+ model?: string;
186
+ operation?: AiOperation;
187
+ serviceTier?: ServiceTier;
188
+ tags?: string[];
189
+ isSystemCost?: boolean;
190
+ systemCostCategory?: string;
191
+ status?: UsageStatus[];
192
+ enforcedOnly?: boolean;
193
+ from?: Date;
194
+ to?: Date;
195
+ limit?: number;
196
+ offset?: number;
197
+ }
198
+
199
+ /**
200
+ * @fileoverview The effective-dated price row and its insert type. Rates are
201
+ * integer nano-USD per 1,000,000 tokens (or per unit for non-token line items),
202
+ * so every cost computation is exact integer math.
203
+ * @layer shared
204
+ */
205
+
206
+ /**
207
+ * One immutable, effective-dated price version for a (provider, model, operation,
208
+ * serviceTier) tuple. A call is rated at the row in effect at its timestamp.
209
+ */
210
+ interface PriceVersion {
211
+ id: string;
212
+ provider: ProviderId;
213
+ model: string;
214
+ operation: AiOperation;
215
+ /** Part of the resolution key; `'standard'` is the default. */
216
+ serviceTier: ServiceTier;
217
+ /** Rates as integer nano-USD per 1,000,000 tokens. */
218
+ inputNanoUsdPerMillion: bigint;
219
+ outputNanoUsdPerMillion: bigint;
220
+ cacheReadNanoUsdPerMillion: bigint;
221
+ cacheWrite5mNanoUsdPerMillion: bigint;
222
+ cacheWrite1hNanoUsdPerMillion: bigint;
223
+ /** Usually equals output; kept separate for models that price reasoning differently. */
224
+ reasoningNanoUsdPerMillion: bigint;
225
+ audioInNanoUsdPerMillion: bigint;
226
+ audioOutNanoUsdPerMillion: bigint;
227
+ imageInNanoUsdPerMillion: bigint;
228
+ imageOutNanoUsdPerMillion: bigint;
229
+ /**
230
+ * Long-context tier threshold: when total input (all input-side categories)
231
+ * exceeds this, the tier rates replace the base input/output rates for the
232
+ * WHOLE call (all-or-nothing, matching Gemini's billing).
233
+ */
234
+ tierThresholdTokens?: number;
235
+ tierInputNanoUsdPerMillion?: bigint;
236
+ tierOutputNanoUsdPerMillion?: bigint;
237
+ /**
238
+ * Non-token line items in nano-USD PER UNIT, matched against
239
+ * `NormalizedUsage.serverToolUse` / `MeteringContext.extraUnits` by EXACT key —
240
+ * so the key must equal the count key the normalizer emits (e.g. Anthropic's
241
+ * `usage.server_tool_use.web_search_requests`) — e.g.
242
+ * `{ web_search_requests: 10_000_000n }` ($0.01 per web-search call).
243
+ */
244
+ unitRates?: Record<string, bigint>;
245
+ currency: 'USD';
246
+ effectiveFrom: Date;
247
+ /** `null` = current; set when a newer row supersedes it. */
248
+ effectiveTo: Date | null;
249
+ /** Provenance: `'snapshot' | 'manual' | 'import'`. */
250
+ source: string;
251
+ }
252
+ /**
253
+ * Caller-supplied fields for `upsertPrice`. Rates default to `0n` and
254
+ * `serviceTier` to `'standard'`; the store assigns `id` and closes the previous
255
+ * open row's `effectiveTo`.
256
+ */
257
+ type NewPriceVersion = Partial<Omit<PriceVersion, 'id' | 'effectiveTo'>> & Pick<PriceVersion, 'provider' | 'model' | 'operation'>;
258
+
259
+ /**
260
+ * @fileoverview Wallet types: the owner reference, the materialized wallet, and
261
+ * the append-only entry ledger whose signed sum is the balance (see spec §9.1).
262
+ * @layer shared
263
+ */
264
+
265
+ /**
266
+ * Identifies the owner of a wallet. `'key'` scopes deliberately cannot own money
267
+ * — API keys spend their owner's wallet.
268
+ */
269
+ interface WalletRef {
270
+ tenantId: string;
271
+ ownerType: 'tenant' | 'team' | 'user';
272
+ ownerId: string;
273
+ }
274
+ /** A prepaid credit wallet with a materialized nano-USD balance. */
275
+ interface Wallet {
276
+ id: string;
277
+ tenantId: string;
278
+ ownerType: WalletRef['ownerType'];
279
+ ownerId: string;
280
+ /** Materialized, kept transactionally consistent with Σ entries. */
281
+ balanceNanoUsd: bigint;
282
+ createdAt: Date;
283
+ updatedAt: Date;
284
+ }
285
+ /** One append-only entry in a wallet's ledger. */
286
+ interface WalletEntry {
287
+ id: string;
288
+ walletId: string;
289
+ type: WalletEntryType;
290
+ /** `+` = credit, `−` = debit. */
291
+ amountNanoUsd: bigint;
292
+ /** Grant burn priority (lower first). */
293
+ priority: number;
294
+ effectiveAt: Date;
295
+ /** Grants only. */
296
+ expiresAt?: Date;
297
+ /** Present on usage-driven debits/refunds. */
298
+ usageRecordId?: string;
299
+ /** Unique per wallet. */
300
+ idempotencyKey: string;
301
+ reason?: string;
302
+ createdAt: Date;
303
+ }
304
+ /** Caller-supplied fields for `IWalletStore.appendEntry`. The store assigns `id`/`createdAt`. */
305
+ type NewWalletEntry = Omit<WalletEntry, 'id' | 'createdAt'>;
306
+
307
+ /**
308
+ * @fileoverview Budget types: the budget definition, its window/policy unions,
309
+ * and the read-side status DTOs a usage meter renders (see spec §10).
310
+ * @layer shared
311
+ */
312
+
313
+ /** How a budget's window repeats. `{ customSeconds }` is a fixed-length rolling window. */
314
+ type BudgetWindowKind = 'day' | 'week' | 'month' | 'total' | {
315
+ customSeconds: number;
316
+ };
317
+ /** What happens when a budget is exceeded. */
318
+ type BudgetPolicy = 'block' | 'throttle' | 'allow';
319
+ /** A spend/token/count cap for a scope over a window. */
320
+ interface Budget {
321
+ id: string;
322
+ tenantId: string;
323
+ scope: MeteringScope;
324
+ /** Restrict which usage counts; empty/absent = all features. */
325
+ features?: string[];
326
+ /** Billed-spend cap. */
327
+ limitNanoUsd?: bigint;
328
+ /** Total-token cap. */
329
+ limitTokens?: number;
330
+ /** Operation-count cap (posted, enforced, non-reversed records). */
331
+ limitCount?: number;
332
+ window: BudgetWindowKind;
333
+ /**
334
+ * Per-budget window anchor. Absent → calendar UTC anchoring. Month windows
335
+ * clamp short months (a Jan 31 anchor yields Feb 28/29, Mar 31, …).
336
+ */
337
+ anchorAt?: Date;
338
+ /** Optional budget lifetime: enforcement ignores the budget after this instant. */
339
+ expiresAt?: Date;
340
+ /** Fractions of the limit that trigger soft alert events. */
341
+ softThresholds: number[];
342
+ policy: BudgetPolicy;
343
+ createdAt: Date;
344
+ }
345
+
346
+ /**
347
+ * @fileoverview Reporting types: the query filter and the aggregated usage
348
+ * summary (including the cache-savings figure) produced by `UsageReportService`.
349
+ * @layer shared
350
+ */
351
+
352
+ /** One aggregated row of a usage report. */
353
+ interface UsageSummary {
354
+ /** groupBy key → value; empty groupBy = one grand-total row. */
355
+ group: Record<string, string>;
356
+ records: number;
357
+ totalTokens: number;
358
+ tokens: Record<TokenCategory, number>;
359
+ rawCostNanoUsd: bigint;
360
+ surchargeNanoUsd: bigint;
361
+ billedCostNanoUsd: bigint;
362
+ /** Σ cacheReadTokens × (inputRate − cacheReadRate) — the "you saved $X via caching" figure. */
363
+ cacheSavingsNanoUsd: bigint;
364
+ }
365
+
366
+ /**
367
+ * @fileoverview The budget persistence port (spec §15.1). Multi-dimension
368
+ * budgets with atomic conditional consume, signed window adjustment, and window
369
+ * rotation. Only validated at init when the budget feature is enabled.
370
+ * @layer server
371
+ */
372
+
373
+ /** A signed or absolute per-window spend delta across the three budget dimensions. */
374
+ interface BudgetDelta {
375
+ nanoUsd: bigint;
376
+ tokens: number;
377
+ count: number;
378
+ }
379
+ /** Limits checked by {@link IBudgetStore.conditionalConsume} (absent = unlimited). */
380
+ interface BudgetLimits {
381
+ nanoUsd?: bigint;
382
+ tokens?: number;
383
+ count?: number;
384
+ }
385
+ /** A window's materialized spend counters. */
386
+ interface BudgetWindowSpend {
387
+ spentNanoUsd: bigint;
388
+ spentTokens: number;
389
+ spentCount: number;
390
+ }
391
+ /** The budget port. */
392
+ interface IBudgetStore {
393
+ /** Create or replace a budget. */
394
+ upsert(budget: Omit<Budget, 'id' | 'createdAt'> & {
395
+ id?: string;
396
+ }): Promise<Budget>;
397
+ /** Delete a budget. */
398
+ remove(budgetId: string): Promise<void>;
399
+ /** Load one budget by id — the rotateWindow/reconcileWindow paths read it by id (§10.5). */
400
+ findBudgetById(budgetId: string): Promise<Budget | null>;
401
+ /**
402
+ * Every budget for the tenant scoped to the EXACT scope or tenant-wide (§10.3). A
403
+ * single {@link MeteringScope} carries no team/user lineage, so the tenant is the
404
+ * only ancestor resolvable — intermediate ancestors are not matched. Each budget
405
+ * returned consumes independently; a subject-level budget never exempts the tenant cap.
406
+ */
407
+ findMatching(tenantId: string, scope: MeteringScope): Promise<Budget[]>;
408
+ /**
409
+ * Atomic multi-dimension conditional consume (§10.8). `false` = a limit would be
410
+ * exceeded. Creates the window row on first touch.
411
+ */
412
+ conditionalConsume(budgetId: string, windowStart: Date, delta: BudgetDelta, limits: BudgetLimits): Promise<boolean>;
413
+ /** Signed release/adjust of a window's counters (capture delta, release, reverse). */
414
+ adjustWindow(budgetId: string, windowStart: Date, delta: BudgetDelta): Promise<void>;
415
+ /** A window's current spend, or `null` when it has no row yet. */
416
+ getWindow(budgetId: string, windowStart: Date): Promise<BudgetWindowSpend | null>;
417
+ /** Move a budget's active window start (rotateWindow support). */
418
+ setWindowStart(budgetId: string, windowStart: Date): Promise<void>;
419
+ }
420
+
421
+ /**
422
+ * @fileoverview The ledger persistence port (spec §15.1). The append-only usage
423
+ * ledger: idempotent append, atomic state transitions, and aggregate queries.
424
+ * Implemented by the official Prisma adapter or a custom store.
425
+ * @layer server
426
+ */
427
+
428
+ /** Aggregate cost totals returned by {@link ILedgerStore.sumCost}. */
429
+ interface LedgerCostSummary {
430
+ rawCostNanoUsd: bigint;
431
+ billedCostNanoUsd: bigint;
432
+ surchargeNanoUsd: bigint;
433
+ totalTokens: number;
434
+ records: number;
435
+ }
436
+ /** A report group-by dimension (spec §13.1). */
437
+ type ReportGroupBy = 'day' | 'week' | 'month' | 'feature' | 'provider' | 'model' | 'operation' | 'serviceTier' | 'scope' | 'beneficiary' | 'tag' | 'systemCostCategory';
438
+ /** The append-only usage ledger port. */
439
+ interface ILedgerStore {
440
+ /**
441
+ * Upsert on `(tenantId, idempotencyKey)` — a replay returns the existing record
442
+ * iff the payload hash matches; a different hash is a conflict (§8.4). When
443
+ * `hashChain` is true and the record is `posted`, the store reads the tenant's
444
+ * last chain hash, computes this record's `prevHash`/`hash`, and persists them
445
+ * ATOMICALLY under a per-tenant lock (§8.6) — the read-compute-write is one
446
+ * serialized operation so concurrent appends never fork the chain. When false
447
+ * (the default), no chain lookup or hashing occurs.
448
+ */
449
+ append(record: NewUsageRecord, payloadHash: string, hashChain?: boolean): Promise<UsageRecord>;
450
+ /**
451
+ * State transitions only (§8.3): `pending→posted` (settle, amounts patched),
452
+ * `pending→released`, `posted→reversed` (annotation only — amount fields
453
+ * rejected). Atomic: returns `null` when the record was not in the expected
454
+ * source state (how exactly one reaper replica wins an expired hold). When
455
+ * `hashChain` is true and `to` is `posted` (settlement), the store computes and
456
+ * persists the record's chain `prevHash`/`hash` under the same per-tenant lock (§8.6).
457
+ */
458
+ transition(id: string, from: UsageStatus, to: UsageStatus, patch?: Partial<UsageRecord>, hashChain?: boolean): Promise<UsageRecord | null>;
459
+ /** Look up a record by its per-tenant idempotency key. */
460
+ findByIdempotencyKey(tenantId: string, key: string): Promise<UsageRecord | null>;
461
+ /**
462
+ * Load one record by its global id — the reversal path loads the original to
463
+ * negate it into a compensating record (§8.5). Returns `null` when absent.
464
+ */
465
+ findById(id: string): Promise<UsageRecord | null>;
466
+ /** Find pending holds older than `olderThan` for the reaper sweep. */
467
+ findExpiredHolds(olderThan: Date, limit: number): Promise<UsageRecord[]>;
468
+ /** Query records by filter. */
469
+ query(filter: LedgerFilter): Promise<UsageRecord[]>;
470
+ /** Aggregate cost/token totals for a filter. */
471
+ sumCost(filter: LedgerFilter): Promise<LedgerCostSummary>;
472
+ /**
473
+ * OPTIONAL scalable aggregation (spec §13.1): `SUM … GROUP BY` across the report
474
+ * dimensions, computed in the store (the official Prisma adapter uses SQL). A
475
+ * store that omits it falls back to `UsageReportService`'s documented
476
+ * query-and-aggregate-in-memory path, capped at `reporting.maxExportRows`.
477
+ */
478
+ summarize?(filter: LedgerFilter, groupBy: ReportGroupBy[]): Promise<UsageSummary[]>;
479
+ /**
480
+ * The last posted record's hash for a tenant (hash-chain continuation, §8.6).
481
+ * The chain is serialized per tenant: the official Prisma adapter takes a
482
+ * Postgres advisory lock so the last-hash read and the chained append/settle run
483
+ * as one atomic operation; an in-memory store simulates this with a per-tenant
484
+ * mutex. Returns `null` before the tenant's first chained record.
485
+ */
486
+ lastHash(tenantId: string): Promise<string | null>;
487
+ }
488
+
489
+ /**
490
+ * @fileoverview The pricing persistence port (spec §15.1). Effective-dated rate
491
+ * resolution, append-only price upserts, and the model list that powers §6.6
492
+ * prefix matching.
493
+ * @layer server
494
+ */
495
+
496
+ /** A distinct priced model tuple, returned by {@link IPricingStore.listModels}. */
497
+ interface PricedModel {
498
+ model: string;
499
+ operation: AiOperation;
500
+ serviceTier: ServiceTier;
501
+ }
502
+ /** The effective-dated price registry port. */
503
+ interface IPricingStore {
504
+ /** The price row in effect at `at` for the tuple, or `null` when none matches. */
505
+ resolveRate(provider: ProviderId, model: string, operation: AiOperation, serviceTier: ServiceTier, at: Date): Promise<PriceVersion | null>;
506
+ /** Close the current open row (`effectiveTo = now`) and insert the new open row. */
507
+ upsertPrice(input: NewPriceVersion): Promise<PriceVersion>;
508
+ /** Full effective-dated history for a tuple (newest first). */
509
+ getPriceHistory(provider: ProviderId, model: string, operation: AiOperation, serviceTier?: ServiceTier): Promise<PriceVersion[]>;
510
+ /** All distinct (model, operation, serviceTier) for a provider — powers §6.6 prefix matching. */
511
+ listModels(provider: ProviderId): Promise<PricedModel[]>;
512
+ }
513
+
514
+ /**
515
+ * @fileoverview The wallet persistence port (spec §15.1). Materialized-balance
516
+ * wallets with atomic conditional debit, grant allocation, and reconciliation.
517
+ * Only validated at init when the wallet feature is enabled.
518
+ * @layer server
519
+ */
520
+
521
+ /** An open grant with its remaining spendable value. */
522
+ type OpenGrant = WalletEntry & {
523
+ remainingNanoUsd: bigint;
524
+ };
525
+ /** A page of wallet entries with the total count. */
526
+ interface WalletEntryPage {
527
+ entries: WalletEntry[];
528
+ total: number;
529
+ }
530
+ /** Filter for {@link IWalletStore.listEntries}. */
531
+ interface WalletEntryFilter {
532
+ from?: Date;
533
+ to?: Date;
534
+ type?: WalletEntryType;
535
+ limit?: number;
536
+ offset?: number;
537
+ }
538
+ /** The prepaid wallet port. */
539
+ interface IWalletStore {
540
+ /** The wallet for a ref, or `null` when it does not exist yet. */
541
+ getWallet(ref: WalletRef): Promise<Wallet | null>;
542
+ /**
543
+ * Create the wallet when missing (idempotent) and append the entry plus any
544
+ * grant allocations in one transaction.
545
+ */
546
+ appendEntry(ref: WalletRef, entry: NewWalletEntry, allocations?: {
547
+ grantEntryId: string;
548
+ amountNanoUsd: bigint;
549
+ }[]): Promise<WalletEntry>;
550
+ /** Atomic conditional debit against the materialized balance. `null` = insufficient (§9.4). */
551
+ conditionalDebit(ref: WalletRef, entry: NewWalletEntry, overdraftNanoUsd: bigint): Promise<WalletEntry | null>;
552
+ /** Open grants with remaining value, ordered per `order` — feeds allocation. */
553
+ openGrants(ref: WalletRef, order: 'expiry' | 'priority' | 'fifo'): Promise<OpenGrant[]>;
554
+ /** A page of entries for a ref. */
555
+ listEntries(ref: WalletRef, filter?: WalletEntryFilter): Promise<WalletEntryPage>;
556
+ /** Recompute the materialized balance from Σ entries. */
557
+ reconcile(ref: WalletRef): Promise<Wallet>;
558
+ }
559
+
560
+ /**
561
+ * @fileoverview The aggregate persistence bundle (spec §4.1). Ledger + pricing
562
+ * are always required; the wallet and budget halves are `Partial` and validated
563
+ * at init only when the corresponding feature is configured.
564
+ * @layer server
565
+ */
566
+
567
+ /**
568
+ * The single store object a host provides. `forRoot()` fans it out under each
569
+ * per-port DI token; a host may override any individual port by binding its token
570
+ * (§4.6).
571
+ */
572
+ interface IAiTokensStore extends ILedgerStore, IPricingStore, Partial<IWalletStore>, Partial<IBudgetStore> {
573
+ }
574
+
575
+ /**
576
+ * @fileoverview `PrismaAiTokensStore` — the official PostgreSQL adapter (spec
577
+ * §15.1–15.3). It implements the ledger + pricing halves of `IAiTokensStore`
578
+ * against any `@prisma/client` instance, talking to PostgreSQL EXCLUSIVELY through
579
+ * PARAMETERIZED raw SQL (`$queryRaw`/`$executeRaw`/`$transaction`) so it never
580
+ * depends on generated model delegates and stays valid against the host's schema.
581
+ * Column identifiers are hardcoded constants (never interpolated from input) and
582
+ * every value is a bound parameter, so the raw SQL is injection-safe. Money is
583
+ * `BIGINT` nano-USD; `unitRates` round-trip as JSON of decimal strings; the
584
+ * `markupMultiplier` `Decimal(10,4)` maps to a `number` at the boundary (exact by
585
+ * the 4-dp rule, §7.2). Store errors map to the domain catalog (§15.2); a payload
586
+ * hash mismatch on the `(tenantId, idempotencyKey)` unique index becomes the
587
+ * exactly-once conflict, and unknown driver errors become `AI_TOKENS_STORE_ERROR`
588
+ * (connection details are never leaked). Row shapes, mappers, and SQL builders live
589
+ * in `./adapter-sql`; the wallet/budget halves use the same conditional-write
590
+ * discipline as the ledger half (§9.4, §10.8).
591
+ * @layer prisma
592
+ */
593
+
594
+ /**
595
+ * The official PostgreSQL adapter. Construct it with the host's `PrismaClient`
596
+ * (the host's schema must include the shipped models, §15.3).
597
+ */
598
+ declare class PrismaAiTokensStore implements IAiTokensStore {
599
+ private readonly prisma;
600
+ private readonly burnOrder;
601
+ /**
602
+ * @param prisma The host's Prisma client (talks to PostgreSQL).
603
+ * @param options Optional wallet grant burn order (must match the module's `wallets.burnOrder`; default `'expiry'`).
604
+ */
605
+ constructor(prisma: PrismaClient, options?: {
606
+ burnOrder?: 'expiry' | 'priority' | 'fifo';
607
+ });
608
+ append(record: NewUsageRecord, payloadHash: string, hashChain?: boolean): Promise<UsageRecord>;
609
+ transition(id: string, from: UsageStatus, to: UsageStatus, patch?: Partial<UsageRecord>, hashChain?: boolean): Promise<UsageRecord | null>;
610
+ findByIdempotencyKey(tenantId: string, key: string): Promise<UsageRecord | null>;
611
+ findById(id: string): Promise<UsageRecord | null>;
612
+ findExpiredHolds(olderThan: Date, limit: number): Promise<UsageRecord[]>;
613
+ query(filter: LedgerFilter): Promise<UsageRecord[]>;
614
+ sumCost(filter: LedgerFilter): Promise<LedgerCostSummary>;
615
+ lastHash(tenantId: string): Promise<string | null>;
616
+ resolveRate(provider: ProviderId, model: string, operation: AiOperation, serviceTier: ServiceTier, at: Date): Promise<PriceVersion | null>;
617
+ upsertPrice(input: NewPriceVersion): Promise<PriceVersion>;
618
+ getPriceHistory(provider: ProviderId, model: string, operation: AiOperation, serviceTier?: ServiceTier): Promise<PriceVersion[]>;
619
+ listModels(provider: ProviderId): Promise<PricedModel[]>;
620
+ /** Advisory seed lock (§6.4): the first caller wins; concurrent boots seed exactly once. */
621
+ acquireSeedLock(key: string): Promise<boolean>;
622
+ /** Insert one usage row and return it; shared by the plain and chained paths. */
623
+ private insert;
624
+ /** Insert a `posted` row under the per-tenant chain lock (§8.6). */
625
+ private insertChained;
626
+ /** Settle a hold `pending → posted`, computing its chain hash under the lock (§8.6). */
627
+ private settleChained;
628
+ /** Atomic status transition via a conditional `UPDATE`; 0 rows → `null` (§8.3). */
629
+ private updateStatus;
630
+ /** The tenant's last chain hash (most recently created settled row). */
631
+ private lastHashWithin;
632
+ getWallet(ref: WalletRef): Promise<Wallet | null>;
633
+ appendEntry(ref: WalletRef, entry: NewWalletEntry, allocations?: {
634
+ grantEntryId: string;
635
+ amountNanoUsd: bigint;
636
+ }[]): Promise<WalletEntry>;
637
+ conditionalDebit(ref: WalletRef, entry: NewWalletEntry, overdraftNanoUsd: bigint): Promise<WalletEntry | null>;
638
+ openGrants(ref: WalletRef, order: 'expiry' | 'priority' | 'fifo'): Promise<OpenGrant[]>;
639
+ listEntries(ref: WalletRef, filter?: WalletEntryFilter): Promise<WalletEntryPage>;
640
+ reconcile(ref: WalletRef): Promise<Wallet>;
641
+ upsert(input: Omit<Budget, 'id' | 'createdAt'> & {
642
+ id?: string;
643
+ }): Promise<Budget>;
644
+ remove(budgetId: string): Promise<void>;
645
+ findBudgetById(budgetId: string): Promise<Budget | null>;
646
+ findMatching(tenantId: string, scope: MeteringScope): Promise<Budget[]>;
647
+ conditionalConsume(budgetId: string, windowStart: Date, delta: BudgetDelta, limits: BudgetLimits): Promise<boolean>;
648
+ adjustWindow(budgetId: string, windowStart: Date, delta: BudgetDelta): Promise<void>;
649
+ getWindow(budgetId: string, windowStart: Date): Promise<BudgetWindowSpend | null>;
650
+ setWindowStart(budgetId: string, windowStart: Date): Promise<void>;
651
+ /** Load the wallet within a transaction (existence + id for the conditional debit). */
652
+ private getWalletWithin;
653
+ /** Fetch or (optionally) create the wallet for a ref within a transaction. */
654
+ private ensureWallet;
655
+ /** Insert a wallet entry, returning the stored row. */
656
+ private insertWalletEntry;
657
+ /** Insert the debit→grant allocation trail. */
658
+ private insertAllocations;
659
+ /** Apply a signed delta to the materialized balance. */
660
+ private applyBalanceDelta;
661
+ /** Write an `expiry` entry (with its allocation) for each expired grant with a remainder. */
662
+ private sweepExpiredGrants;
663
+ /** Greedily allocate a debit of `cost` across the open grants in burn order. */
664
+ private burnDown;
665
+ /** The open-grants query (remaining > 0, effective, not expired) ordered per burn order. */
666
+ private openGrantsSql;
667
+ /** Return the existing entry on a matching replay, throw the conflict, or `undefined` when new. */
668
+ private walletReplay;
669
+ /** Ensure a (zeroed) window row exists for first-touch consumption. */
670
+ private ensureWindow;
671
+ /** Map the P2002 replay-or-conflict path for a wallet entry (§15.2). */
672
+ private replayOrConflictWallet;
673
+ /** Return the existing record on a matching payload replay, else raise the conflict (§15.2). */
674
+ private replayOrConflict;
675
+ }
676
+
677
+ export { PrismaAiTokensStore };