usage-tab 1.0.0 → 1.2.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/README.md +64 -8
- package/dist/index.cjs +555 -192
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -3
- package/dist/index.d.ts +66 -3
- package/dist/index.js +555 -192
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -112,7 +112,10 @@ result.warnings; // readonly PriceWarning[] — empty here, never silently dropp
|
|
|
112
112
|
version) proves two calculations used byte-identical pricing data.
|
|
113
113
|
- **Historical lookups are exact and reproducible.** A cost computed for a
|
|
114
114
|
specific `at` date always resolves the same pricing period, regardless of
|
|
115
|
-
when you run it
|
|
115
|
+
when — or on which machine — you run it: every ISO `at` string is read as
|
|
116
|
+
UTC, including the offset-less form `Date.parse` would otherwise read in
|
|
117
|
+
the host's local timezone. See
|
|
118
|
+
[Historical lookup](#historical-lookup-and-the-data-freshnesseffective-date-policy).
|
|
116
119
|
- **Zero runtime dependencies, browser-safe.** No `node:` import in `src/`.
|
|
117
120
|
|
|
118
121
|
## API
|
|
@@ -127,7 +130,7 @@ interface PriceRequest {
|
|
|
127
130
|
provider?: string; // qualifies resolution to one provider — see "two channels, one rule" below
|
|
128
131
|
usage: LlmUsage | unknown; // normalize a raw provider response first — see below
|
|
129
132
|
mode?: 'standard' | 'batch';
|
|
130
|
-
at?: Date | string; // defaults to `new Date()
|
|
133
|
+
at?: Date | string; // defaults to `new Date()`; an ISO string is read as UTC
|
|
131
134
|
}
|
|
132
135
|
|
|
133
136
|
interface CostBreakdown {
|
|
@@ -429,27 +432,80 @@ result.totalUsdExact; // "0.666666" — exact
|
|
|
429
432
|
result.totalUsd; // 0.666666 — the same value, as a number
|
|
430
433
|
```
|
|
431
434
|
|
|
435
|
+
### Totalling many requests exactly
|
|
436
|
+
|
|
437
|
+
`sum += breakdown.totalUsd` puts binary floating point back exactly where
|
|
438
|
+
`totalUsdExact` removed it. `createCostAggregator` keeps the running totals
|
|
439
|
+
on the same exact `bigint` path, overall and per model:
|
|
440
|
+
|
|
441
|
+
```ts
|
|
442
|
+
import { calculateCost, createCostAggregator, sumExactUsd } from 'usage-tab';
|
|
443
|
+
|
|
444
|
+
const aggregator = createCostAggregator();
|
|
445
|
+
for (const call of [
|
|
446
|
+
{ model: 'gpt-4o', provider: 'openai', usage: { inputTokens: 40_000, outputTokens: 4_000 } },
|
|
447
|
+
{
|
|
448
|
+
model: 'claude-sonnet-5',
|
|
449
|
+
provider: 'anthropic',
|
|
450
|
+
usage: { inputTokens: 40_000, outputTokens: 4_000 },
|
|
451
|
+
},
|
|
452
|
+
]) {
|
|
453
|
+
aggregator.add(calculateCost({ ...call, at: '2026-08-15' }));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
aggregator.total().totalUsdExact; // exact decimal string — the value to store
|
|
457
|
+
aggregator.total().count; // 2
|
|
458
|
+
aggregator.byModel(); // Map keyed "openai:gpt-4o", "anthropic:claude-sonnet-5"
|
|
459
|
+
|
|
460
|
+
// Already have the strings — from a database column, say:
|
|
461
|
+
sumExactUsd(['0.10', '0.20']); // "0.30", where 0.1 + 0.2 gives 0.30000000000000004
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
`total().registryVersions` lists every pricing snapshot the aggregate drew
|
|
465
|
+
on. More than one is expected for a report spanning a registry update, and a
|
|
466
|
+
bug for a total meant to be reproducible against a single one — this package
|
|
467
|
+
reports it rather than guessing which you meant.
|
|
468
|
+
|
|
469
|
+
Both throw `InvalidRateError` on a value that is not a non-negative decimal
|
|
470
|
+
string, rather than coercing it: a stringified `NaN` or an exponent-notation
|
|
471
|
+
`"1e-7"` silently summing to something wrong is the failure this exists to
|
|
472
|
+
prevent.
|
|
473
|
+
|
|
432
474
|
### Historical lookup and the data-freshness/effective-date policy
|
|
433
475
|
|
|
434
476
|
Every price carries an `effectiveFrom` (and, when superseded, an
|
|
435
477
|
`effectiveTo`) date. `calculateCost`'s `at` picks the period active on that
|
|
436
|
-
date
|
|
437
|
-
fixture for this behavior:
|
|
478
|
+
date - `gemini-3.6-flash`'s real promotional rate, whose start and end
|
|
479
|
+
Google publishes, is the golden fixture for this behavior:
|
|
438
480
|
|
|
439
481
|
```ts
|
|
440
482
|
import { calculateCost } from 'usage-tab';
|
|
441
483
|
|
|
442
484
|
const usage = { inputTokens: 1_000_000, outputTokens: 1_000_000 };
|
|
443
485
|
|
|
444
|
-
calculateCost({ model: '
|
|
486
|
+
calculateCost({ model: 'gemini-3.6-flash', provider: 'google', usage, at: '2026-11-01' })
|
|
445
487
|
.totalUsdExact;
|
|
446
|
-
// "
|
|
488
|
+
// "4.50" - the promotional rate ($0.75/$3.75), published as running through 2026-12-31
|
|
447
489
|
|
|
448
|
-
calculateCost({ model: '
|
|
490
|
+
calculateCost({ model: 'gemini-3.6-flash', provider: 'google', usage, at: '2027-01-15' })
|
|
449
491
|
.totalUsdExact;
|
|
450
|
-
// "
|
|
492
|
+
// "9.00" - the standard rate ($1.50/$7.50), resuming 2027-01-01
|
|
451
493
|
```
|
|
452
494
|
|
|
495
|
+
Period boundaries are UTC midnights, so **every ISO `at` string is read as
|
|
496
|
+
UTC**. An ISO date (`'2026-09-01'`) and an offset-carrying datetime
|
|
497
|
+
(`'2026-09-01T05:00:00Z'`, `'...+02:00'`) already are by specification; one
|
|
498
|
+
_without_ an offset — `'2026-09-01T05:00:00'`, the shape a log timestamp, a
|
|
499
|
+
`datetime-local` input, and several DB drivers produce — is `Date.parse`'s
|
|
500
|
+
local-time case, and is read as UTC here too. Otherwise the same lookup
|
|
501
|
+
prices at $12.00 in one deployment and $18.00 in another, purely from the
|
|
502
|
+
host's `TZ`.
|
|
503
|
+
|
|
504
|
+
A non-ISO string (`'2026/09/01'`, `'September 1, 2026'`) is
|
|
505
|
+
implementation-defined rather than specified, so there is no single reading
|
|
506
|
+
to normalize it to and it keeps whatever `Date.parse` does with it — local
|
|
507
|
+
time, in practice. Pass a `Date` or an ISO form.
|
|
508
|
+
|
|
453
509
|
Pricing data is committed, not fetched — there is no runtime network call,
|
|
454
510
|
ever. That means it can go stale between releases: a provider can change a
|
|
455
511
|
price the day after `usage-tab` ships, and calculations will use the old
|