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

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,23 @@
1
1
  # @mastra/observability
2
2
 
3
+ ## 1.17.1-alpha.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixed the `__truncated` marker on span data. It now counts only the fields dropped by the object-key limit, so traces no longer report more omitted keys than were really dropped. Values beyond the limit are no longer read. ([#21630](https://github.com/mastra-ai/mastra/pull/21630))
8
+
9
+ - Updated dependencies [[`4e7a421`](https://github.com/mastra-ai/mastra/commit/4e7a421dce8a48742f785d1e93ad2f43a572b282), [`242e324`](https://github.com/mastra-ai/mastra/commit/242e3241e73cbd5c9bb86a31ebb49ca0256488d4), [`217e967`](https://github.com/mastra-ai/mastra/commit/217e9672d8b3160eb729d8e9f0044949e88da239), [`d774e89`](https://github.com/mastra-ai/mastra/commit/d774e8930c781df8c9effe3763e6b501c099b6cc), [`9c27a53`](https://github.com/mastra-ai/mastra/commit/9c27a53cd9d3de4f3f025bc387d94ce371c33f95), [`dff25a1`](https://github.com/mastra-ai/mastra/commit/dff25a1103fa72ee082a9b6f805ebeb5ce400753), [`217e967`](https://github.com/mastra-ai/mastra/commit/217e9672d8b3160eb729d8e9f0044949e88da239), [`7f78585`](https://github.com/mastra-ai/mastra/commit/7f785857e401570e2ffb316911f126ed363aa537), [`f2a4afd`](https://github.com/mastra-ai/mastra/commit/f2a4afd7e37e809669001ed17724b341a5c1f45e), [`d438148`](https://github.com/mastra-ai/mastra/commit/d438148e222c1e2fb3c652725ce75680962ebec4), [`ba05fe0`](https://github.com/mastra-ai/mastra/commit/ba05fe0738f70cb686777546e968237d09269142), [`d26a8d4`](https://github.com/mastra-ai/mastra/commit/d26a8d4281f28414715b333c85bedaf70d0b2890), [`677cdc6`](https://github.com/mastra-ai/mastra/commit/677cdc6af564dec29a13464d12b7ab2a4efc22e9), [`a318490`](https://github.com/mastra-ai/mastra/commit/a318490e17da32f338d50929c770d901a9b3dd72), [`763e0c6`](https://github.com/mastra-ai/mastra/commit/763e0c61e04d76ad9a9efd301aa57525ca0cbea9), [`23e0be2`](https://github.com/mastra-ai/mastra/commit/23e0be261381e49534b4ff3101c60ee64a946cbf), [`7fc8806`](https://github.com/mastra-ai/mastra/commit/7fc880627d3cbf995d31ea0e8b807bf15417e651), [`0e02eac`](https://github.com/mastra-ai/mastra/commit/0e02eacdb2e30e1697a41910b41163742a181dc1), [`4df174c`](https://github.com/mastra-ai/mastra/commit/4df174c32bddf093a82f273070b8380aef7c9e90), [`f7c25b5`](https://github.com/mastra-ai/mastra/commit/f7c25b5106ddfb48e591f98df7a51e0f2dd01dba), [`dc09cc1`](https://github.com/mastra-ai/mastra/commit/dc09cc1083d861cde192c1cd235324dc75b8c731), [`36b4649`](https://github.com/mastra-ai/mastra/commit/36b4649045a3a380cbab8ceca866db4086223aff), [`377eb81`](https://github.com/mastra-ai/mastra/commit/377eb81ce43b964e3a6b541df172da74a8ff3716)]:
10
+ - @mastra/core@1.60.0-alpha.8
11
+
12
+ ## 1.17.1-alpha.1
13
+
14
+ ### Patch Changes
15
+
16
+ - 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))
17
+
18
+ - 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)]:
19
+ - @mastra/core@1.60.0-alpha.4
20
+
3
21
  ## 1.17.1-alpha.0
4
22
 
5
23
  ### 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
@@ -217,6 +217,12 @@ const LOGGER_METHODS = [
217
217
  "warn",
218
218
  "error"
219
219
  ];
