usage-tab 1.0.0 → 1.1.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/dist/index.d.cts CHANGED
@@ -201,7 +201,18 @@ interface PriceRequest {
201
201
  readonly usage: LlmUsage | unknown;
202
202
  /** `'batch'` applies the resolved period's `batchMultiplier` when one is published; falls back to standard pricing (with a warning) otherwise. */
203
203
  readonly mode?: PriceMode;
204
- /** Effective date for pricing-period selection. Defaults to `new Date()`. */
204
+ /**
205
+ * Effective date for pricing-period selection. Defaults to `new Date()`.
206
+ *
207
+ * Pricing periods start at UTC midnight, so every ISO string form is read
208
+ * as UTC: an ISO date (`'2026-09-01'`) and an offset-carrying datetime
209
+ * (`'2026-09-01T05:00:00Z'`) already are, and one *without* an offset
210
+ * (`'2026-09-01T05:00:00'` — a log timestamp, a `datetime-local` input,
211
+ * several DB drivers) is read as UTC too, rather than in whatever timezone
212
+ * the process happens to run. A non-ISO string (`'2026/09/01'`,
213
+ * `'September 1, 2026'`) is implementation-defined and local in practice —
214
+ * pass a `Date` or an ISO form instead.
215
+ */
205
216
  readonly at?: Date | string;
206
217
  }
