@mastra/observability 1.17.1-alpha.0 → 1.17.1-alpha.1

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # @mastra/observability
2
2
 
3
+ ## 1.17.1-alpha.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixed Anthropic cache-write cost estimates by applying TTL-specific rates without double counting aggregate tokens. ([#21563](https://github.com/mastra-ai/mastra/pull/21563))
8
+
9
+ - Updated dependencies [[`d7e6745`](https://github.com/mastra-ai/mastra/commit/d7e67456954863c55440ea9c49bc6ceb9949972d), [`9acb50f`](https://github.com/mastra-ai/mastra/commit/9acb50f71cec9c362f06820033f90ae6b1f8282f), [`46e9e3f`](https://github.com/mastra-ai/mastra/commit/46e9e3f73babe1bc70080a596cf2ac0b9da48519), [`3f9a190`](https://github.com/mastra-ai/mastra/commit/3f9a19057c027155867b9317294ee4ca7bd0581a), [`e8808e3`](https://github.com/mastra-ai/mastra/commit/e8808e3d8eb585a2565be53e56a7e0e1477352a4), [`d4be8c1`](https://github.com/mastra-ai/mastra/commit/d4be8c1739d22d621e3f78790e1dd5eb5ecc3589), [`a5d2eb1`](https://github.com/mastra-ai/mastra/commit/a5d2eb10347eade1ae2816d88f466c25186c54a5), [`e81744c`](https://github.com/mastra-ai/mastra/commit/e81744cd13c46619c142dc521dc0baac47607a84)]:
10
+ - @mastra/core@1.60.0-alpha.4
11
+
3
12
  ## 1.17.1-alpha.0
4
13
 
5
14
  ### Patch Changes
package/README.md CHANGED
@@ -56,9 +56,11 @@ Metrics are automatically extracted from span lifecycle events by `AutoExtracted
56
56
  - `mastra_workflow_duration_ms`
57
57
  - `mastra_model_duration_ms`
58
58
  - `mastra_model_total_input_tokens` / `mastra_model_total_output_tokens`
59
- - `mastra_model_input_text_tokens` / `mastra_model_input_cache_read_tokens` / `mastra_model_input_cache_write_tokens` / `mastra_model_input_audio_tokens` / `mastra_model_input_image_tokens`
59
+ - `mastra_model_input_text_tokens` / `mastra_model_input_cache_read_tokens` / `mastra_model_input_cache_write_tokens` / `mastra_model_input_cache_write_5m_tokens` / `mastra_model_input_cache_write_1h_tokens` / `mastra_model_input_audio_tokens` / `mastra_model_input_image_tokens`
60
60
  - `mastra_model_output_text_tokens` / `mastra_model_output_reasoning_tokens` / `mastra_model_output_audio_tokens` / `mastra_model_output_image_tokens`
61
61
 
62
+ For Anthropic models, the aggregate cache-write metric remains available while the 5-minute and 1-hour metrics preserve the provider's TTL-specific token counts and pricing.
63
+
62
64
  Auto-extracted metrics carry labels: `entity_type`, `entity_name`, `status`, plus `model` and `provider` on model generation spans.
63
65
 
64
66
  ### Structured logging
package/dist/index.cjs CHANGED
@@ -1102,6 +1102,8 @@ const MINIFIED_METER_TO_CANONICAL = {
1102
1102
  ot: "output_tokens",
1103
1103
  icrt: "input_cache_read_tokens",
1104
1104
  icwt: "input_cache_write_tokens",
1105
+ icwt5m: "input_cache_write_5m_tokens",
1106
+ icwt1h: "input_cache_write_1h_tokens",
1105
1107
  iat: "input_audio_tokens",
1106
1108
  oat: "output_audio_tokens",
1107
1109
  ort: "output_reasoning_tokens"
@@ -1158,6 +1160,15 @@ function parsePricingModelText(content) {
1158
1160
  }
1159
1161
  return pricingModels;
1160
1162
  }
1163
+ function expandRates(row, tier) {
1164
+ const rates = Object.fromEntries(Object.entries(tier.r).map(([meter, value]) => [MINIFIED_METER_TO_CANONICAL[meter], value.c]));
1165
+ const cacheWriteRate = rates.input_cache_write_tokens;
1166
+ if (row.m.includes("claude") && typeof cacheWriteRate === "number") {
1167
+ rates.input_cache_write_5m_tokens ??= cacheWriteRate;
1168
+ rates.input_cache_write_1h_tokens ??= cacheWriteRate * 1.6;
1169
+ }
1170
+ return rates;
1171
+ }
1161
1172
  function expandPricingModelRow(row) {
1162
1173
  return new PricingModel({
1163
1174
  id: row.i,
@@ -1172,7 +1183,7 @@ function expandPricingModelRow(row) {
1172
1183
  op: condition.op,
1173
1184
  value: condition.value
1174
1185
  })),
1175
- rates: Object.fromEntries(Object.entries(tier.r).map(([meter, value]) => [MINIFIED_METER_TO_CANONICAL[meter], value.c]))
1186
+ rates: expandRates(row, tier)
1176
1187
  }))
1177
1188
  });
1178
1189
  }
@@ -1303,6 +1314,8 @@ const PricingMeter = {
1303
1314
  INPUT_AUDIO_TOKENS: "input_audio_tokens",
1304
1315
  INPUT_CACHE_READ_TOKENS: "input_cache_read_tokens",
1305
1316
  INPUT_CACHE_WRITE_TOKENS: "input_cache_write_tokens",
1317
+ INPUT_CACHE_WRITE_5M_TOKENS: "input_cache_write_5m_tokens",
1318
+ INPUT_CACHE_WRITE_1H_TOKENS: "input_cache_write_1h_tokens",
1306
1319
  INPUT_IMAGE_TOKENS: "input_image_tokens",
1307
1320
  OUTPUT_TOKENS: "output_tokens",
1308
1321
  OUTPUT_AUDIO_TOKENS: "output_audio_tokens",
@@ -1315,6 +1328,8 @@ const TokenMetrics = {
1315
1328
  INPUT_TEXT: "mastra_model_input_text_tokens",
1316
1329
  INPUT_CACHE_READ: "mastra_model_input_cache_read_tokens",
1317
1330
  INPUT_CACHE_WRITE: "mastra_model_input_cache_write_tokens",
1331
+ INPUT_CACHE_WRITE_5M: "mastra_model_input_cache_write_5m_tokens",
1332
+ INPUT_CACHE_WRITE_1H: "mastra_model_input_cache_write_1h_tokens",
1318
1333
  INPUT_AUDIO: "mastra_model_input_audio_tokens",
1319
1334
  INPUT_IMAGE: "mastra_model_input_image_tokens",
1320
1335
  OUTPUT_TEXT: "mastra_model_output_text_tokens",
@@ -1344,6 +1359,8 @@ function getTokenMetricSamples(usage) {
1344
1359
  pushIfPositive(TokenMetrics.INPUT_TEXT, usage.inputDetails.text);
1345
1360
  pushIfPositive(TokenMetrics.INPUT_CACHE_READ, usage.inputDetails.cacheRead);
1346
1361
  pushIfPositive(TokenMetrics.INPUT_CACHE_WRITE, usage.inputDetails.cacheWrite);
1362
+ pushIfPositive(TokenMetrics.INPUT_CACHE_WRITE_5M, usage.inputDetails.cacheWrite5m);
1363
+ pushIfPositive(TokenMetrics.INPUT_CACHE_WRITE_1H, usage.inputDetails.cacheWrite1h);
1347
1364
  pushIfPositive(TokenMetrics.INPUT_AUDIO, usage.inputDetails.audio);
1348
1365
  pushIfPositive(TokenMetrics.INPUT_IMAGE, usage.inputDetails.image);
1349
1366
  }
@@ -1410,15 +1427,40 @@ function estimateCosts(args, pricingRegistry = PricingRegistry.getGlobal()) {
1410
1427
  results.set(TokenMetrics.INPUT_CACHE_READ, result.costContext);
1411
1428
  inputDetailResults.push(result);
1412
1429
  }
1413
- if (usage.inputDetails?.cacheWrite) {
1430
+ const cacheWriteDetailResults = [];
1431
+ const cacheWrite5m = usage.inputDetails?.cacheWrite5m ?? 0;
1432
+ const cacheWrite1h = usage.inputDetails?.cacheWrite1h ?? 0;
1433
+ if (cacheWrite5m > 0) {
1434
+ const result = estimateCostForMeter({
1435
+ meter: PricingMeter.INPUT_CACHE_WRITE_5M_TOKENS,
1436
+ tokenCount: cacheWrite5m,
1437
+ ...estimateFields
1438
+ });
1439
+ results.set(TokenMetrics.INPUT_CACHE_WRITE_5M, result.costContext);
1440
+ cacheWriteDetailResults.push(result);
1441
+ inputDetailResults.push(result);
1442
+ }
1443
+ if (cacheWrite1h > 0) {
1444
+ const result = estimateCostForMeter({
1445
+ meter: PricingMeter.INPUT_CACHE_WRITE_1H_TOKENS,
1446
+ tokenCount: cacheWrite1h,
1447
+ ...estimateFields
1448
+ });
1449
+ results.set(TokenMetrics.INPUT_CACHE_WRITE_1H, result.costContext);
1450
+ cacheWriteDetailResults.push(result);
1451
+ inputDetailResults.push(result);
1452
+ }
1453
+ const unclassifiedCacheWrite = Math.max(0, (usage.inputDetails?.cacheWrite ?? 0) - cacheWrite5m - cacheWrite1h);
1454
+ if (unclassifiedCacheWrite > 0) {
1414
1455
  const result = estimateCostForMeter({
1415
1456
  meter: PricingMeter.INPUT_CACHE_WRITE_TOKENS,
1416
- tokenCount: usage.inputDetails.cacheWrite,
1457
+ tokenCount: unclassifiedCacheWrite,
1417
1458
  ...estimateFields
1418
1459
  });
1419
- results.set(TokenMetrics.INPUT_CACHE_WRITE, result.costContext);
1460
+ cacheWriteDetailResults.push(result);
1420
1461
  inputDetailResults.push(result);
1421
1462
  }
1463
+ if (cacheWriteDetailResults.length > 0) setCombinedCostContext(results, TokenMetrics.INPUT_CACHE_WRITE, cacheWriteDetailResults, pricingModel, costMetadata);
1422
1464
  if (usage.inputDetails?.image) {
1423
1465
  const result = estimateCostForMeter({
1424
1466
  meter: PricingMeter.INPUT_IMAGE_TOKENS,
@@ -1495,6 +1537,20 @@ function estimateCosts(args, pricingRegistry = PricingRegistry.getGlobal()) {
1495
1537
  function applyErrorContextForUsage(results, usage, errorContext) {
1496
1538
  for (const sample of getTokenMetricSamples(usage)) results.set(sample.name, errorContext);
1497
1539
  }
1540
+ function setCombinedCostContext(results, metric, detailResults, pricingModel, costMetadata) {
1541
+ const estimatedCosts = detailResults.map((result) => result.costContext.estimatedCost).filter((value) => typeof value === "number");
1542
+ const hasFailedCost = detailResults.some((result) => !result.success);
1543
+ results.set(metric, {
1544
+ provider: pricingModel.provider,
1545
+ model: pricingModel.model,
1546
+ ...estimatedCosts.length > 0 && { estimatedCost: estimatedCosts.reduce((sum, value) => sum + value, 0) },
1547
+ ...estimatedCosts.length > 0 && { costUnit: pricingModel.currency },
1548
+ costMetadata: hasFailedCost ? {
1549
+ ...costMetadata,
1550
+ error: "partial_cost"
1551
+ } : { ...costMetadata }
1552
+ });
1553
+ }
1498
1554
  function setAggregateCostContext(args) {
1499
1555
  const { results, totalMetric, fallbackMeter, totalTokenCount, detailResults, pricingModel, pricingTier, costMetadata } = args;
1500
1556
  if (totalTokenCount == null) return;
@@ -1735,14 +1791,24 @@ function extractUsageMetrics(usage, providerMetadata) {
1735
1791
  if (isDefined(aiSdkDetails?.cacheReadTokens)) inputDetails.cacheRead = aiSdkDetails.cacheReadTokens;
1736
1792
  if (isDefined(aiSdkDetails?.cacheWriteTokens)) inputDetails.cacheWrite = aiSdkDetails.cacheWriteTokens;
1737
1793
  if (!isDefined(inputDetails.cacheRead) && isDefined(usage.cachedInputTokens)) inputDetails.cacheRead = usage.cachedInputTokens;
1794
+ if (isDefined(usage.cacheCreationInputTokens5m)) inputDetails.cacheWrite5m = usage.cacheCreationInputTokens5m;
1795
+ if (isDefined(usage.cacheCreationInputTokens1h)) inputDetails.cacheWrite1h = usage.cacheCreationInputTokens1h;
1738
1796
  if (!isDefined(inputDetails.cacheWrite) && isDefined(usage.cacheCreationInputTokens)) inputDetails.cacheWrite = usage.cacheCreationInputTokens;
1797
+ if (!isDefined(inputDetails.cacheWrite) && (isDefined(inputDetails.cacheWrite5m) || isDefined(inputDetails.cacheWrite1h))) inputDetails.cacheWrite = (inputDetails.cacheWrite5m ?? 0) + (inputDetails.cacheWrite1h ?? 0);
1739
1798
  if (isDefined(usage.reasoningTokens)) outputDetails.reasoning = usage.reasoningTokens;
1740
1799
  const anthropic = providerMetadata?.anthropic;
1741
1800
  if (anthropic) {
1742
1801
  const rawV3InputUsage = isV3RawUsage(usage.raw) ? usage.raw.inputTokens : void 0;
1743
1802
  const hasV3CachedTotals = rawV3InputUsage?.total !== void 0 && (rawV3InputUsage.cacheRead !== void 0 || rawV3InputUsage.cacheWrite !== void 0);
1744
1803
  if (!isDefined(inputDetails.cacheRead) && isDefined(anthropic.cacheReadInputTokens)) inputDetails.cacheRead = anthropic.cacheReadInputTokens;
1745
- if (!isDefined(inputDetails.cacheWrite) && isDefined(anthropic.cacheCreationInputTokens)) inputDetails.cacheWrite = anthropic.cacheCreationInputTokens;
1804
+ const cacheWrite5m = anthropic.cacheCreation?.ephemeral_5m_input_tokens ?? anthropic.cacheCreation?.ephemeral5mInputTokens;
1805
+ const cacheWrite1h = anthropic.cacheCreation?.ephemeral_1h_input_tokens ?? anthropic.cacheCreation?.ephemeral1hInputTokens;
1806
+ if (!isDefined(inputDetails.cacheWrite5m) && isDefined(cacheWrite5m)) inputDetails.cacheWrite5m = cacheWrite5m;
1807
+ if (!isDefined(inputDetails.cacheWrite1h) && isDefined(cacheWrite1h)) inputDetails.cacheWrite1h = cacheWrite1h;
1808
+ if (!isDefined(inputDetails.cacheWrite)) {
1809
+ if (isDefined(anthropic.cacheCreationInputTokens)) inputDetails.cacheWrite = anthropic.cacheCreationInputTokens;
1810
+ else if (isDefined(cacheWrite5m) || isDefined(cacheWrite1h)) inputDetails.cacheWrite = (cacheWrite5m ?? 0) + (cacheWrite1h ?? 0);
1811
+ }
1746
1812
  if (!(hasV3CachedTotals || isDefined(usage.cachedInputTokens) && usage.cachedInputTokens > 0 || isDefined(usage.cacheCreationInputTokens) && usage.cacheCreationInputTokens > 0) && (isDefined(inputDetails.cacheRead) || isDefined(inputDetails.cacheWrite))) inputTokens = (usage.inputTokens ?? 0) + (inputDetails.cacheRead ?? 0) + (inputDetails.cacheWrite ?? 0);
1747
1813
  }
1748
1814
  const google = providerMetadata?.google;
@@ -1783,6 +1849,8 @@ function mergeInputDetails(a, b) {
1783
1849
  text: addOptional(a.text, b.text),
1784
1850
  cacheRead: addOptional(a.cacheRead, b.cacheRead),
1785
1851
  cacheWrite: addOptional(a.cacheWrite, b.cacheWrite),
1852
+ cacheWrite5m: addOptional(a.cacheWrite5m, b.cacheWrite5m),
1853
+ cacheWrite1h: addOptional(a.cacheWrite1h, b.cacheWrite1h),
1786
1854
  audio: addOptional(a.audio, b.audio),
1787
1855
  image: addOptional(a.image, b.image)
1788
1856
  };