@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.
- package/CHANGELOG.md +113 -0
- package/LICENSE +21 -0
- package/README.md +795 -0
- package/dist/prices/index.cjs +218 -0
- package/dist/prices/index.d.cts +131 -0
- package/dist/prices/index.d.ts +131 -0
- package/dist/prices/index.mjs +216 -0
- package/dist/prisma/index.cjs +1197 -0
- package/dist/prisma/index.d.cts +677 -0
- package/dist/prisma/index.d.ts +677 -0
- package/dist/prisma/index.mjs +1195 -0
- package/dist/prisma/migrations/0001_init.sql +198 -0
- package/dist/prisma/schema.prisma.fragment +197 -0
- package/dist/redis/index.cjs +110 -0
- package/dist/redis/index.d.cts +98 -0
- package/dist/redis/index.d.ts +98 -0
- package/dist/redis/index.mjs +107 -0
- package/dist/server/index.cjs +4405 -0
- package/dist/server/index.d.cts +3202 -0
- package/dist/server/index.d.ts +3202 -0
- package/dist/server/index.mjs +4354 -0
- package/dist/shared/index.cjs +659 -0
- package/dist/shared/index.d.cts +1044 -0
- package/dist/shared/index.d.ts +1044 -0
- package/dist/shared/index.mjs +629 -0
- package/package.json +216 -0
|
@@ -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 };
|