207
218
  interface CostLine {
@@ -323,6 +334,32 @@ interface CustomPriceInput {
323
334
  readonly observedAt?: string;
324
335
  readonly notes?: readonly string[];
325
336
  }
337
+ /**
338
+ * An exact running total over one or more `CostBreakdown`s — see
339
+ * `createCostAggregator`.
340
+ */
341
+ interface CostTotal {
342
+ /** How many breakdowns this total covers. */
343
+ readonly count: number;
344
+ /** Ergonomic numeric total. Not the authoritative value — see `totalUsdExact`. */
345
+ readonly totalUsd: number;
346
+ /** Exact decimal string total, summed with the same fixed-point arithmetic each individual cost was computed with. */
347
+ readonly totalUsdExact: string;
348
+ /**
349
+ * Every distinct `registryVersion` the aggregated breakdowns were priced
350
+ * against, sorted. More than one means the total mixes pricing snapshots —
351
+ * expected for a report spanning a registry update, a bug for a total meant
352
+ * to be reproducible against a single one.
353
+ */
354
+ readonly registryVersions: readonly string[];
355
+ }
356
+ interface CostAggregator {
357
+ /** Adds one breakdown to the running totals. Throws `InvalidRateError` if its `totalUsdExact` is not a non-negative decimal string. */
358
+ add(breakdown: CostBreakdown): void;
359
+ total(): CostTotal;
360
+ /** Per-model totals, keyed `` `${provider}:${canonicalModel}` `` — the resolved identity, so two aliases of one model share a bucket. */
361
+ byModel(): ReadonlyMap<string, CostTotal>;
362
+ }
326
363
 
327
364
  declare function calculateCost(request: PriceRequest, options?: PriceOptions): CostBreakdown;
328
365
 
@@ -345,6 +382,32 @@ declare function createPriceCalculator(defaults?: PriceCalculatorOptions): Price
345
382
  */
346
383
  declare function createPriceOverride(input: CustomPriceInput): ModelDescriptor;
347
384
 
385
+ /**
386
+ * Exact sum of decimal USD strings — `totalUsdExact`/`costUsdExact` values,
387
+ * or any string in the same non-negative decimal format. Returns a decimal
388
+ * string in that same format (`"0.00"` for an empty list).
389
+ *
390
+ * Throws `InvalidRateError` (`INVALID_RATE`) for anything that is not a
391
+ * non-negative decimal string, rather than coercing it: a stringified
392
+ * `NaN`, a float-formatted `"1e-7"`, or a `number` that slipped past a
393
+ * `readonly string[]` at the type level are all silent under-reporting
394
+ * waiting to happen.
395
+ */
396
+ declare function sumExactUsd(values: readonly string[]): string;
397
+ /**
398
+ * Accumulates `CostBreakdown`s into exact running totals — overall and per
399
+ * `provider:canonicalModel`. Sums are kept as exact fixed-point amounts, so
400
+ * memory does not grow with the number of breakdowns added; only the number
401
+ * of distinct models does.
402
+ *
403
+ * `total().registryVersions` carries the aggregate's provenance: more than
404
+ * one entry means the total mixes pricing snapshots, which is legitimate for
405
+ * a historical report spanning a registry update and a bug for a total that
406
+ * was supposed to be priced against one snapshot. This package cannot tell
407
+ * the two apart, so it reports rather than warns.
408
+ */
409
+ declare function createCostAggregator(): CostAggregator;
410
+
348
411
  /**
349
412
  * Stable error types this package throws directly. `resolveModel` and
350
413
  * `calculateCost` also propagate `AmbiguousAliasError`, `UnknownModelError`,
@@ -417,4 +480,4 @@ declare function normalizeGoogleUsage(value: unknown): NormalizedUsageResult;
417
480
 
418
481
  declare function normalizeOpenAICompatibleUsage(value: unknown): NormalizedUsageResult;
419
482
 
420
- export { AmbiguousAliasError, type CostBreakdown, type CostLine, type CustomPriceInput, InvalidLookupDateError, InvalidRateError, InvalidTokenCountError, InvalidUsageError, type LlmUsage, MODEL_REGISTRY, type ModelCandidate, type ModelDescriptor, NoPricingPeriodError, type NormalizedUsageResult, type PriceCalculator, type PriceCalculatorOptions, type PriceMode, type PriceOptions, type PriceRequest, type PriceWarning, type PriceWarningCode, type PricingPeriod, type ProviderId, REGISTRY_VERSION, type RegistrySource, type ResolveModelOptions, type ResolvedModel, UnknownModelError, calculateCost, createPriceCalculator, createPriceOverride, normalizeAnthropicUsage, normalizeGoogleUsage, normalizeOpenAICompatibleUsage, normalizeOpenAIUsage, resolveModel };
483
+ export { AmbiguousAliasError, type CostAggregator, type CostBreakdown, type CostLine, type CostTotal, type CustomPriceInput, InvalidLookupDateError, InvalidRateError, InvalidTokenCountError, InvalidUsageError, type LlmUsage, MODEL_REGISTRY, type ModelCandidate, type ModelDescriptor, NoPricingPeriodError, type NormalizedUsageResult, type PriceCalculator, type PriceCalculatorOptions, type PriceMode, type PriceOptions, type PriceRequest, type PriceWarning, type PriceWarningCode, type PricingPeriod, type ProviderId, REGISTRY_VERSION, type RegistrySource, type ResolveModelOptions, type ResolvedModel, UnknownModelError, calculateCost, createCostAggregator, createPriceCalculator, createPriceOverride, normalizeAnthropicUsage, normalizeGoogleUsage, normalizeOpenAICompatibleUsage, normalizeOpenAIUsage, resolveModel, sumExactUsd };
package/dist/index.d.ts CHANGED
@@ -201,7 +201,18 @@ interface PriceRequest {
201
201
  readonly usage: LlmUsage | unknown;
202
202
  /** `'batch'` applies the resolved period's `batchMultiplier` when one is published; falls back to standard pricing (with a warning) otherwise. */
203
203
  readonly mode?: PriceMode;
204
- /** Effective date for pricing-period selection. Defaults to `new Date()`. */
204
+ /**
205
+ * Effective date for pricing-period selection. Defaults to `new Date()`.
206
+ *
207
+ * Pricing periods start at UTC midnight, so every ISO string form is read
208
+ * as UTC: an ISO date (`'2026-09-01'`) and an offset-carrying datetime
209
+ * (`'2026-09-01T05:00:00Z'`) already are, and one *without* an offset
210
+ * (`'2026-09-01T05:00:00'` — a log timestamp, a `datetime-local` input,
211
+ * several DB drivers) is read as UTC too, rather than in whatever timezone
212
+ * the process happens to run. A non-ISO string (`'2026/09/01'`,
213
+ * `'September 1, 2026'`) is implementation-defined and local in practice —
214
+ * pass a `Date` or an ISO form instead.
215
+ */
205
216
  readonly at?: Date | string;
206
217
  }
