@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,1044 @@
1
+ /**
2
+ * @fileoverview Catalog of the AI providers this library recognizes out of the
3
+ * box. Custom OpenAI-compatible providers register their own id at runtime via
4
+ * `providerPresets.openaiCompatible(id)`, so the public {@link ProviderId} type
5
+ * (see `types/catalogs.ts`) widens this closed list with `(string & {})`.
6
+ * @layer shared
7
+ */
8
+ /**
9
+ * The provider identifiers shipped with the library. Frozen tuple so the derived
10
+ * {@link KnownProviderId} union stays in sync with the runtime values.
11
+ */
12
+ declare const PROVIDER_IDS: readonly ["openai", "azure-openai", "anthropic", "gemini", "vertex", "mistral", "bedrock", "openrouter", "deepseek", "xai", "groq"];
13
+ /** A provider id known to the library at build time (excludes custom ids). */
14
+ type KnownProviderId = (typeof PROVIDER_IDS)[number];
15
+
16
+ /**
17
+ * @fileoverview Catalog of AI operation kinds a usage record can describe. The
18
+ * operation is part of the price-resolution key; `'responses'` bills identically
19
+ * to `'chat'` and shares its price rows (see spec §5.1).
20
+ * @layer shared
21
+ */
22
+ /** Every operation kind the library can meter and price. */
23
+ declare const AI_OPERATIONS: readonly ["chat", "responses", "embeddings", "image", "video", "audio", "rerank", "moderation"];
24
+ /** A logical AI operation kind. */
25
+ type AiOperation = (typeof AI_OPERATIONS)[number];
26
+
27
+ /**
28
+ * @fileoverview Catalog of response service tiers. Providers may silently
29
+ * downgrade a request (OpenAI returns `service_tier: "default"` when priority
30
+ * capacity is exhausted), so pricing keys off the tier reported in the RESPONSE,
31
+ * which is what the normalizers read (see spec §5.1).
32
+ * @layer shared
33
+ */
34
+ /**
35
+ * Service tier of the actual response. `'batch'` is the Batch API (~50% discount);
36
+ * `'flex'` is OpenAI flex processing (batch rates, synchronous); `'priority'` is
37
+ * paid premium; `'standard'` is the default.
38
+ */
39
+ declare const SERVICE_TIERS: readonly ["standard", "batch", "flex", "priority"];
40
+ /** A response service tier. */
41
+ type ServiceTier = (typeof SERVICE_TIERS)[number];
42
+
43
+ /**
44
+ * @fileoverview Catalog of the token categories that are rated independently.
45
+ * Cache reads/writes are first-class because a naive input/output-only model
46
+ * over-bills cached traffic by up to 10× (see spec §5.4).
47
+ * @layer shared
48
+ */
49
+ /** Every token category that carries its own per-million rate. */
50
+ declare const TOKEN_CATEGORIES: readonly ["input", "output", "cacheRead", "cacheWrite5m", "cacheWrite1h", "reasoning", "audioIn", "audioOut", "imageIn", "imageOut"];
51
+ /** A single rated token category. */
52
+ type TokenCategory = (typeof TOKEN_CATEGORIES)[number];
53
+
54
+ /**
55
+ * @fileoverview Catalog of wallet entry kinds. Balances are the signed sum of
56
+ * these append-only entries (see spec §9.1).
57
+ * @layer shared
58
+ */
59
+ /** Every kind of entry that can appear in a wallet's append-only ledger. */
60
+ declare const WALLET_ENTRY_TYPES: readonly ["grant", "debit", "refund", "adjustment", "expiry"];
61
+ /** A wallet entry kind. */
62
+ type WalletEntryType = (typeof WALLET_ENTRY_TYPES)[number];
63
+
64
+ /**
65
+ * @fileoverview The exhaustive catalog of error codes the library raises. Keys
66
+ * and values are identical strings so `keyof typeof AI_TOKENS_ERROR_CODES` is the
67
+ * canonical code union that the message/status maps (server layer) are typed
68
+ * against — the compiler then enforces exhaustive coverage (see spec §16).
69
+ * @layer shared
70
+ */
71
+ /** Every error code the library can raise, mapped to itself for value access. */
72
+ declare const AI_TOKENS_ERROR_CODES: {
73
+ readonly AI_TOKENS_NOT_CONFIGURED: "AI_TOKENS_NOT_CONFIGURED";
74
+ readonly AI_TOKENS_INVALID_CONFIG: "AI_TOKENS_INVALID_CONFIG";
75
+ readonly AI_TOKENS_UNKNOWN_PROVIDER: "AI_TOKENS_UNKNOWN_PROVIDER";
76
+ readonly AI_TOKENS_USAGE_MALFORMED: "AI_TOKENS_USAGE_MALFORMED";
77
+ readonly AI_TOKENS_PRICE_NOT_FOUND: "AI_TOKENS_PRICE_NOT_FOUND";
78
+ readonly AI_TOKENS_FX_REQUIRED: "AI_TOKENS_FX_REQUIRED";
79
+ readonly AI_TOKENS_BUDGET_EXCEEDED: "AI_TOKENS_BUDGET_EXCEEDED";
80
+ readonly AI_TOKENS_QUOTA_EXCEEDED: "AI_TOKENS_QUOTA_EXCEEDED";
81
+ readonly AI_TOKENS_INSUFFICIENT_CREDITS: "AI_TOKENS_INSUFFICIENT_CREDITS";
82
+ readonly AI_TOKENS_HOLD_NOT_FOUND: "AI_TOKENS_HOLD_NOT_FOUND";
83
+ readonly AI_TOKENS_HOLD_EXPIRED: "AI_TOKENS_HOLD_EXPIRED";
84
+ readonly AI_TOKENS_HOLD_ALREADY_SETTLED: "AI_TOKENS_HOLD_ALREADY_SETTLED";
85
+ readonly AI_TOKENS_IDEMPOTENCY_CONFLICT: "AI_TOKENS_IDEMPOTENCY_CONFLICT";
86
+ readonly AI_TOKENS_STREAM_USAGE_MISSING: "AI_TOKENS_STREAM_USAGE_MISSING";
87
+ readonly AI_TOKENS_STORE_ERROR: "AI_TOKENS_STORE_ERROR";
88
+ };
89
+ /** A library error code. */
90
+ type AiTokensErrorCode = keyof typeof AI_TOKENS_ERROR_CODES;
91
+
92
+ /**
93
+ * @fileoverview The canonical usage shape every provider normalizer produces.
94
+ * Downstream pricing, ledger, and budget logic is generic over this type, so it
95
+ * never sees a provider-specific payload.
96
+ * @layer shared
97
+ */
98
+
99
+ /**
100
+ * Provider-agnostic, per-call token and cost usage. All token counts are plain
101
+ * `number` (well under 2^53 per record); money is `bigint` nano-USD.
102
+ */
103
+ interface NormalizedUsage {
104
+ provider: ProviderId;
105
+ /** Model id as reported by the RESPONSE (may be a dated snapshot — see spec §6.6). */
106
+ model: string;
107
+ operation: AiOperation;
108
+ /** Tier reported by the response; absent → `'standard'`. */
109
+ serviceTier?: ServiceTier;
110
+ /** Uncached, non-reasoning input tokens (after the last cache breakpoint). */
111
+ inputTokens: number;
112
+ /**
113
+ * Output/completion tokens EXCLUDING reasoning tokens (§5.5 invariant).
114
+ * Anthropic folds thinking into output with no sub-field, so its adapter leaves
115
+ * `reasoningTokens` at 0 and keeps everything here.
116
+ */
117
+ outputTokens: number;
118
+ /** Tokens served from a cache hit (billed at a fraction of input). */
119
+ cacheReadTokens: number;
120
+ /** Tokens written to a 5-minute cache (Anthropic 1.25× input; Bedrock cacheWrite). */
121
+ cacheWrite5mTokens: number;
122
+ /** Tokens written to a 1-hour cache (Anthropic 2× input). */
123
+ cacheWrite1hTokens: number;
124
+ /** Reasoning/thinking tokens reported separately (OpenAI, Gemini). */
125
+ reasoningTokens: number;
126
+ /** Audio input tokens (multimodal). */
127
+ audioInTokens: number;
128
+ /** Audio output tokens (multimodal). */
129
+ audioOutTokens: number;
130
+ /** Image input tokens (multimodal). */
131
+ imageInTokens: number;
132
+ /** Image output tokens (multimodal). */
133
+ imageOutTokens: number;
134
+ /**
135
+ * Server-side tool usage counts — NON-token line items rated via
136
+ * `PriceVersion.unitRates` (§6.2), e.g. `{ web_search_requests: 2 }`. Anthropic
137
+ * reports these in `usage.server_tool_use`; for OpenAI/Gemini the host passes
138
+ * counts via `MeteringContext.extraUnits`.
139
+ */
140
+ serverToolUse?: Record<string, number>;
141
+ /** Provider-reported cost in nano-USD when available (OpenRouter `usage.cost`). */
142
+ providerReportedCostNanoUsd?: bigint;
143
+ /** Unclassified fields preserved verbatim for audit. */
144
+ raw?: Record<string, unknown>;
145
+ }
146
+
147
+ /**
148
+ * @fileoverview Canonical catalog types that the rest of the library is generic
149
+ * over: provider/operation/tier unions, the metering scope, the rating mode, and
150
+ * the pure normalizer/preset contracts. Framework-free.
151
+ * @layer shared
152
+ */
153
+
154
+ /**
155
+ * A provider identifier. Widened with `(string & {})` so custom
156
+ * OpenAI-compatible providers can register their own id while known ids keep
157
+ * editor autocomplete.
158
+ */
159
+ type ProviderId = KnownProviderId | (string & {});
160
+ /**
161
+ * How a call is rated. `'rate-table'` computes cost from the price registry;
162
+ * `'provider-reported'` trusts `NormalizedUsage.providerReportedCostNanoUsd`.
163
+ */
164
+ type RatingMode = 'rate-table' | 'provider-reported';
165
+ /** The payer subject whose wallet/budget a call consumes. */
166
+ interface MeteringScope {
167
+ /** `'key'` scopes spend their owner's wallet — they cannot own money. */
168
+ type: 'tenant' | 'team' | 'user' | 'key';
169
+ id: string;
170
+ }
171
+ /** The discriminant of {@link MeteringScope}. */
172
+ type ScopeType = MeteringScope['type'];
173
+ /**
174
+ * A pure function mapping one provider's raw `usage` object into the canonical
175
+ * {@link NormalizedUsage} shape. Consumes plain objects — never a provider SDK.
176
+ */
177
+ type UsageNormalizer = (raw: unknown) => NormalizedUsage;
178
+ /**
179
+ * Pairs a normalizer with the right provider id and rating mode. Passed in
180
+ * `MeteringContext.preset` and consumed by `meter()`, the interceptor, and
181
+ * `record()`.
182
+ */
183
+ interface ProviderPreset {
184
+ provider: ProviderId;
185
+ normalizer: UsageNormalizer;
186
+ ratingMode: RatingMode;
187
+ }
188
+
189
+ /**
190
+ * @fileoverview The immutable ledger row for one AI call (or its hold), its
191
+ * lifecycle status, the ledger query filter, and the append insert type.
192
+ * @layer shared
193
+ */
194
+
195
+ /** Lifecycle status of a usage record (see spec §8.3). */
196
+ type UsageStatus = 'pending' | 'posted' | 'reversed' | 'released';
197
+ /**
198
+ * One append-only ledger entry for a single AI call (or its hold). All money is
199
+ * integer nano-USD; token counts are plain `number`.
200
+ */
201
+ interface UsageRecord {
202
+ id: string;
203
+ tenantId: string;
204
+ /** The PAYER — the subject whose wallet/budget is consumed. */
205
+ scope: MeteringScope;
206
+ /** Optional distinct beneficiary (reporting dimension only). */
207
+ beneficiary?: MeteringScope;
208
+ /** Optional actor who triggered the call (audit). */
209
+ requestedBy?: string;
210
+ provider: ProviderId;
211
+ /** Model reported by the response (pricing follows this). */
212
+ model: string;
213
+ /** Model the host requested, when supplied (drift audit; §6.6). */
214
+ requestedModel?: string;
215
+ operation: AiOperation;
216
+ serviceTier: ServiceTier;
217
+ /** Caller-supplied logical operation, e.g. `'workout.generate'`. */
218
+ feature: string;
219
+ /** Free-form cost-attribution labels (≤ 10), e.g. `['team:research']`. */
220
+ tags: string[];
221
+ inputTokens: number;
222
+ outputTokens: number;
223
+ cacheReadTokens: number;
224
+ cacheWrite5mTokens: number;
225
+ cacheWrite1hTokens: number;
226
+ reasoningTokens: number;
227
+ audioInTokens: number;
228
+ audioOutTokens: number;
229
+ imageInTokens: number;
230
+ imageOutTokens: number;
231
+ totalTokens: number;
232
+ /** Non-token line items actually rated, e.g. `{ web_search_requests: 2 }`. */
233
+ extraUnits?: Record<string, number>;
234
+ /** `null` for provider-reported mode or a priceMissing record. */
235
+ priceVersionId: string | null;
236
+ /** Provider cost (tokens + surcharges). */
237
+ rawCostNanoUsd: bigint;
238
+ /** The non-token share of `rawCostNanoUsd`. */
239
+ surchargeNanoUsd: bigint;
240
+ /** After markup — what the customer pays. */
241
+ billedCostNanoUsd: bigint;
242
+ /** The resolved 4-dp markup value actually applied. */
243
+ markupMultiplier: number;
244
+ /** `'USD'` in v0.1 (§7.4). */
245
+ currency: string;
246
+ /** Rated at 0 in non-strict mode; a backfill target. */
247
+ priceMissing: boolean;
248
+ status: UsageStatus;
249
+ /** Set on a posted record when a compensating record reversed it (§8.5). */
250
+ reversedByRecordId?: string;
251
+ /** Set on a compensating record, pointing back at the record it negates. */
252
+ reversesRecordId?: string;
253
+ /** Unique per `(tenantId, idempotencyKey)`. */
254
+ idempotencyKey: string;
255
+ /** Ties an invoice line back to app logs. */
256
+ correlationId?: string;
257
+ /** Provider request id. */
258
+ requestId?: string;
259
+ /** Platform-absorbed — never consumes wallet/budget. */
260
+ isSystemCost: boolean;
261
+ /** e.g. `'workout_generation_retry'`. */
262
+ systemCostCategory?: string;
263
+ /** True when this record consumed wallet/budget (reconciliation predicate §10.7). */
264
+ enforced: boolean;
265
+ /** Previous chain hash (optional per-tenant hash chain, §8.6). */
266
+ prevHash?: string;
267
+ /** This record's chain hash (optional per-tenant hash chain, §8.6). */
268
+ hash?: string;
269
+ /** When the LLM call happened. */
270
+ occurredAt: Date;
271
+ createdAt: Date;
272
+ /** Pending-row settlements only. */
273
+ updatedAt: Date;
274
+ }
275
+ /**
276
+ * Caller-supplied fields for `ILedgerStore.append`. The store computes
277
+ * `id`/`hash`/`prevHash`/`createdAt`/`updatedAt`.
278
+ */
279
+ type NewUsageRecord = Omit<UsageRecord, 'id' | 'prevHash' | 'hash' | 'createdAt' | 'updatedAt'>;
280
+ /** Query filter for the ledger store. */
281
+ interface LedgerFilter {
282
+ tenantId: string;
283
+ scope?: MeteringScope;
284
+ beneficiary?: MeteringScope;
285
+ feature?: string;
286
+ features?: string[];
287
+ provider?: ProviderId;
288
+ model?: string;
289
+ operation?: AiOperation;
290
+ serviceTier?: ServiceTier;
291
+ tags?: string[];
292
+ isSystemCost?: boolean;
293
+ systemCostCategory?: string;
294
+ status?: UsageStatus[];
295
+ enforcedOnly?: boolean;
296
+ from?: Date;
297
+ to?: Date;
298
+ limit?: number;
299
+ offset?: number;
300
+ }
301
+
302
+ /**
303
+ * @fileoverview Append-only ledger field whitelists and the transition guard — the
304
+ * single source of truth shared by the ledger service and the store adapters so the
305
+ * two layers can never drift. `AMOUNT_FIELDS` are the money/token amounts that stay
306
+ * immutable after a record posts; `SETTLEMENT_FIELDS` are everything a settlement
307
+ * (`pending → posted`) may replace with actuals; `RELEASE_FIELDS` are the audit
308
+ * annotations a void (`pending → released`) may set; `REVERSAL_LINKAGE_FIELD` is the
309
+ * sole annotation a `posted → reversed` flip may set. `isLegalLedgerTransition` and
310
+ * `isLegalTransitionPatchKey` encode the state machine and its per-transition patch
311
+ * contract so every layer accepts exactly the same patches (see spec §8.3/§8.5).
312
+ * @layer shared
313
+ */
314
+
315
+ /**
316
+ * The money/token amount columns that are append-only once a record posts — a
317
+ * void (`pending → released`) or a reversal annotation may never patch them.
318
+ */
319
+ declare const AMOUNT_FIELDS: readonly ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWrite5mTokens", "cacheWrite1hTokens", "reasoningTokens", "audioInTokens", "audioOutTokens", "imageInTokens", "imageOutTokens", "totalTokens", "rawCostNanoUsd", "surchargeNanoUsd", "billedCostNanoUsd"];
320
+ /**
321
+ * Every field a settlement (`pending → posted`) may replace with actuals: the
322
+ * append-only amounts plus the pricing metadata resolved at settlement time.
323
+ */
324
+ declare const SETTLEMENT_FIELDS: readonly ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWrite5mTokens", "cacheWrite1hTokens", "reasoningTokens", "audioInTokens", "audioOutTokens", "imageInTokens", "imageOutTokens", "totalTokens", "rawCostNanoUsd", "surchargeNanoUsd", "billedCostNanoUsd", "priceVersionId", "priceMissing", "markupMultiplier"];
325
+ /**
326
+ * The audit-annotation columns a void (`pending → released`) may patch. A release
327
+ * never bills, so it may only annotate the voided hold with request/correlation
328
+ * linkage — never an append-only amount, the settlement pricing metadata, or an
329
+ * immutable identity column. Kept as an explicit column whitelist so the store
330
+ * boundary never interpolates an unknown identifier into raw SQL.
331
+ */
332
+ declare const RELEASE_FIELDS: readonly ["correlationId", "requestId"];
333
+ /** The sole field a `posted → reversed` annotation may set — the reversal linkage. */
334
+ declare const REVERSAL_LINKAGE_FIELD: "reversedByRecordId";
335
+ /**
336
+ * The only patch keys a settlement (`pending → posted`) may set — the settlement
337
+ * amount/cost/pricing columns plus the reversal-linkage field. An immutable
338
+ * identity column (`id`/`tenantId`/`idempotencyKey`) is never a member.
339
+ */
340
+ declare const SETTLEMENT_PATCH_KEYS: ReadonlySet<string>;
341
+ /**
342
+ * Whether `from → to` is a legal ledger state transition (§8.3): a hold settles
343
+ * (`pending → posted`) or is voided (`pending → released`), and a posted record may
344
+ * be annotated as reversed (`posted → reversed`). Every other pair is a caller bug.
345
+ *
346
+ * @param from The record's current lifecycle status.
347
+ * @param to The requested target status.
348
+ * @returns `true` when the transition is permitted by the state machine.
349
+ */
350
+ declare function isLegalLedgerTransition(from: UsageStatus, to: UsageStatus): boolean;
351
+ /**
352
+ * Whether `key` is a legal patch field for the `from → to` transition (§8.3/§8.5) —
353
+ * the single source of truth shared by the ledger service guard and every store
354
+ * adapter so the two layers can never drift:
355
+ * - `pending → posted` (settle): only the settlement fields (+ the reversal linkage).
356
+ * - `pending → released` (void): only the audit-annotation fields.
357
+ * - `posted → reversed` (annotate): only the reversal linkage.
358
+ * Any other (illegal) transition patches nothing. The result set is always drawn
359
+ * from the fixed column whitelists above, so a store may interpolate a passing key
360
+ * as a raw SQL identifier without risk of injection.
361
+ *
362
+ * @param from The record's current lifecycle status.
363
+ * @param to The requested target status.
364
+ * @param key The patch column a caller wishes to set.
365
+ * @returns `true` when the column may legally be patched on this transition.
366
+ */
367
+ declare function isLegalTransitionPatchKey(from: UsageStatus, to: UsageStatus, key: string): boolean;
368
+
369
+ /**
370
+ * @fileoverview Normalizer for the OpenAI Chat Completions `usage` shape. The
371
+ * critical rule (spec §5.3/§5.5): `completion_tokens` INCLUDES reasoning tokens,
372
+ * so `outputTokens = completion_tokens − reasoning_tokens` — double-billing that
373
+ * detail is the audit's #1 billing bug. Cached and audio prompt tokens are a
374
+ * subset of `prompt_tokens` and are split into their own categories.
375
+ * @layer shared
376
+ */
377
+
378
+ /**
379
+ * Normalize an OpenAI Chat Completions response into {@link NormalizedUsage}.
380
+ *
381
+ * @param raw The provider response object (with a nested `usage`).
382
+ * @returns The canonical usage.
383
+ * @throws {Error} When the `usage` object or its required token fields are absent.
384
+ * @example
385
+ * normalizeOpenAiChatUsage({ model: 'gpt-5.2', usage: { prompt_tokens: 100, completion_tokens: 40 } })
386
+ */
387
+ declare function normalizeOpenAiChatUsage(raw: unknown): NormalizedUsage;
388
+
389
+ /**
390
+ * @fileoverview Normalizer for the OpenAI Responses API `usage` shape. Same
391
+ * reasoning-subtraction rule as Chat Completions, but the fields are renamed
392
+ * `input_tokens` / `output_tokens` with `input_tokens_details` /
393
+ * `output_tokens_details` (spec §5.3).
394
+ * @layer shared
395
+ */
396
+
397
+ /**
398
+ * Normalize an OpenAI Responses API response into {@link NormalizedUsage}.
399
+ *
400
+ * @param raw The provider response object (with a nested `usage`).
401
+ * @returns The canonical usage.
402
+ * @throws {Error} When the `usage` object or its required token fields are absent.
403
+ * @example
404
+ * normalizeOpenAiResponsesUsage({ model: 'gpt-5.2', usage: { input_tokens: 100, output_tokens: 40 } })
405
+ */
406
+ declare function normalizeOpenAiResponsesUsage(raw: unknown): NormalizedUsage;
407
+
408
+ /**
409
+ * @fileoverview Normalizer for OpenAI-compatible gateways (DeepSeek, xAI, Groq, …)
410
+ * that reuse the Chat Completions `prompt_tokens` / `completion_tokens` shape
411
+ * without OpenAI-specific detail guarantees. DeepSeek's `prompt_cache_hit_tokens`
412
+ * maps to `cacheReadTokens` when present. The provider id is left empty for the
413
+ * preset (`providerPresets.openaiCompatible(id)`) or context to supply (spec §5.3).
414
+ * @layer shared
415
+ */
416
+
417
+ /**
418
+ * Normalize an OpenAI-compatible chat response into {@link NormalizedUsage}.
419
+ *
420
+ * @param raw The provider response object (with a nested `usage`).
421
+ * @returns The canonical usage; `provider` is `''` unless the preset overrides it.
422
+ * @throws {Error} When the `usage` object or its required token fields are absent.
423
+ * @example
424
+ * normalizeOpenAiCompatibleUsage({ model: 'deepseek-chat', usage: { prompt_tokens: 100, completion_tokens: 40 } })
425
+ */
426
+ declare function normalizeOpenAiCompatibleUsage(raw: unknown): NormalizedUsage;
427
+
428
+ /**
429
+ * @fileoverview Normalizer for the Anthropic Messages `usage` shape. Anthropic
430
+ * folds thinking into `output_tokens` with no sub-field, so `reasoningTokens`
431
+ * stays `0` (spec §5.3/§5.5). Cache writes split into 5-minute and 1-hour
432
+ * categories via `cache_creation`; an unsplit `cache_creation_input_tokens`
433
+ * total defaults to the 5-minute category. Server tool use maps to `serverToolUse`.
434
+ * @layer shared
435
+ */
436
+
437
+ /**
438
+ * Normalize an Anthropic Messages response into {@link NormalizedUsage}.
439
+ *
440
+ * @param raw The provider response object (with a nested `usage`).
441
+ * @returns The canonical usage.
442
+ * @throws {Error} When the `usage` object or its required token fields are absent.
443
+ * @example
444
+ * normalizeAnthropicUsage({ model: 'claude-opus', usage: { input_tokens: 100, output_tokens: 40 } })
445
+ */
446
+ declare function normalizeAnthropicUsage(raw: unknown): NormalizedUsage;
447
+
448
+ /**
449
+ * @fileoverview Normalizer for the Google Gemini / Vertex `usageMetadata` shape.
450
+ * `candidatesTokenCount` EXCLUDES thoughts, so it maps directly to `outputTokens`
451
+ * with no subtraction; `thoughtsTokenCount` maps to `reasoningTokens`;
452
+ * `toolUsePromptTokenCount` folds into `inputTokens` (billed at the input rate);
453
+ * `cachedContentTokenCount` is the cache-read portion of the prompt (spec §5.3).
454
+ * @layer shared
455
+ */
456
+
457
+ /**
458
+ * Normalize a Gemini / Vertex response into {@link NormalizedUsage}.
459
+ *
460
+ * @param raw The provider response object (with a nested `usageMetadata`).
461
+ * @returns The canonical usage.
462
+ * @throws {Error} When `usageMetadata` or its required token field is absent.
463
+ * @example
464
+ * normalizeGeminiUsage({ modelVersion: 'gemini-2.5-flash', usageMetadata: { promptTokenCount: 100, candidatesTokenCount: 40 } })
465
+ */
466
+ declare function normalizeGeminiUsage(raw: unknown): NormalizedUsage;
467
+
468
+ /**
469
+ * @fileoverview Normalizer for the AWS Bedrock Converse `usage` shape. Cache
470
+ * writes come either as a single `cacheWriteInputTokens` total (defaulting to the
471
+ * 5-minute category) or split across `cacheDetails[]` TTL entries (spec §5.3).
472
+ * Model ids like `us.anthropic.claude-…-v1:0` are region-prefixed and are
473
+ * resolved downstream (spec §6.6).
474
+ * @layer shared
475
+ */
476
+
477
+ /**
478
+ * Normalize a Bedrock Converse response into {@link NormalizedUsage}.
479
+ *
480
+ * @param raw The provider response object (with a nested `usage`).
481
+ * @returns The canonical usage.
482
+ * @throws {Error} When the `usage` object or its required token fields are absent.
483
+ * @example
484
+ * normalizeBedrockConverseUsage({ model: 'us.anthropic.claude', usage: { inputTokens: 100, outputTokens: 40 } })
485
+ */
486
+ declare function normalizeBedrockConverseUsage(raw: unknown): NormalizedUsage;
487
+
488
+ /**
489
+ * @fileoverview Normalizer for the Mistral `usage` shape. v0.1 maps
490
+ * `prompt_tokens` / `completion_tokens` only; any additional audio/cache detail
491
+ * fields are version-dependent and preserved verbatim in `raw` (spec §5.3/§20.3).
492
+ * @layer shared
493
+ */
494
+
495
+ /**
496
+ * Normalize a Mistral response into {@link NormalizedUsage}.
497
+ *
498
+ * @param raw The provider response object (with a nested `usage`).
499
+ * @returns The canonical usage.
500
+ * @throws {Error} When the `usage` object or its required token fields are absent.
501
+ * @example
502
+ * normalizeMistralUsage({ model: 'mistral-large', usage: { prompt_tokens: 100, completion_tokens: 40 } })
503
+ */
504
+ declare function normalizeMistralUsage(raw: unknown): NormalizedUsage;
505
+
506
+ /**
507
+ * @fileoverview Normalizer for the OpenRouter `usage` shape. `usage.cost` is the
508
+ * real charged amount in USD (1 credit = 1 USD) and converts to
509
+ * `providerReportedCostNanoUsd`, enabling `'provider-reported'` rating with no
510
+ * price row. `completion_tokens` includes reasoning, so it is subtracted like
511
+ * OpenAI (spec §5.3/§5.5/§6.5). `cost_details` is preserved in `raw`.
512
+ * @layer shared
513
+ */
514
+
515
+ /**
516
+ * Normalize an OpenRouter response into {@link NormalizedUsage}.
517
+ *
518
+ * @param raw The provider response object (with a nested `usage`).
519
+ * @returns The canonical usage, including `providerReportedCostNanoUsd` when `usage.cost` is present.
520
+ * @throws {Error} When the `usage` object or its required token fields are absent.
521
+ * @example
522
+ * normalizeOpenRouterUsage({ model: 'gpt-5.2', usage: { prompt_tokens: 100, completion_tokens: 40, cost: 0.0123 } })
523
+ */
524
+ declare function normalizeOpenRouterUsage(raw: unknown): NormalizedUsage;
525
+
526
+ /**
527
+ * @fileoverview Normalizer for the Vercel AI SDK `usage` shape. It reads BOTH the
528
+ * v5 shape (`usage.{inputTokens, outputTokens, cachedInputTokens, reasoningTokens}`)
529
+ * and the v6 shape (`inputTokenDetails.{cacheReadTokens, cacheWriteTokens}`,
530
+ * `outputTokenDetails.reasoningTokens`). `outputTokens` includes reasoning tokens,
531
+ * so they are subtracted. The provider id and model are supplied by the caller's
532
+ * preset/context and default to empty here (spec §5.3).
533
+ * @layer shared
534
+ */
535
+
536
+ /**
537
+ * Normalize a Vercel AI SDK response into {@link NormalizedUsage}.
538
+ *
539
+ * @param raw The provider response object (with a nested `usage`).
540
+ * @returns The canonical usage; `provider` is `''` unless the context overrides it.
541
+ * @throws {Error} When the `usage` object or its required token fields are absent.
542
+ * @example
543
+ * normalizeVercelAiSdkUsage({ usage: { inputTokens: 100, outputTokens: 40 } })
544
+ */
545
+ declare function normalizeVercelAiSdkUsage(raw: unknown): NormalizedUsage;
546
+
547
+ /**
548
+ * @fileoverview Exact nano-USD money helpers. Every persisted monetary value is
549
+ * an integer number of nano-USD (`1e-9` USD) held as a `bigint`, so all
550
+ * arithmetic is exact integer math. Floating point appears only at the two
551
+ * documented boundaries: the OpenRouter `usage.cost` entry conversion
552
+ * ({@link floatUsdToNanoUsd}) and presentation ({@link formatNanoUsd}).
553
+ * @layer shared
554
+ */
555
+ /**
556
+ * The building block for token pricing: cost of `tokens` at a rate expressed as
557
+ * nano-USD per 1,000,000 tokens, as exact integer math (spec §7.1).
558
+ *
559
+ * @param tokens Integer token count.
560
+ * @param ratePerMillionNano Rate in nano-USD per 1,000,000 tokens.
561
+ * @returns Cost in nano-USD, truncated toward zero on the division.
562
+ * @example
563
+ * perMillion(1000, 5_000_000_000n) // 5_000_000n ($0.005 for 1k tokens at $5/M)
564
+ */
565
+ declare function perMillion(tokens: number, ratePerMillionNano: bigint): bigint;
566
+ /**
567
+ * Convert a floating-point USD amount (e.g. OpenRouter `usage.cost`) into
568
+ * nano-USD, rounding half away from zero at nano precision. The dollar and
569
+ * fractional parts are handled separately so no product overflows the 2^53
570
+ * exact-integer range: the result is exact for `|usd| < $1,000` (and for any
571
+ * value whose fractional dollars round cleanly). This is the only float→bigint
572
+ * entry point on a money path.
573
+ *
574
+ * @param usd The amount in USD.
575
+ * @returns The amount in nano-USD.
576
+ * @throws {RangeError} When `usd` is not finite.
577
+ * @example
578
+ * floatUsdToNanoUsd(0.005) // 5_000_000n
579
+ */
580
+ declare function floatUsdToNanoUsd(usd: number): bigint;
581
+ /** Options controlling how {@link formatNanoUsd} renders a value. */
582
+ interface FormatNanoUsdOptions {
583
+ /** ISO currency code; `'USD'` renders a `$` prefix, others append the code. Default `'USD'`. */
584
+ currency?: string;
585
+ /** Presentation FX rate as target-currency nano-units per 1 USD; converts at read time. */
586
+ fxRateNano?: bigint;
587
+ /** Fractional digits to render, in the range `[0, 9]`. Default `6`. */
588
+ decimals?: number;
589
+ }
590
+ /**
591
+ * Render a nano-USD value as a human-readable currency string. Presentation
592
+ * only — rounding to the displayed precision is round-half-up computed in
593
+ * `bigint`, never floating point (spec §7.4).
594
+ *
595
+ * @param nanoUsd The value in nano-USD.
596
+ * @param opts Currency, FX, and precision options.
597
+ * @returns The formatted string, e.g. `"$0.005000"`.
598
+ * @throws {RangeError} When `opts.decimals` is not an integer in `[0, 9]`.
599
+ * @example
600
+ * formatNanoUsd(5_000_000n) // '$0.005000'
601
+ * formatNanoUsd(5_000_000n, { currency: 'BRL', fxRateNano: 5_000_000_000n }) // '0.025000 BRL'
602
+ */
603
+ declare function formatNanoUsd(nanoUsd: bigint, opts?: FormatNanoUsdOptions): string;
604
+
605
+ /**
606
+ * @fileoverview The effective-dated price row and its insert type. Rates are
607
+ * integer nano-USD per 1,000,000 tokens (or per unit for non-token line items),
608
+ * so every cost computation is exact integer math.
609
+ * @layer shared
610
+ */
611
+
612
+ /**
613
+ * One immutable, effective-dated price version for a (provider, model, operation,
614
+ * serviceTier) tuple. A call is rated at the row in effect at its timestamp.
615
+ */
616
+ interface PriceVersion {
617
+ id: string;
618
+ provider: ProviderId;
619
+ model: string;
620
+ operation: AiOperation;
621
+ /** Part of the resolution key; `'standard'` is the default. */
622
+ serviceTier: ServiceTier;
623
+ /** Rates as integer nano-USD per 1,000,000 tokens. */
624
+ inputNanoUsdPerMillion: bigint;
625
+ outputNanoUsdPerMillion: bigint;
626
+ cacheReadNanoUsdPerMillion: bigint;
627
+ cacheWrite5mNanoUsdPerMillion: bigint;
628
+ cacheWrite1hNanoUsdPerMillion: bigint;
629
+ /** Usually equals output; kept separate for models that price reasoning differently. */
630
+ reasoningNanoUsdPerMillion: bigint;
631
+ audioInNanoUsdPerMillion: bigint;
632
+ audioOutNanoUsdPerMillion: bigint;
633
+ imageInNanoUsdPerMillion: bigint;
634
+ imageOutNanoUsdPerMillion: bigint;
635
+ /**
636
+ * Long-context tier threshold: when total input (all input-side categories)
637
+ * exceeds this, the tier rates replace the base input/output rates for the
638
+ * WHOLE call (all-or-nothing, matching Gemini's billing).
639
+ */
640
+ tierThresholdTokens?: number;
641
+ tierInputNanoUsdPerMillion?: bigint;
642
+ tierOutputNanoUsdPerMillion?: bigint;
643
+ /**
644
+ * Non-token line items in nano-USD PER UNIT, matched against
645
+ * `NormalizedUsage.serverToolUse` / `MeteringContext.extraUnits` by EXACT key —
646
+ * so the key must equal the count key the normalizer emits (e.g. Anthropic's
647
+ * `usage.server_tool_use.web_search_requests`) — e.g.
648
+ * `{ web_search_requests: 10_000_000n }` ($0.01 per web-search call).
649
+ */
650
+ unitRates?: Record<string, bigint>;
651
+ currency: 'USD';
652
+ effectiveFrom: Date;
653
+ /** `null` = current; set when a newer row supersedes it. */
654
+ effectiveTo: Date | null;
655
+ /** Provenance: `'snapshot' | 'manual' | 'import'`. */
656
+ source: string;
657
+ }
658
+ /**
659
+ * Caller-supplied fields for `upsertPrice`. Rates default to `0n` and
660
+ * `serviceTier` to `'standard'`; the store assigns `id` and closes the previous
661
+ * open row's `effectiveTo`.
662
+ */
663
+ type NewPriceVersion = Partial<Omit<PriceVersion, 'id' | 'effectiveTo'>> & Pick<PriceVersion, 'provider' | 'model' | 'operation'>;
664
+
665
+ /**
666
+ * @fileoverview The pure cost engine (spec §7.1). Computes the provider cost of a
667
+ * {@link NormalizedUsage} against a {@link PriceVersion} in exact bigint nano-USD.
668
+ * The long-context tier is all-or-nothing: when total input crosses the
669
+ * threshold, the tier rates replace the base input/output rates for the WHOLE
670
+ * call. Non-token line items are rated via `unitRates`. The token and surcharge
671
+ * parts are returned separately so reports can distinguish them.
672
+ * @layer shared
673
+ */
674
+
675
+ /** The separable parts of a computed provider cost, all in nano-USD. */
676
+ interface CostBreakdown {
677
+ /** Token cost plus surcharges. */
678
+ totalNanoUsd: bigint;
679
+ /** The token-only share. */
680
+ tokenNanoUsd: bigint;
681
+ /** The non-token (server tool use) share. */
682
+ surchargeNanoUsd: bigint;
683
+ }
684
+ /**
685
+ * Compute the provider cost of a normalized usage against a price version.
686
+ *
687
+ * @param usage The normalized usage.
688
+ * @param rate The effective-dated price version.
689
+ * @returns The exact cost broken into token and surcharge parts.
690
+ * @example
691
+ * // 1,000 Opus input tokens at $5/M → 5_000_000n nano-USD ($0.005).
692
+ * computeCostNanoUsd(usage, rate).totalNanoUsd
693
+ */
694
+ declare function computeCostNanoUsd(usage: NormalizedUsage, rate: PriceVersion): CostBreakdown;
695
+
696
+ /**
697
+ * @fileoverview Markup / margin math (spec §7.2). The multiplier is validated
698
+ * (finite, > 0) and rounded to 4 decimal places to match the persisted
699
+ * `Decimal(10,4)`; the billed amount is exact bigint with truncation toward zero
700
+ * on the final division. Markup applies in BOTH rating modes.
701
+ * @layer shared
702
+ */
703
+ /**
704
+ * Validate and round a markup multiplier to 4 decimal places — the value that is
705
+ * persisted on each usage record.
706
+ *
707
+ * @param multiplier The raw multiplier.
708
+ * @returns The multiplier rounded to 4 decimal places.
709
+ * @throws {RangeError} When the multiplier is not finite or not > 0.
710
+ * @example
711
+ * resolveMultiplier4dp(1.23456) // 1.2346
712
+ */
713
+ declare function resolveMultiplier4dp(multiplier: number): number;
714
+ /**
715
+ * Apply a markup multiplier to a raw provider cost, in exact bigint nano-USD.
716
+ *
717
+ * @param rawCostNanoUsd The provider cost in nano-USD.
718
+ * @param multiplier The markup multiplier (e.g. `4.0` for 4× resale).
719
+ * @returns The billed cost in nano-USD (division truncates toward zero).
720
+ * @throws {RangeError} When the multiplier is not finite or not > 0.
721
+ * @example
722
+ * applyMarkup(5_000_000n, 4.0) // 20_000_000n
723
+ */
724
+ declare function applyMarkup(rawCostNanoUsd: bigint, multiplier: number): bigint;
725
+
726
+ /**
727
+ * @fileoverview Host-side derivation of a stable idempotency key from request
728
+ * content (spec §8.4). The payload is serialized to canonical JSON — object keys
729
+ * sorted recursively, array order preserved, `undefined` dropped, `bigint`
730
+ * encoded as a QUOTED, type-tagged string (e.g. `42n` → `"42n"`), `Date` as its
731
+ * ISO string — then hashed with a pure synchronous SHA-256. The `n`-suffixed
732
+ * quoted form makes a `bigint` unambiguous: it can never serialize to the same
733
+ * text as the number `42` (`42`) or a numeric string `"42"` (`"42"`), so
734
+ * `deriveIdempotencyKey(42)` and `deriveIdempotencyKey(42n)` never collide.
735
+ * Deriving the key from content means a retry after a 429/network failure reuses
736
+ * the same key and is deduplicated.
737
+ * @layer shared
738
+ */
739
+ /**
740
+ * Derive a stable idempotency key from a request payload. Equal payloads (up to
741
+ * object-key order) yield the same key; distinct payloads yield distinct keys.
742
+ * Pure, synchronous, and edge-safe.
743
+ *
744
+ * @param payload Any JSON-like request content.
745
+ * @returns A 64-character lowercase hex SHA-256 digest.
746
+ * @example
747
+ * deriveIdempotencyKey({ a: 1, b: 2 }) === deriveIdempotencyKey({ b: 2, a: 1 }) // true
748
+ */
749
+ declare function deriveIdempotencyKey(payload: unknown): string;
750
+
751
+ /**
752
+ * @fileoverview Wallet types: the owner reference, the materialized wallet, and
753
+ * the append-only entry ledger whose signed sum is the balance (see spec §9.1).
754
+ * @layer shared
755
+ */
756
+
757
+ /**
758
+ * Identifies the owner of a wallet. `'key'` scopes deliberately cannot own money
759
+ * — API keys spend their owner's wallet.
760
+ */
761
+ interface WalletRef {
762
+ tenantId: string;
763
+ ownerType: 'tenant' | 'team' | 'user';
764
+ ownerId: string;
765
+ }
766
+ /** A prepaid credit wallet with a materialized nano-USD balance. */
767
+ interface Wallet {
768
+ id: string;
769
+ tenantId: string;
770
+ ownerType: WalletRef['ownerType'];
771
+ ownerId: string;
772
+ /** Materialized, kept transactionally consistent with Σ entries. */
773
+ balanceNanoUsd: bigint;
774
+ createdAt: Date;
775
+ updatedAt: Date;
776
+ }
777
+ /** One append-only entry in a wallet's ledger. */
778
+ interface WalletEntry {
779
+ id: string;
780
+ walletId: string;
781
+ type: WalletEntryType;
782
+ /** `+` = credit, `−` = debit. */
783
+ amountNanoUsd: bigint;
784
+ /** Grant burn priority (lower first). */
785
+ priority: number;
786
+ effectiveAt: Date;
787
+ /** Grants only. */
788
+ expiresAt?: Date;
789
+ /** Present on usage-driven debits/refunds. */
790
+ usageRecordId?: string;
791
+ /** Unique per wallet. */
792
+ idempotencyKey: string;
793
+ reason?: string;
794
+ createdAt: Date;
795
+ }
796
+ /** Caller-supplied fields for `IWalletStore.appendEntry`. The store assigns `id`/`createdAt`. */
797
+ type NewWalletEntry = Omit<WalletEntry, 'id' | 'createdAt'>;
798
+
799
+ /**
800
+ * @fileoverview Budget types: the budget definition, its window/policy unions,
801
+ * and the read-side status DTOs a usage meter renders (see spec §10).
802
+ * @layer shared
803
+ */
804
+
805
+ /** How a budget's window repeats. `{ customSeconds }` is a fixed-length rolling window. */
806
+ type BudgetWindowKind = 'day' | 'week' | 'month' | 'total' | {
807
+ customSeconds: number;
808
+ };
809
+ /** What happens when a budget is exceeded. */
810
+ type BudgetPolicy = 'block' | 'throttle' | 'allow';
811
+ /** A spend/token/count cap for a scope over a window. */
812
+ interface Budget {
813
+ id: string;
814
+ tenantId: string;
815
+ scope: MeteringScope;
816
+ /** Restrict which usage counts; empty/absent = all features. */
817
+ features?: string[];
818
+ /** Billed-spend cap. */
819
+ limitNanoUsd?: bigint;
820
+ /** Total-token cap. */
821
+ limitTokens?: number;
822
+ /** Operation-count cap (posted, enforced, non-reversed records). */
823
+ limitCount?: number;
824
+ window: BudgetWindowKind;
825
+ /**
826
+ * Per-budget window anchor. Absent → calendar UTC anchoring. Month windows
827
+ * clamp short months (a Jan 31 anchor yields Feb 28/29, Mar 31, …).
828
+ */
829
+ anchorAt?: Date;
830
+ /** Optional budget lifetime: enforcement ignores the budget after this instant. */
831
+ expiresAt?: Date;
832
+ /** Fractions of the limit that trigger soft alert events. */
833
+ softThresholds: number[];
834
+ policy: BudgetPolicy;
835
+ createdAt: Date;
836
+ }
837
+ /** The read-side status of one budget window — powers a usage meter. */
838
+ interface BudgetStatus {
839
+ budgetId: string;
840
+ features?: string[];
841
+ window: BudgetWindowKind;
842
+ windowStart: Date;
843
+ /** `null` for `'total'`. */
844
+ resetsAt: Date | null;
845
+ policy: BudgetPolicy;
846
+ limit: {
847
+ nanoUsd?: bigint;
848
+ tokens?: number;
849
+ count?: number;
850
+ };
851
+ spent: {
852
+ nanoUsd: bigint;
853
+ tokens: number;
854
+ count: number;
855
+ };
856
+ /** Absent dimension = unlimited. */
857
+ remaining: {
858
+ nanoUsd?: bigint;
859
+ tokens?: number;
860
+ count?: number;
861
+ };
862
+ /** Max across limited dimensions. */
863
+ usedFraction: number;
864
+ }
865
+ /** Aggregate access status for a scope (wallet + all matching budgets). */
866
+ interface AccessStatus {
867
+ hasAccess: boolean;
868
+ blockedBy?: 'wallet' | 'budget';
869
+ wallet?: {
870
+ balanceNanoUsd: bigint;
871
+ credits: number;
872
+ overdraftRemainingNanoUsd: bigint;
873
+ };
874
+ budgets: BudgetStatus[];
875
+ }
876
+
877
+ /**
878
+ * @fileoverview Reporting types: the query filter and the aggregated usage
879
+ * summary (including the cache-savings figure) produced by `UsageReportService`.
880
+ * @layer shared
881
+ */
882
+
883
+ /** Filter for a usage report query. */
884
+ interface ReportFilter {
885
+ tenantId: string;
886
+ scope?: MeteringScope;
887
+ beneficiary?: MeteringScope;
888
+ feature?: string;
889
+ features?: string[];
890
+ provider?: ProviderId;
891
+ model?: string;
892
+ operation?: AiOperation;
893
+ serviceTier?: ServiceTier;
894
+ /** Records carrying ANY of these tags. */
895
+ tags?: string[];
896
+ isSystemCost?: boolean;
897
+ systemCostCategory?: string;
898
+ enforcedOnly?: boolean;
899
+ /** Default `['posted', 'reversed']`. */
900
+ status?: UsageStatus[];
901
+ from: Date;
902
+ to: Date;
903
+ }
904
+ /** One aggregated row of a usage report. */
905
+ interface UsageSummary {
906
+ /** groupBy key → value; empty groupBy = one grand-total row. */
907
+ group: Record<string, string>;
908
+ records: number;
909
+ totalTokens: number;
910
+ tokens: Record<TokenCategory, number>;
911
+ rawCostNanoUsd: bigint;
912
+ surchargeNanoUsd: bigint;
913
+ billedCostNanoUsd: bigint;
914
+ /** Σ cacheReadTokens × (inputRate − cacheReadRate) — the "you saved $X via caching" figure. */
915
+ cacheSavingsNanoUsd: bigint;
916
+ }
917
+
918
+ /**
919
+ * @fileoverview The event envelope, the event-type union, and one payload type
920
+ * per event in the catalog (see spec §12). Delivery is at-least-once, so `id` is
921
+ * the consumer's dedupe key. `bigint` payload fields cross a JSON boundary as
922
+ * decimal strings (§15.5) — in-process delivery keeps them as `bigint`.
923
+ * @layer shared
924
+ */
925
+
926
+ /** Every event `type` the library emits. */
927
+ type AiTokensEventType = 'ai_tokens.usage.recorded' | 'ai_tokens.usage.reversed' | 'ai_tokens.hold.released' | 'ai_tokens.budget.threshold_crossed' | 'ai_tokens.budget.exceeded' | 'ai_tokens.budget.projected_exceeded' | 'ai_tokens.wallet.low_balance' | 'ai_tokens.wallet.depleted' | 'ai_tokens.wallet.granted' | 'ai_tokens.price.missing' | 'ai_tokens.audit';
928
+ /** The at-least-once event envelope. */
929
+ interface AiTokensEvent<T = unknown> {
930
+ /** UUID — the consumer's dedupe key. */
931
+ id: string;
932
+ type: AiTokensEventType;
933
+ occurredAt: Date;
934
+ tenantId: string;
935
+ scope?: MeteringScope;
936
+ data: T;
937
+ }
938
+ /** A per-window spend snapshot shared by budget event payloads. */
939
+ interface BudgetDimensionSnapshot {
940
+ nanoUsd?: bigint;
941
+ tokens?: number;
942
+ count?: number;
943
+ }
944
+ /** `ai_tokens.usage.recorded` payload. */
945
+ interface UsageRecordedEventData {
946
+ usageRecordId: string;
947
+ feature: string;
948
+ provider: ProviderId;
949
+ model: string;
950
+ serviceTier: ServiceTier;
951
+ totalTokens: number;
952
+ rawCostNanoUsd: bigint;
953
+ billedCostNanoUsd: bigint;
954
+ enforced: boolean;
955
+ isSystemCost: boolean;
956
+ }
957
+ /** `ai_tokens.usage.reversed` payload. */
958
+ interface UsageReversedEventData {
959
+ usageRecordId: string;
960
+ reversalRecordId: string;
961
+ reason: string;
962
+ }
963
+ /** `ai_tokens.hold.released` payload. */
964
+ interface HoldReleasedEventData {
965
+ holdId: string;
966
+ reason: string;
967
+ expired: boolean;
968
+ }
969
+ /** `ai_tokens.budget.threshold_crossed` payload. */
970
+ interface BudgetThresholdCrossedEventData {
971
+ budgetId: string;
972
+ threshold: number;
973
+ usedFraction: number;
974
+ limit: BudgetDimensionSnapshot;
975
+ spent: BudgetDimensionSnapshot;
976
+ remaining: BudgetDimensionSnapshot;
977
+ resetsAt: Date | null;
978
+ }
979
+ /** `ai_tokens.budget.exceeded` payload. */
980
+ interface BudgetExceededEventData {
981
+ budgetId: string;
982
+ policy: BudgetPolicy;
983
+ dimension: 'cost' | 'tokens' | 'count';
984
+ limit: BudgetDimensionSnapshot;
985
+ spent: BudgetDimensionSnapshot;
986
+ resetsAt: Date | null;
987
+ }
988
+ /** `ai_tokens.budget.projected_exceeded` payload. */
989
+ interface BudgetProjectedExceededEventData {
990
+ budgetId: string;
991
+ projectedAt: Date;
992
+ usedFraction: number;
993
+ resetsAt: Date | null;
994
+ }
995
+ /** `ai_tokens.wallet.low_balance` payload. */
996
+ interface WalletLowBalanceEventData {
997
+ walletId: string;
998
+ balanceNanoUsd: bigint;
999
+ thresholdFraction: number;
1000
+ }
1001
+ /** `ai_tokens.wallet.depleted` payload. */
1002
+ interface WalletDepletedEventData {
1003
+ walletId: string;
1004
+ balanceNanoUsd: bigint;
1005
+ }
1006
+ /** `ai_tokens.wallet.granted` payload. */
1007
+ interface WalletGrantedEventData {
1008
+ walletId: string;
1009
+ entryId: string;
1010
+ amountNanoUsd: bigint;
1011
+ expiresAt?: Date;
1012
+ }
1013
+ /** `ai_tokens.price.missing` payload. */
1014
+ interface PriceMissingEventData {
1015
+ provider: ProviderId;
1016
+ model: string;
1017
+ operation: AiOperation;
1018
+ serviceTier: ServiceTier;
1019
+ usageRecordId: string;
1020
+ }
1021
+ /** `ai_tokens.audit` payload — an admin-plane mutation (§14.4). */
1022
+ interface AuditEventData {
1023
+ action: string;
1024
+ actor?: string;
1025
+ details: Record<string, unknown>;
1026
+ }
1027
+
1028
+ /**
1029
+ * @fileoverview The canonical error response body. `AiTokensException` (server
1030
+ * layer) serializes to this shape so hosts get a stable `{ error: {...} }`
1031
+ * envelope (see spec §16).
1032
+ * @layer shared
1033
+ */
1034
+
1035
+ /** The JSON body every {@link AI_TOKENS_ERROR_CODES} failure serializes to. */
1036
+ interface AiTokensErrorResponse {
1037
+ error: {
1038
+ code: keyof typeof AI_TOKENS_ERROR_CODES;
1039
+ message: string;
1040
+ details?: Record<string, unknown>;
1041
+ };
1042
+ }
1043
+
1044
+ export { AI_OPERATIONS, AI_TOKENS_ERROR_CODES, AMOUNT_FIELDS, type AccessStatus, type AiOperation, type AiTokensErrorCode, type AiTokensErrorResponse, type AiTokensEvent, type AiTokensEventType, type AuditEventData, type Budget, type BudgetDimensionSnapshot, type BudgetExceededEventData, type BudgetPolicy, type BudgetProjectedExceededEventData, type BudgetStatus, type BudgetThresholdCrossedEventData, type BudgetWindowKind, type CostBreakdown, type FormatNanoUsdOptions, type HoldReleasedEventData, type KnownProviderId, type LedgerFilter, type MeteringScope, type NewPriceVersion, type NewUsageRecord, type NewWalletEntry, type NormalizedUsage, PROVIDER_IDS, type PriceMissingEventData, type PriceVersion, type ProviderId, type ProviderPreset, RELEASE_FIELDS, REVERSAL_LINKAGE_FIELD, type RatingMode, type ReportFilter, SERVICE_TIERS, SETTLEMENT_FIELDS, SETTLEMENT_PATCH_KEYS, type ScopeType, type ServiceTier, TOKEN_CATEGORIES, type TokenCategory, type UsageNormalizer, type UsageRecord, type UsageRecordedEventData, type UsageReversedEventData, type UsageStatus, type UsageSummary, WALLET_ENTRY_TYPES, type Wallet, type WalletDepletedEventData, type WalletEntry, type WalletEntryType, type WalletGrantedEventData, type WalletLowBalanceEventData, type WalletRef, applyMarkup, computeCostNanoUsd, deriveIdempotencyKey, floatUsdToNanoUsd, formatNanoUsd, isLegalLedgerTransition, isLegalTransitionPatchKey, normalizeAnthropicUsage, normalizeBedrockConverseUsage, normalizeGeminiUsage, normalizeMistralUsage, normalizeOpenAiChatUsage, normalizeOpenAiCompatibleUsage, normalizeOpenAiResponsesUsage, normalizeOpenRouterUsage, normalizeVercelAiSdkUsage, perMillion, resolveMultiplier4dp };