ai-sdk-cost-calculator 0.1.21 → 0.1.22

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
@@ -1536,32 +1536,38 @@ function totalsToUsage(totals) {
1536
1536
  }
1537
1537
  };
1538
1538
  }
1539
+ var nonNegative = (n) => n > 0 ? n : 0;
1539
1540
  function getUsageTokenDetails(usage) {
1540
1541
  const inputDetails = usage.inputTokenDetails;
1541
1542
  const outputDetails = usage.outputTokenDetails;
1542
- const cacheReadTokens = inputDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? 0;
1543
- const cacheWriteTokens = inputDetails?.cacheWriteTokens ?? 0;
1543
+ const cacheReadTokens = nonNegative(
1544
+ inputDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? 0
1545
+ );
1546
+ const cacheWriteTokens = nonNegative(inputDetails?.cacheWriteTokens ?? 0);
1544
1547
  let noCacheTokens;
1545
1548
  if (inputDetails?.noCacheTokens !== void 0) {
1546
- noCacheTokens = inputDetails.noCacheTokens;
1549
+ noCacheTokens = nonNegative(inputDetails.noCacheTokens);
1547
1550
  } else if (usage.inputTokens !== void 0) {
1548
- noCacheTokens = Math.max(
1549
- 0,
1551
+ noCacheTokens = nonNegative(
1550
1552
  usage.inputTokens - cacheReadTokens - cacheWriteTokens
1551
1553
  );
1552
1554
  } else {
1553
1555
  noCacheTokens = 0;
1554
1556
  }
1555
- const reasoningTokens = outputDetails?.reasoningTokens ?? usage.reasoningTokens ?? 0;
1557
+ const reasoningTokens = nonNegative(
1558
+ outputDetails?.reasoningTokens ?? usage.reasoningTokens ?? 0
1559
+ );
1556
1560
  let textTokens;
1557
1561
  if (outputDetails?.textTokens !== void 0) {
1558
- textTokens = outputDetails.textTokens;
1562
+ textTokens = nonNegative(outputDetails.textTokens);
1559
1563
  } else if (usage.outputTokens !== void 0) {
1560
- textTokens = Math.max(0, usage.outputTokens - reasoningTokens);
1564
+ textTokens = nonNegative(usage.outputTokens - reasoningTokens);
1561
1565
  } else {
1562
1566
  textTokens = 0;
1563
1567
  }
1564
- const totalInputTokens = usage.inputTokens ?? noCacheTokens + cacheReadTokens + cacheWriteTokens;
1568
+ const componentSum = noCacheTokens + cacheReadTokens + cacheWriteTokens;
1569
+ const reportedInput = usage.inputTokens ?? 0;
1570
+ const totalInputTokens = Math.max(reportedInput, componentSum);
1565
1571
  return {
1566
1572
  noCacheTokens,
1567
1573
  cacheReadTokens,
@@ -1724,9 +1730,9 @@ function calculateCost(options) {
1724
1730
  totalCost: 0
1725
1731
  };
1726
1732
  for (const field of COST_COMPONENT_FIELDS) {
1727
- const rounded = roundToMicroDollars(components[field]);
1728
- breakdown[field] = rounded;
1729
- totalCost += components[field];
1733
+ const value = components[field] > 0 ? components[field] : 0;
1734
+ breakdown[field] = roundToMicroDollars(value);
1735
+ totalCost += value;
1730
1736
  }
1731
1737
  breakdown.totalCost = roundToMicroDollars(totalCost);
1732
1738
  return breakdown;
package/dist/index.mjs CHANGED
@@ -1484,32 +1484,38 @@ function totalsToUsage(totals) {
1484
1484
  }
1485
1485
  };
1486
1486
  }
1487
+ var nonNegative = (n) => n > 0 ? n : 0;
1487
1488
  function getUsageTokenDetails(usage) {
1488
1489
  const inputDetails = usage.inputTokenDetails;
1489
1490
  const outputDetails = usage.outputTokenDetails;
1490
- const cacheReadTokens = inputDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? 0;
1491
- const cacheWriteTokens = inputDetails?.cacheWriteTokens ?? 0;
1491
+ const cacheReadTokens = nonNegative(
1492
+ inputDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? 0
1493
+ );
1494
+ const cacheWriteTokens = nonNegative(inputDetails?.cacheWriteTokens ?? 0);
1492
1495
  let noCacheTokens;
1493
1496
  if (inputDetails?.noCacheTokens !== void 0) {
1494
- noCacheTokens = inputDetails.noCacheTokens;
1497
+ noCacheTokens = nonNegative(inputDetails.noCacheTokens);
1495
1498
  } else if (usage.inputTokens !== void 0) {
1496
- noCacheTokens = Math.max(
1497
- 0,
1499
+ noCacheTokens = nonNegative(
1498
1500
  usage.inputTokens - cacheReadTokens - cacheWriteTokens
1499
1501
  );
1500
1502
  } else {
1501
1503
  noCacheTokens = 0;
1502
1504
  }
1503
- const reasoningTokens = outputDetails?.reasoningTokens ?? usage.reasoningTokens ?? 0;
1505
+ const reasoningTokens = nonNegative(
1506
+ outputDetails?.reasoningTokens ?? usage.reasoningTokens ?? 0
1507
+ );
1504
1508
  let textTokens;
1505
1509
  if (outputDetails?.textTokens !== void 0) {
1506
- textTokens = outputDetails.textTokens;
1510
+ textTokens = nonNegative(outputDetails.textTokens);
1507
1511
  } else if (usage.outputTokens !== void 0) {
1508
- textTokens = Math.max(0, usage.outputTokens - reasoningTokens);
1512
+ textTokens = nonNegative(usage.outputTokens - reasoningTokens);
1509
1513
  } else {
1510
1514
  textTokens = 0;
1511
1515
  }
1512
- const totalInputTokens = usage.inputTokens ?? noCacheTokens + cacheReadTokens + cacheWriteTokens;
1516
+ const componentSum = noCacheTokens + cacheReadTokens + cacheWriteTokens;
1517
+ const reportedInput = usage.inputTokens ?? 0;
1518
+ const totalInputTokens = Math.max(reportedInput, componentSum);
1513
1519
  return {
1514
1520
  noCacheTokens,
1515
1521
  cacheReadTokens,
@@ -1672,9 +1678,9 @@ function calculateCost(options) {
1672
1678
  totalCost: 0
1673
1679
  };
1674
1680
  for (const field of COST_COMPONENT_FIELDS) {
1675
- const rounded = roundToMicroDollars(components[field]);
1676
- breakdown[field] = rounded;
1677
- totalCost += components[field];
1681
+ const value = components[field] > 0 ? components[field] : 0;
1682
+ breakdown[field] = roundToMicroDollars(value);
1683
+ totalCost += value;
1678
1684
  }
1679
1685
  breakdown.totalCost = roundToMicroDollars(totalCost);
1680
1686
  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.22",
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",