207
218
  interface CostLine {
@@ -323,6 +334,32 @@ interface CustomPriceInput {
323
334
  readonly observedAt?: string;
324
335
  readonly notes?: readonly string[];
325
336
  }
337
+ /**
338
+ * An exact running total over one or more `CostBreakdown`s — see
339
+ * `createCostAggregator`.
340
+ */
341
+ interface CostTotal {
342
+ /** How many breakdowns this total covers. */
343
+ readonly count: number;
344
+ /** Ergonomic numeric total. Not the authoritative value — see `totalUsdExact`. */
345
+ readonly totalUsd: number;
346
+ /** Exact decimal string total, summed with the same fixed-point arithmetic each individual cost was computed with. */
347
+ readonly totalUsdExact: string;
348
+ /**
349
+ * Every distinct `registryVersion` the aggregated breakdowns were priced
350
+ * against, sorted. More than one means the total mixes pricing snapshots —
351
+ * expected for a report spanning a registry update, a bug for a total meant
352
+ * to be reproducible against a single one.
353
+ */
354
+ readonly registryVersions: readonly string[];
355
+ }
356
+ interface CostAggregator {
357
+ /** Adds one breakdown to the running totals. Throws `InvalidRateError` if its `totalUsdExact` is not a non-negative decimal string. */
358
+ add(breakdown: CostBreakdown): void;
359
+ total(): CostTotal;
360
+ /** Per-model totals, keyed `` `${provider}:${canonicalModel}` `` — the resolved identity, so two aliases of one model share a bucket. */
361
+ byModel(): ReadonlyMap<string, CostTotal>;
362
+ }
326
363
 
327
364
  declare function calculateCost(request: PriceRequest, options?: PriceOptions): CostBreakdown;
328
365
 
@@ -345,6 +382,32 @@ declare function createPriceCalculator(defaults?: PriceCalculatorOptions): Price
345
382
  */
346
383
  declare function createPriceOverride(input: CustomPriceInput): ModelDescriptor;
347
384
 
385
+ /**
386
+ * Exact sum of decimal USD strings — `totalUsdExact`/`costUsdExact` values,
387
+ * or any string in the same non-negative decimal format. Returns a decimal
388
+ * string in that same format (`"0.00"` for an empty list).
389
+ *
390
+ * Throws `InvalidRateError` (`INVALID_RATE`) for anything that is not a
391
+ * non-negative decimal string, rather than coercing it: a stringified
392
+ * `NaN`, a float-formatted `"1e-7"`, or a `number` that slipped past a
393
+ * `readonly string[]` at the type level are all silent under-reporting
394
+ * waiting to happen.
395
+ */
396
+ declare function sumExactUsd(values: readonly string[]): string;
397
+ /**
398
+ * Accumulates `CostBreakdown`s into exact running totals — overall and per
399
+ * `provider:canonicalModel`. Sums are kept as exact fixed-point amounts, so
400
+ * memory does not grow with the number of breakdowns added; only the number
401
+ * of distinct models does.
402
+ *
403
+ * `total().registryVersions` carries the aggregate's provenance: more than
404
+ * one entry means the total mixes pricing snapshots, which is legitimate for
405
+ * a historical report spanning a registry update and a bug for a total that
406
+ * was supposed to be priced against one snapshot. This package cannot tell
407
+ * the two apart, so it reports rather than warns.
408
+ */
409
+ declare function createCostAggregator(): CostAggregator;
410
+
348
411
  /**
349
412
  * Stable error types this package throws directly. `resolveModel` and
350
413
  * `calculateCost` also propagate `AmbiguousAliasError`, `UnknownModelError`,
@@ -417,4 +480,4 @@ declare function normalizeGoogleUsage(value: unknown): NormalizedUsageResult;
417
480
 
418
481
  declare function normalizeOpenAICompatibleUsage(value: unknown): NormalizedUsageResult;
419
482
 
420
- export { AmbiguousAliasError, type CostBreakdown, type CostLine, type CustomPriceInput, InvalidLookupDateError, InvalidRateError, InvalidTokenCountError, InvalidUsageError, type LlmUsage, MODEL_REGISTRY, type ModelCandidate, type ModelDescriptor, NoPricingPeriodError, type NormalizedUsageResult, type PriceCalculator, type PriceCalculatorOptions, type PriceMode, type PriceOptions, type PriceRequest, type PriceWarning, type PriceWarningCode, type PricingPeriod, type ProviderId, REGISTRY_VERSION, type RegistrySource, type ResolveModelOptions, type ResolvedModel, UnknownModelError, calculateCost, createPriceCalculator, createPriceOverride, normalizeAnthropicUsage, normalizeGoogleUsage, normalizeOpenAICompatibleUsage, normalizeOpenAIUsage, resolveModel };
483
+ export { AmbiguousAliasError, type CostAggregator, type CostBreakdown, type CostLine, type CostTotal, type CustomPriceInput, InvalidLookupDateError, InvalidRateError, InvalidTokenCountError, InvalidUsageError, type LlmUsage, MODEL_REGISTRY, type ModelCandidate, type ModelDescriptor, NoPricingPeriodError, type NormalizedUsageResult, type PriceCalculator, type PriceCalculatorOptions, type PriceMode, type PriceOptions, type PriceRequest, type PriceWarning, type PriceWarningCode, type PricingPeriod, type ProviderId, REGISTRY_VERSION, type RegistrySource, type ResolveModelOptions, type ResolvedModel, UnknownModelError, calculateCost, createCostAggregator, createPriceCalculator, createPriceOverride, normalizeAnthropicUsage, normalizeGoogleUsage, normalizeOpenAICompatibleUsage, normalizeOpenAIUsage, resolveModel, sumExactUsd };
package/dist/index.js CHANGED
@@ -80,8 +80,9 @@ function toCandidate(descriptor) {
80
80
  }
81
81
  function matchExact(pool, id, provider) {
82
82
  if (provider !== void 0) {
83
- const canonical = pool.find((d) => d.provider === provider && d.canonicalId === id);
84
- if (canonical !== void 0) return { unique: canonical };
83
+ const canonical = pool.filter((d) => d.provider === provider && d.canonicalId === id);
84
+ if (canonical.length === 1) return { unique: canonical[0] };
85
+ if (canonical.length > 1) return { ambiguous: canonical };
85
86
  const scoped = pool.filter((d) => d.provider === provider && d.aliases.includes(id));
86
87
  if (scoped.length === 1) return { unique: scoped[0] };
87
88
  if (scoped.length > 1) return { ambiguous: scoped };
@@ -109,16 +110,22 @@ function resolveModel(requestedId, registry, options = {}) {
109
110
  }
110
111
  }
111
112
  if (provider !== void 0) {
112
- const canonical = registry.find(
113
+ const canonical = registry.filter(
113
114
  (d) => d.provider === provider && d.canonicalId === requestedId
114
115
  );
115
- if (canonical !== void 0) {
116
- return {
117
- descriptor: canonical,
118
- matchedBy: "canonical-qualified",
119
- requestedId,
120
- requestedProvider: provider
121
- };
116
+ if (canonical.length === 1) {
117
+ const descriptor = canonical[0];
118
+ if (descriptor !== void 0) {
119
+ return {
120
+ descriptor,
121
+ matchedBy: "canonical-qualified",
122
+ requestedId,
123
+ requestedProvider: provider
124
+ };
125
+ }
126
+ }
127
+ if (canonical.length > 1) {
128
+ throw new AmbiguousAliasError(requestedId, canonical.map(toCandidate));
122
129
  }
123
130
  const scoped = registry.filter(
124
131
  (d) => d.provider === provider && d.aliases.includes(requestedId)
@@ -172,10 +179,16 @@ function resolveModel(requestedId, registry, options = {}) {
172
179
  }
173
180
 
174
181
  // ../../internal/model-registry/src/pricing-period.ts
182
+ var OFFSETLESS_ISO_DATETIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/;
175
183
  function toTimestamp(value) {
176
- const ms = value instanceof Date ? value.getTime() : Date.parse(value);
184
+ if (value instanceof Date) {
185
+ const ms2 = value.getTime();
186
+ if (Number.isNaN(ms2)) throw new InvalidLookupDateError(String(value));
187
+ return ms2;
188
+ }
189
+ const ms = Date.parse(OFFSETLESS_ISO_DATETIME.test(value) ? `${value}Z` : value);
177
190
  if (Number.isNaN(ms)) {
178
- throw new InvalidLookupDateError(value instanceof Date ? value.toISOString() : value);
191
+ throw new InvalidLookupDateError(value);
179
192
  }
180
193
  return ms;
181
194
  }
@@ -1507,7 +1520,7 @@ var InvalidRateError = class extends Error {
1507
1520
  var ZERO = { numerator: 0n, scale: 0 };
1508
1521
  var DECIMAL_PATTERN = /^\d+(\.\d+)?$/;
1509
1522
  function parseDecimalRate(value, field) {
1510
- if (!DECIMAL_PATTERN.test(value)) {
1523
+ if (typeof value !== "string" || !DECIMAL_PATTERN.test(value)) {
1511
1524
  throw new InvalidRateError(field, value);
1512
1525
  }
1513
1526
  const dot = value.indexOf(".");
@@ -1941,6 +1954,59 @@ function createPriceOverride(input) {
1941
1954
  };
1942
1955
  }
1943
1956
 
1957
+ // src/aggregate.ts
1958
+ function sumExactUsd(values) {
1959
+ const amounts = [];
1960
+ for (const [index, value] of values.entries()) {
1961
+ amounts.push(parseDecimalRate(value, `values[${String(index)}]`));
1962
+ }
1963
+ return formatExact(addExact(amounts));
1964
+ }
1965
+ function modelKey(breakdown) {
1966
+ return `${breakdown.provider}:${breakdown.canonicalModel}`;
1967
+ }
1968
+ function newBucket() {
1969
+ return { count: 0, sum: ZERO, registryVersions: /* @__PURE__ */ new Set() };
1970
+ }
1971
+ function accumulate(bucket, amount, registryVersion) {
1972
+ bucket.count += 1;
1973
+ bucket.sum = addExact([bucket.sum, amount]);
1974
+ bucket.registryVersions.add(registryVersion);
1975
+ }
1976
+ function toTotal(bucket) {
1977
+ return {
1978
+ count: bucket.count,
1979
+ totalUsd: toDisplayNumber(bucket.sum),
1980
+ totalUsdExact: formatExact(bucket.sum),
1981
+ registryVersions: [...bucket.registryVersions].sort()
1982
+ };
1983
+ }
1984
+ function createCostAggregator() {
1985
+ const overall = newBucket();
1986
+ const byModelBuckets = /* @__PURE__ */ new Map();
1987
+ return {
1988
+ add(breakdown) {
1989
+ const amount = parseDecimalRate(breakdown.totalUsdExact, "totalUsdExact");
1990
+ const key = modelKey(breakdown);
1991
+ let bucket = byModelBuckets.get(key);
1992
+ if (bucket === void 0) {
1993
+ bucket = newBucket();
1994
+ byModelBuckets.set(key, bucket);
1995
+ }
1996
+ accumulate(bucket, amount, breakdown.registryVersion);
1997
+ accumulate(overall, amount, breakdown.registryVersion);
1998
+ },
1999
+ total() {
2000
+ return toTotal(overall);
2001
+ },
2002
+ byModel() {
2003
+ const result = /* @__PURE__ */ new Map();
2004
+ for (const [key, bucket] of byModelBuckets) result.set(key, toTotal(bucket));
2005
+ return result;
2006
+ }
2007
+ };
2008
+ }
2009
+
1944
2010
  // src/normalize/openai.ts
1945
2011
  var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
1946
2012
  "prompt_tokens",
@@ -2139,12 +2205,14 @@ export {
2139
2205
  REGISTRY_VERSION,
2140
2206
  UnknownModelError,
2141
2207
  calculateCost,
2208
+ createCostAggregator,
2142
2209
  createPriceCalculator,
2143
2210
  createPriceOverride,
2144
2211
  normalizeAnthropicUsage,
2145
2212
  normalizeGoogleUsage,
2146
2213
  normalizeOpenAICompatibleUsage,
2147
2214
  normalizeOpenAIUsage,
2148
- resolveModel2 as resolveModel
2215
+ resolveModel2 as resolveModel,
2216
+ sumExactUsd
2149
2217
  };
2150
2218
  //# sourceMappingURL=index.js.map