ai-sdk-cost-calculator 0.1.21 → 0.1.23

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.
@@ -1 +1 @@
1
- {"version":3,"file":"calculator.d.ts","sourceRoot":"","sources":["../../src/core/calculator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,IAAI,CAAC;AACxC,OAAO,EAA4B,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,SAAS,CAAC;AAMjB,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,kBAAkB,CAAC;IAC1B,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2CAA2C;IAC3C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iDAAiD;IACjD,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,kDAAkD;IAClD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,qDAAqD;IACrD,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,iCAAiC;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AA0DD,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAkL1E;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAU,GAAG,MAAM,CAErE;AAwCD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,QAAQ,GAAE,MAAU,GACnB,MAAM,CAaR"}
1
+ {"version":3,"file":"calculator.d.ts","sourceRoot":"","sources":["../../src/core/calculator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,IAAI,CAAC;AACxC,OAAO,EAA4B,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,SAAS,CAAC;AAMjB,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,kBAAkB,CAAC;IAC1B,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2CAA2C;IAC3C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iDAAiD;IACjD,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,kDAAkD;IAClD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,qDAAqD;IACrD,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,iCAAiC;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AA0DD,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAsL1E;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAU,GAAG,MAAM,CAErE;AAwCD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,QAAQ,GAAE,MAAU,GACnB,MAAM,CAaR"}
@@ -134,6 +134,10 @@ export function calculateCost(options) {
134
134
  imageGenerationCost,
135
135
  additionalCost: 0,
136
136
  };
137
+ // Defensive: clamp every cost component to ≥0. Negative costs are never
138
+ // valid — they only arise from upstream data anomalies (e.g., AI SDK
139
+ // providers occasionally emit negative noCacheTokens when cache size
140
+ // exceeds prompt size). Clamping here guarantees the breakdown contract.
137
141
  let totalCost = 0;
138
142
  const breakdown = {
139
143
  currency: "USD",
@@ -141,9 +145,9 @@ export function calculateCost(options) {
141
145
  totalCost: 0,
142
146
  };
143
147
  for (const field of COST_COMPONENT_FIELDS) {
144
- const rounded = roundToMicroDollars(components[field]);
145
- breakdown[field] = rounded;
146
- totalCost += components[field];
148
+ const value = components[field] > 0 ? components[field] : 0;
149
+ breakdown[field] = roundToMicroDollars(value);
150
+ totalCost += value;
147
151
  }
148
152
  breakdown.totalCost = roundToMicroDollars(totalCost);
149
153
  return breakdown;
package/dist/index.js CHANGED
@@ -458,6 +458,18 @@ var openaiPricing = {
458
458
 
459
459
  // src/pricing/providers/anthropic.ts
460
460
  var anthropicPricing = {
461
+ // Claude Opus 4.7
462
+ "claude-opus-4-7": {
463
+ inputPer1MTokens: 5,
464
+ outputPer1MTokens: 25,
465
+ cacheReadPer1MTokens: 0.5,
466
+ cacheWritePer1MTokens: 6.25,
467
+ longContextThreshold: 2e5,
468
+ longContextInputPer1MTokens: 10,
469
+ longContextOutputPer1MTokens: 37.5,
470
+ longContextCacheReadPer1MTokens: 1,
471
+ longContextCacheWritePer1MTokens: 12.5
472
+ },
461
473
  // Claude Opus 4.6
462
474
  "claude-opus-4-6": {
463
475
  inputPer1MTokens: 5,
@@ -1536,32 +1548,38 @@ function totalsToUsage(totals) {
1536
1548
  }
1537
1549
  };
1538
1550
  }
1551
+ var nonNegative = (n) => n > 0 ? n : 0;
1539
1552
  function getUsageTokenDetails(usage) {
1540
1553
  const inputDetails = usage.inputTokenDetails;
1541
1554
  const outputDetails = usage.outputTokenDetails;
1542
- const cacheReadTokens = inputDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? 0;
1543
- const cacheWriteTokens = inputDetails?.cacheWriteTokens ?? 0;
1555
+ const cacheReadTokens = nonNegative(
1556
+ inputDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? 0
1557
+ );
1558
+ const cacheWriteTokens = nonNegative(inputDetails?.cacheWriteTokens ?? 0);
1544
1559
  let noCacheTokens;
1545
1560
  if (inputDetails?.noCacheTokens !== void 0) {
1546
- noCacheTokens = inputDetails.noCacheTokens;
1561
+ noCacheTokens = nonNegative(inputDetails.noCacheTokens);
1547
1562
  } else if (usage.inputTokens !== void 0) {
1548
- noCacheTokens = Math.max(
1549
- 0,
1563
+ noCacheTokens = nonNegative(
1550
1564
  usage.inputTokens - cacheReadTokens - cacheWriteTokens
1551
1565
  );
1552
1566
  } else {
1553
1567
  noCacheTokens = 0;
1554
1568
  }
1555
- const reasoningTokens = outputDetails?.reasoningTokens ?? usage.reasoningTokens ?? 0;
1569
+ const reasoningTokens = nonNegative(
1570
+ outputDetails?.reasoningTokens ?? usage.reasoningTokens ?? 0
1571
+ );
1556
1572
  let textTokens;
1557
1573
  if (outputDetails?.textTokens !== void 0) {
1558
- textTokens = outputDetails.textTokens;
1574
+ textTokens = nonNegative(outputDetails.textTokens);
1559
1575
  } else if (usage.outputTokens !== void 0) {
1560
- textTokens = Math.max(0, usage.outputTokens - reasoningTokens);
1576
+ textTokens = nonNegative(usage.outputTokens - reasoningTokens);
1561
1577
  } else {
1562
1578
  textTokens = 0;
1563
1579
  }
1564
- const totalInputTokens = usage.inputTokens ?? noCacheTokens + cacheReadTokens + cacheWriteTokens;
1580
+ const componentSum = noCacheTokens + cacheReadTokens + cacheWriteTokens;
1581
+ const reportedInput = usage.inputTokens ?? 0;
1582
+ const totalInputTokens = Math.max(reportedInput, componentSum);
1565
1583
  return {
1566
1584
  noCacheTokens,
1567
1585
  cacheReadTokens,
@@ -1724,9 +1742,9 @@ function calculateCost(options) {
1724
1742
  totalCost: 0
1725
1743
  };
1726
1744
  for (const field of COST_COMPONENT_FIELDS) {
1727
- const rounded = roundToMicroDollars(components[field]);
1728
- breakdown[field] = rounded;
1729
- totalCost += components[field];
1745
+ const value = components[field] > 0 ? components[field] : 0;
1746
+ breakdown[field] = roundToMicroDollars(value);
1747
+ totalCost += value;
1730
1748
  }
1731
1749
  breakdown.totalCost = roundToMicroDollars(totalCost);
1732
1750
  return breakdown;
package/dist/index.mjs CHANGED
@@ -406,6 +406,18 @@ var openaiPricing = {
406
406
 
407
407
  // src/pricing/providers/anthropic.ts
408
408
  var anthropicPricing = {
409
+ // Claude Opus 4.7
410
+ "claude-opus-4-7": {
411
+ inputPer1MTokens: 5,
412
+ outputPer1MTokens: 25,
413
+ cacheReadPer1MTokens: 0.5,
414
+ cacheWritePer1MTokens: 6.25,
415
+ longContextThreshold: 2e5,
416
+ longContextInputPer1MTokens: 10,
417
+ longContextOutputPer1MTokens: 37.5,
418
+ longContextCacheReadPer1MTokens: 1,
419
+ longContextCacheWritePer1MTokens: 12.5
420
+ },
409
421
  // Claude Opus 4.6
410
422
  "claude-opus-4-6": {
411
423
  inputPer1MTokens: 5,
@@ -1484,32 +1496,38 @@ function totalsToUsage(totals) {
1484
1496
  }
1485
1497
  };
1486
1498
  }
1499
+ var nonNegative = (n) => n > 0 ? n : 0;
1487
1500
  function getUsageTokenDetails(usage) {
1488
1501
  const inputDetails = usage.inputTokenDetails;
1489
1502
  const outputDetails = usage.outputTokenDetails;
1490
- const cacheReadTokens = inputDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? 0;
1491
- const cacheWriteTokens = inputDetails?.cacheWriteTokens ?? 0;
1503
+ const cacheReadTokens = nonNegative(
1504
+ inputDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? 0
1505
+ );
1506
+ const cacheWriteTokens = nonNegative(inputDetails?.cacheWriteTokens ?? 0);
1492
1507
  let noCacheTokens;
1493
1508
  if (inputDetails?.noCacheTokens !== void 0) {
1494
- noCacheTokens = inputDetails.noCacheTokens;
1509
+ noCacheTokens = nonNegative(inputDetails.noCacheTokens);
1495
1510
  } else if (usage.inputTokens !== void 0) {
1496
- noCacheTokens = Math.max(
1497
- 0,
1511
+ noCacheTokens = nonNegative(
1498
1512
  usage.inputTokens - cacheReadTokens - cacheWriteTokens
1499
1513
  );
1500
1514
  } else {
1501
1515
  noCacheTokens = 0;
1502
1516
  }
1503
- const reasoningTokens = outputDetails?.reasoningTokens ?? usage.reasoningTokens ?? 0;
1517
+ const reasoningTokens = nonNegative(
1518
+ outputDetails?.reasoningTokens ?? usage.reasoningTokens ?? 0
1519
+ );
1504
1520
  let textTokens;
1505
1521
  if (outputDetails?.textTokens !== void 0) {
1506
- textTokens = outputDetails.textTokens;
1522
+ textTokens = nonNegative(outputDetails.textTokens);
1507
1523
  } else if (usage.outputTokens !== void 0) {
1508
- textTokens = Math.max(0, usage.outputTokens - reasoningTokens);
1524
+ textTokens = nonNegative(usage.outputTokens - reasoningTokens);
1509
1525
  } else {
1510
1526
  textTokens = 0;
1511
1527
  }
1512
- const totalInputTokens = usage.inputTokens ?? noCacheTokens + cacheReadTokens + cacheWriteTokens;
1528
+ const componentSum = noCacheTokens + cacheReadTokens + cacheWriteTokens;
1529
+ const reportedInput = usage.inputTokens ?? 0;
1530
+ const totalInputTokens = Math.max(reportedInput, componentSum);
1513
1531
  return {
1514
1532
  noCacheTokens,
1515
1533
  cacheReadTokens,
@@ -1672,9 +1690,9 @@ function calculateCost(options) {
1672
1690
  totalCost: 0
1673
1691
  };
1674
1692
  for (const field of COST_COMPONENT_FIELDS) {
1675
- const rounded = roundToMicroDollars(components[field]);
1676
- breakdown[field] = rounded;
1677
- totalCost += components[field];
1693
+ const value = components[field] > 0 ? components[field] : 0;
1694
+ breakdown[field] = roundToMicroDollars(value);
1695
+ totalCost += value;
1678
1696
  }
1679
1697
  breakdown.totalCost = roundToMicroDollars(totalCost);
1680
1698
  return breakdown;
@@ -1 +1 @@
1
- {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/shared/usage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,IAAI,CAAC;AAE7C,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,sBAAsB,IAAI,gBAAgB,CAQzD;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,kBAAkB,GACxB,IAAI,CAON;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,kBAAkB,CAmB1E;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,kBAAkB,GACxB,iBAAiB,CAkDnB"}
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/shared/usage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,IAAI,CAAC;AAE7C,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,sBAAsB,IAAI,gBAAgB,CAQzD;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,kBAAkB,GACxB,IAAI,CAON;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,kBAAkB,CAmB1E;AASD,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,kBAAkB,GACxB,iBAAiB,CAwDnB"}
@@ -33,40 +33,52 @@ export function totalsToUsage(totals) {
33
33
  },
34
34
  };
35
35
  }
36
+ /** Clamp negative token counts to 0. Some AI SDK providers (notably Google
37
+ * with context caching) can report `noCacheTokens` as negative when
38
+ * cachedContentTokenCount exceeds promptTokenCount (the cache blob is
39
+ * larger than what the prompt actually consumed). Negative tokens produce
40
+ * negative costs, which is never correct. */
41
+ const nonNegative = (n) => (n > 0 ? n : 0);
36
42
  export function getUsageTokenDetails(usage) {
37
43
  const inputDetails = usage.inputTokenDetails;
38
44
  const outputDetails = usage.outputTokenDetails;
39
45
  // Cache read: prefer details, fall back to deprecated top-level cachedInputTokens
40
- const cacheReadTokens = inputDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? 0;
41
- const cacheWriteTokens = inputDetails?.cacheWriteTokens ?? 0;
46
+ const cacheReadTokens = nonNegative(inputDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? 0);
47
+ const cacheWriteTokens = nonNegative(inputDetails?.cacheWriteTokens ?? 0);
42
48
  // noCacheTokens: prefer details. If details exist but noCacheTokens is
43
49
  // undefined (some providers populate only cacheRead), derive from
44
50
  // inputTokens - cacheRead - cacheWrite. If no details at all, use inputTokens.
51
+ // Always clamp ≥0: providers can report negatives when cache > prompt.
45
52
  let noCacheTokens;
46
53
  if (inputDetails?.noCacheTokens !== undefined) {
47
- noCacheTokens = inputDetails.noCacheTokens;
54
+ noCacheTokens = nonNegative(inputDetails.noCacheTokens);
48
55
  }
49
56
  else if (usage.inputTokens !== undefined) {
50
- noCacheTokens = Math.max(0, usage.inputTokens - cacheReadTokens - cacheWriteTokens);
57
+ noCacheTokens = nonNegative(usage.inputTokens - cacheReadTokens - cacheWriteTokens);
51
58
  }
52
59
  else {
53
60
  noCacheTokens = 0;
54
61
  }
55
62
  // reasoningTokens: prefer details, fall back to deprecated top-level
56
- const reasoningTokens = outputDetails?.reasoningTokens ?? usage.reasoningTokens ?? 0;
63
+ const reasoningTokens = nonNegative(outputDetails?.reasoningTokens ?? usage.reasoningTokens ?? 0);
57
64
  // textTokens: prefer details. If details exist but textTokens is undefined,
58
65
  // derive from outputTokens - reasoningTokens. If no details, use outputTokens.
59
66
  let textTokens;
60
67
  if (outputDetails?.textTokens !== undefined) {
61
- textTokens = outputDetails.textTokens;
68
+ textTokens = nonNegative(outputDetails.textTokens);
62
69
  }
63
70
  else if (usage.outputTokens !== undefined) {
64
- textTokens = Math.max(0, usage.outputTokens - reasoningTokens);
71
+ textTokens = nonNegative(usage.outputTokens - reasoningTokens);
65
72
  }
66
73
  else {
67
74
  textTokens = 0;
68
75
  }
69
- const totalInputTokens = usage.inputTokens ?? noCacheTokens + cacheReadTokens + cacheWriteTokens;
76
+ // Total input for long-context threshold check. Use the larger of:
77
+ // - reported inputTokens
78
+ // - sum of components (in case inputTokens is stale or undercounts)
79
+ const componentSum = noCacheTokens + cacheReadTokens + cacheWriteTokens;
80
+ const reportedInput = usage.inputTokens ?? 0;
81
+ const totalInputTokens = Math.max(reportedInput, componentSum);
70
82
  return {
71
83
  noCacheTokens,
72
84
  cacheReadTokens,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-sdk-cost-calculator",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Cost calculator for Vercel AI SDK token usage. Supports OpenAI, Anthropic, Google, xAI (Grok), and DeepSeek with long context pricing, prompt caching, and reasoning tokens.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",