220
+ /** Keys whose value decides the outcome in `shouldStripEntry()`. */
221
+ const VALUE_STRIP_KEYS = /* @__PURE__ */ new Set([
222
+ "logger",
223
+ "tracingContext",
224
+ ...FUNCTION_KEYS_TO_STRIP
225
+ ]);
220
226
  const DEFAULT_DEEP_CLEAN_OPTIONS = Object.freeze({
221
227
  keysToStrip: [],
222
228
  maxDepth: 8,
@@ -298,6 +304,25 @@ function shouldStripEntry(key, value, stripSet) {
298
304
  if (key === "logger") return typeof value === "function" || isLoggerLike(value);
299
305
  return FUNCTION_KEYS_TO_STRIP.has(key) && typeof value === "function";
300
306
  }
307
+ /**
308
+ * Whether an entry past the object-key limit would have been stripped anyway.
309
+ *
310
+ * The entry is dropped either way, so the value is taken from its property descriptor
311
+ * rather than read: a getter must not run for data that is being discarded. That keeps
312
+ * the truncation count the same wherever a runtime-shaped key sits. An accessor cannot
313
+ * be classified without invoking it, so it is left to the limit and counted.
314
+ */
315
+ function wouldBeStripped(key, val, stripSet) {
316
+ if (!VALUE_STRIP_KEYS.has(key)) return false;
317
+ let descriptor;
318
+ try {
319
+ descriptor = Object.getOwnPropertyDescriptor(val, key);
320
+ } catch {
321
+ return false;
322
+ }
323
+ if (!descriptor || !("value" in descriptor)) return false;
324
+ return shouldStripEntry(key, descriptor.value, stripSet);
325
+ }
301
326
  function restoreSerializedMapKey(keyType, key) {
302
327
  switch (keyType) {
303
328
  case "undefined": return;
@@ -483,25 +508,22 @@ function deepClean(value, options = DEFAULT_DEEP_CLEAN_OPTIONS) {
483
508
  return formatSerializationError(error);
484
509
  }
485
510
  let keyCount = 0;
511
+ let omittedByLimit = 0;
486
512
  for (const key of keys) {
487
513
  if (stripSet.has(key)) continue;
514
+ if (keyCount >= maxObjectKeys) {
515
+ if (!wouldBeStripped(key, val, stripSet)) omittedByLimit++;
516
+ continue;
517
+ }
488
518
  let rawValue;
489
519
  try {
490
520
  rawValue = val[key];
491
521
  } catch (error) {
492
- if (keyCount >= maxObjectKeys) {
493
- cleaned["__truncated"] = `${keys.length - keyCount} more keys omitted`;
494
- break;
495
- }
496
522
  cleaned[key] = formatSerializationError(error);
497
523
  keyCount++;
498
524
  continue;
499
525
  }
500
526
  if (shouldStripEntry(key, rawValue, stripSet)) continue;
501
- if (keyCount >= maxObjectKeys) {
502
- cleaned["__truncated"] = `${keys.length - keyCount} more keys omitted`;
503
- break;
504
- }
505
527
  try {
506
528
  cleaned[key] = helper(rawValue, depth + 1);
507
529
  keyCount++;
@@ -510,6 +532,7 @@ function deepClean(value, options = DEFAULT_DEEP_CLEAN_OPTIONS) {
510
532
  keyCount++;
511
533
  }
512
534
  }
535
+ if (omittedByLimit > 0) cleaned["__truncated"] = `${omittedByLimit} more keys omitted`;
513
536
  return cleaned;
514
537
  } finally {
515
538
  if (typeof val === "object" && val !== null) ancestors.delete(val);
@@ -1102,6 +1125,8 @@ const MINIFIED_METER_TO_CANONICAL = {
1102
1125
  ot: "output_tokens",
1103
1126
  icrt: "input_cache_read_tokens",
1104
1127
  icwt: "input_cache_write_tokens",
1128
+ icwt5m: "input_cache_write_5m_tokens",
1129
+ icwt1h: "input_cache_write_1h_tokens",
1105
1130
  iat: "input_audio_tokens",
1106
1131
  oat: "output_audio_tokens",
1107
1132
  ort: "output_reasoning_tokens"
@@ -1158,6 +1183,15 @@ function parsePricingModelText(content) {
1158
1183
  }
1159
1184
  return pricingModels;
1160
1185
  }
1186
+ function expandRates(row, tier) {
1187
+ const rates = Object.fromEntries(Object.entries(tier.r).map(([meter, value]) => [MINIFIED_METER_TO_CANONICAL[meter], value.c]));
1188
+ const cacheWriteRate = rates.input_cache_write_tokens;
1189
+ if (row.m.includes("claude") && typeof cacheWriteRate === "number") {
1190
+ rates.input_cache_write_5m_tokens ??= cacheWriteRate;
1191
+ rates.input_cache_write_1h_tokens ??= cacheWriteRate * 1.6;
1192
+ }
1193
+ return rates;
1194
+ }
1161
1195
  function expandPricingModelRow(row) {
1162
1196
  return new PricingModel({
1163
1197
  id: row.i,
@@ -1172,7 +1206,7 @@ function expandPricingModelRow(row) {
1172
1206
  op: condition.op,
1173
1207
  value: condition.value
1174
1208
  })),
1175
- rates: Object.fromEntries(Object.entries(tier.r).map(([meter, value]) => [MINIFIED_METER_TO_CANONICAL[meter], value.c]))
1209
+ rates: expandRates(row, tier)
1176
1210
  }))
1177
1211
  });
1178
1212
  }
@@ -1303,6 +1337,8 @@ const PricingMeter = {
1303
1337
  INPUT_AUDIO_TOKENS: "input_audio_tokens",
1304
1338
  INPUT_CACHE_READ_TOKENS: "input_cache_read_tokens",
1305
1339
  INPUT_CACHE_WRITE_TOKENS: "input_cache_write_tokens",
1340
+ INPUT_CACHE_WRITE_5M_TOKENS: "input_cache_write_5m_tokens",
1341
+ INPUT_CACHE_WRITE_1H_TOKENS: "input_cache_write_1h_tokens",
1306
1342
  INPUT_IMAGE_TOKENS: "input_image_tokens",
1307
1343
  OUTPUT_TOKENS: "output_tokens",
1308
1344
  OUTPUT_AUDIO_TOKENS: "output_audio_tokens",
@@ -1315,6 +1351,8 @@ const TokenMetrics = {
1315
1351
  INPUT_TEXT: "mastra_model_input_text_tokens",
1316
1352
  INPUT_CACHE_READ: "mastra_model_input_cache_read_tokens",
1317
1353
  INPUT_CACHE_WRITE: "mastra_model_input_cache_write_tokens",
1354
+ INPUT_CACHE_WRITE_5M: "mastra_model_input_cache_write_5m_tokens",
1355
+ INPUT_CACHE_WRITE_1H: "mastra_model_input_cache_write_1h_tokens",
1318
1356
  INPUT_AUDIO: "mastra_model_input_audio_tokens",
1319
1357
  INPUT_IMAGE: "mastra_model_input_image_tokens",
1320
1358
  OUTPUT_TEXT: "mastra_model_output_text_tokens",
@@ -1344,6 +1382,8 @@ function getTokenMetricSamples(usage) {
1344
1382
  pushIfPositive(TokenMetrics.INPUT_TEXT, usage.inputDetails.text);
1345
1383
  pushIfPositive(TokenMetrics.INPUT_CACHE_READ, usage.inputDetails.cacheRead);
1346
1384
  pushIfPositive(TokenMetrics.INPUT_CACHE_WRITE, usage.inputDetails.cacheWrite);
1385
+ pushIfPositive(TokenMetrics.INPUT_CACHE_WRITE_5M, usage.inputDetails.cacheWrite5m);
1386
+ pushIfPositive(TokenMetrics.INPUT_CACHE_WRITE_1H, usage.inputDetails.cacheWrite1h);
1347
1387
  pushIfPositive(TokenMetrics.INPUT_AUDIO, usage.inputDetails.audio);
1348
1388
  pushIfPositive(TokenMetrics.INPUT_IMAGE, usage.inputDetails.image);
1349
1389
  }
@@ -1410,15 +1450,40 @@ function estimateCosts(args, pricingRegistry = PricingRegistry.getGlobal()) {
1410
1450
  results.set(TokenMetrics.INPUT_CACHE_READ, result.costContext);
1411
1451
  inputDetailResults.push(result);
1412
1452
  }
1413
- if (usage.inputDetails?.cacheWrite) {
1453
+ const cacheWriteDetailResults = [];
1454
+ const cacheWrite5m = usage.inputDetails?.cacheWrite5m ?? 0;
1455
+ const cacheWrite1h = usage.inputDetails?.cacheWrite1h ?? 0;
1456
+ if (cacheWrite5m > 0) {
1457
+ const result = estimateCostForMeter({
1458
+ meter: PricingMeter.INPUT_CACHE_WRITE_5M_TOKENS,
1459
+ tokenCount: cacheWrite5m,
1460
+ ...estimateFields
1461
+ });
1462
+ results.set(TokenMetrics.INPUT_CACHE_WRITE_5M, result.costContext);
1463
+ cacheWriteDetailResults.push(result);
1464
+ inputDetailResults.push(result);
1465
+ }
1466
+ if (cacheWrite1h > 0) {
1467
+ const result = estimateCostForMeter({
1468
+ meter: PricingMeter.INPUT_CACHE_WRITE_1H_TOKENS,
1469
+ tokenCount: cacheWrite1h,
1470
+ ...estimateFields
1471
+ });
1472
+ results.set(TokenMetrics.INPUT_CACHE_WRITE_1H, result.costContext);
1473
+ cacheWriteDetailResults.push(result);
1474
+ inputDetailResults.push(result);
1475
+ }
1476
+ const unclassifiedCacheWrite = Math.max(0, (usage.inputDetails?.cacheWrite ?? 0) - cacheWrite5m - cacheWrite1h);
1477
+ if (unclassifiedCacheWrite > 0) {
1414
1478
  const result = estimateCostForMeter({
1415
1479
  meter: PricingMeter.INPUT_CACHE_WRITE_TOKENS,
1416
- tokenCount: usage.inputDetails.cacheWrite,
1480
+ tokenCount: unclassifiedCacheWrite,
1417
1481
  ...estimateFields
1418
1482
  });
1419
- results.set(TokenMetrics.INPUT_CACHE_WRITE, result.costContext);
1483
+ cacheWriteDetailResults.push(result);
1420
1484
  inputDetailResults.push(result);
1421
1485
  }
1486
+ if (cacheWriteDetailResults.length > 0) setCombinedCostContext(results, TokenMetrics.INPUT_CACHE_WRITE, cacheWriteDetailResults, pricingModel, costMetadata);
1422
1487
  if (usage.inputDetails?.image) {
1423
1488
  const result = estimateCostForMeter({
1424
1489
  meter: PricingMeter.INPUT_IMAGE_TOKENS,
@@ -1495,6 +1560,20 @@ function estimateCosts(args, pricingRegistry = PricingRegistry.getGlobal()) {
1495
1560
  function applyErrorContextForUsage(results, usage, errorContext) {
1496
1561
  for (const sample of getTokenMetricSamples(usage)) results.set(sample.name, errorContext);
1497
1562
  }
1563
+ function setCombinedCostContext(results, metric, detailResults, pricingModel, costMetadata) {
1564
+ const estimatedCosts = detailResults.map((result) => result.costContext.estimatedCost).filter((value) => typeof value === "number");
1565
+ const hasFailedCost = detailResults.some((result) => !result.success);
1566
+ results.set(metric, {
1567
+ provider: pricingModel.provider,
1568
+ model: pricingModel.model,
1569
+ ...estimatedCosts.length > 0 && { estimatedCost: estimatedCosts.reduce((sum, value) => sum + value, 0) },
1570
+ ...estimatedCosts.length > 0 && { costUnit: pricingModel.currency },
1571
+ costMetadata: hasFailedCost ? {
1572
+ ...costMetadata,
1573
+ error: "partial_cost"
1574
+ } : { ...costMetadata }
1575
+ });
1576
+ }
1498
1577
  function setAggregateCostContext(args) {
1499
1578
  const { results, totalMetric, fallbackMeter, totalTokenCount, detailResults, pricingModel, pricingTier, costMetadata } = args;
1500
1579
  if (totalTokenCount == null) return;
@@ -1735,14 +1814,24 @@ function extractUsageMetrics(usage, providerMetadata) {
1735
1814
  if (isDefined(aiSdkDetails?.cacheReadTokens)) inputDetails.cacheRead = aiSdkDetails.cacheReadTokens;
1736
1815
  if (isDefined(aiSdkDetails?.cacheWriteTokens)) inputDetails.cacheWrite = aiSdkDetails.cacheWriteTokens;
1737
1816
  if (!isDefined(inputDetails.cacheRead) && isDefined(usage.cachedInputTokens)) inputDetails.cacheRead = usage.cachedInputTokens;
1817
+ if (isDefined(usage.cacheCreationInputTokens5m)) inputDetails.cacheWrite5m = usage.cacheCreationInputTokens5m;
1818
+ if (isDefined(usage.cacheCreationInputTokens1h)) inputDetails.cacheWrite1h = usage.cacheCreationInputTokens1h;
1738
1819
  if (!isDefined(inputDetails.cacheWrite) && isDefined(usage.cacheCreationInputTokens)) inputDetails.cacheWrite = usage.cacheCreationInputTokens;
1820
+ if (!isDefined(inputDetails.cacheWrite) && (isDefined(inputDetails.cacheWrite5m) || isDefined(inputDetails.cacheWrite1h))) inputDetails.cacheWrite = (inputDetails.cacheWrite5m ?? 0) + (inputDetails.cacheWrite1h ?? 0);
1739
1821
  if (isDefined(usage.reasoningTokens)) outputDetails.reasoning = usage.reasoningTokens;
1740
1822
  const anthropic = providerMetadata?.anthropic;
1741
1823
  if (anthropic) {
1742
1824
  const rawV3InputUsage = isV3RawUsage(usage.raw) ? usage.raw.inputTokens : void 0;
1743
1825
  const hasV3CachedTotals = rawV3InputUsage?.total !== void 0 && (rawV3InputUsage.cacheRead !== void 0 || rawV3InputUsage.cacheWrite !== void 0);
1744
1826
  if (!isDefined(inputDetails.cacheRead) && isDefined(anthropic.cacheReadInputTokens)) inputDetails.cacheRead = anthropic.cacheReadInputTokens;
1745
- if (!isDefined(inputDetails.cacheWrite) && isDefined(anthropic.cacheCreationInputTokens)) inputDetails.cacheWrite = anthropic.cacheCreationInputTokens;
1827
+ const cacheWrite5m = anthropic.cacheCreation?.ephemeral_5m_input_tokens ?? anthropic.cacheCreation?.ephemeral5mInputTokens;
1828
+ const cacheWrite1h = anthropic.cacheCreation?.ephemeral_1h_input_tokens ?? anthropic.cacheCreation?.ephemeral1hInputTokens;
1829
+ if (!isDefined(inputDetails.cacheWrite5m) && isDefined(cacheWrite5m)) inputDetails.cacheWrite5m = cacheWrite5m;
1830
+ if (!isDefined(inputDetails.cacheWrite1h) && isDefined(cacheWrite1h)) inputDetails.cacheWrite1h = cacheWrite1h;
1831
+ if (!isDefined(inputDetails.cacheWrite)) {
1832
+ if (isDefined(anthropic.cacheCreationInputTokens)) inputDetails.cacheWrite = anthropic.cacheCreationInputTokens;
1833
+ else if (isDefined(cacheWrite5m) || isDefined(cacheWrite1h)) inputDetails.cacheWrite = (cacheWrite5m ?? 0) + (cacheWrite1h ?? 0);
1834
+ }
1746
1835
  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
1836
  }
1748
1837
  const google = providerMetadata?.google;
@@ -1783,6 +1872,8 @@ function mergeInputDetails(a, b) {
1783
1872
  text: addOptional(a.text, b.text),
1784
1873
  cacheRead: addOptional(a.cacheRead, b.cacheRead),
1785
1874
  cacheWrite: addOptional(a.cacheWrite, b.cacheWrite),
1875
+ cacheWrite5m: addOptional(a.cacheWrite5m, b.cacheWrite5m),
1876
+ cacheWrite1h: addOptional(a.cacheWrite1h, b.cacheWrite1h),
1786
1877
  audio: addOptional(a.audio, b.audio),
1787
1878
  image: addOptional(a.image, b.image)
1788
1879
  };