@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/dist/index.js CHANGED
@@ -192,6 +192,12 @@ const LOGGER_METHODS = [
192
192
  "warn",
193
193
  "error"
194
194
  ];
195
+ /** Keys whose value decides the outcome in `shouldStripEntry()`. */
196
+ const VALUE_STRIP_KEYS = /* @__PURE__ */ new Set([
197
+ "logger",
198
+ "tracingContext",
199
+ ...FUNCTION_KEYS_TO_STRIP
200
+ ]);
195
201
  const DEFAULT_DEEP_CLEAN_OPTIONS = Object.freeze({
196
202
  keysToStrip: [],
197
203
  maxDepth: 8,
@@ -273,6 +279,25 @@ function shouldStripEntry(key, value, stripSet) {
273
279
  if (key === "logger") return typeof value === "function" || isLoggerLike(value);
274
280
  return FUNCTION_KEYS_TO_STRIP.has(key) && typeof value === "function";
275
281
  }
282
+ /**
283
+ * Whether an entry past the object-key limit would have been stripped anyway.
284
+ *
285
+ * The entry is dropped either way, so the value is taken from its property descriptor
286
+ * rather than read: a getter must not run for data that is being discarded. That keeps
287
+ * the truncation count the same wherever a runtime-shaped key sits. An accessor cannot
288
+ * be classified without invoking it, so it is left to the limit and counted.
289
+ */
290
+ function wouldBeStripped(key, val, stripSet) {
291
+ if (!VALUE_STRIP_KEYS.has(key)) return false;
292
+ let descriptor;
293
+ try {
294
+ descriptor = Object.getOwnPropertyDescriptor(val, key);
295
+ } catch {
296
+ return false;
297
+ }
298
+ if (!descriptor || !("value" in descriptor)) return false;
299
+ return shouldStripEntry(key, descriptor.value, stripSet);
300
+ }
276
301
  function restoreSerializedMapKey(keyType, key) {
277
302
  switch (keyType) {
278
303
  case "undefined": return;
@@ -458,25 +483,22 @@ function deepClean(value, options = DEFAULT_DEEP_CLEAN_OPTIONS) {
458
483
  return formatSerializationError(error);
459
484
  }
460
485
  let keyCount = 0;
486
+ let omittedByLimit = 0;
461
487
  for (const key of keys) {
462
488
  if (stripSet.has(key)) continue;
489
+ if (keyCount >= maxObjectKeys) {
490
+ if (!wouldBeStripped(key, val, stripSet)) omittedByLimit++;
491
+ continue;
492
+ }
463
493
  let rawValue;
464
494
  try {
465
495
  rawValue = val[key];
466
496
  } catch (error) {
467
- if (keyCount >= maxObjectKeys) {
468
- cleaned["__truncated"] = `${keys.length - keyCount} more keys omitted`;
469
- break;
470
- }
471
497
  cleaned[key] = formatSerializationError(error);
472
498
  keyCount++;
473
499
  continue;
474
500
  }
475
501
  if (shouldStripEntry(key, rawValue, stripSet)) continue;
476
- if (keyCount >= maxObjectKeys) {
477
- cleaned["__truncated"] = `${keys.length - keyCount} more keys omitted`;
478
- break;
479
- }
480
502
  try {
481
503
  cleaned[key] = helper(rawValue, depth + 1);
482
504
  keyCount++;
@@ -485,6 +507,7 @@ function deepClean(value, options = DEFAULT_DEEP_CLEAN_OPTIONS) {
485
507
  keyCount++;
486
508
  }
487
509
  }
510
+ if (omittedByLimit > 0) cleaned["__truncated"] = `${omittedByLimit} more keys omitted`;
488
511
  return cleaned;
489
512
  } finally {
490
513
  if (typeof val === "object" && val !== null) ancestors.delete(val);
@@ -1077,6 +1100,8 @@ const MINIFIED_METER_TO_CANONICAL = {
1077
1100
  ot: "output_tokens",
1078
1101
  icrt: "input_cache_read_tokens",
1079
1102
  icwt: "input_cache_write_tokens",
1103
+ icwt5m: "input_cache_write_5m_tokens",
1104
+ icwt1h: "input_cache_write_1h_tokens",
1080
1105
  iat: "input_audio_tokens",
1081
1106
  oat: "output_audio_tokens",
1082
1107
  ort: "output_reasoning_tokens"
@@ -1133,6 +1158,15 @@ function parsePricingModelText(content) {
1133
1158
  }
1134
1159
  return pricingModels;
1135
1160
  }
1161
+ function expandRates(row, tier) {
1162
+ const rates = Object.fromEntries(Object.entries(tier.r).map(([meter, value]) => [MINIFIED_METER_TO_CANONICAL[meter], value.c]));
1163
+ const cacheWriteRate = rates.input_cache_write_tokens;
1164
+ if (row.m.includes("claude") && typeof cacheWriteRate === "number") {
1165
+ rates.input_cache_write_5m_tokens ??= cacheWriteRate;
1166
+ rates.input_cache_write_1h_tokens ??= cacheWriteRate * 1.6;
1167
+ }
1168
+ return rates;
1169
+ }
1136
1170
  function expandPricingModelRow(row) {
1137
1171
  return new PricingModel({
1138
1172
  id: row.i,
@@ -1147,7 +1181,7 @@ function expandPricingModelRow(row) {
1147
1181
  op: condition.op,
1148
1182
  value: condition.value
1149
1183
  })),
1150
- rates: Object.fromEntries(Object.entries(tier.r).map(([meter, value]) => [MINIFIED_METER_TO_CANONICAL[meter], value.c]))
1184
+ rates: expandRates(row, tier)
1151
1185
  }))
1152
1186
  });
1153
1187
  }
@@ -1278,6 +1312,8 @@ const PricingMeter = {
1278
1312
  INPUT_AUDIO_TOKENS: "input_audio_tokens",
1279
1313
  INPUT_CACHE_READ_TOKENS: "input_cache_read_tokens",
1280
1314
  INPUT_CACHE_WRITE_TOKENS: "input_cache_write_tokens",
1315
+ INPUT_CACHE_WRITE_5M_TOKENS: "input_cache_write_5m_tokens",
1316
+ INPUT_CACHE_WRITE_1H_TOKENS: "input_cache_write_1h_tokens",
1281
1317
  INPUT_IMAGE_TOKENS: "input_image_tokens",
1282
1318
  OUTPUT_TOKENS: "output_tokens",
1283
1319
  OUTPUT_AUDIO_TOKENS: "output_audio_tokens",
@@ -1290,6 +1326,8 @@ const TokenMetrics = {
1290
1326
  INPUT_TEXT: "mastra_model_input_text_tokens",
1291
1327
  INPUT_CACHE_READ: "mastra_model_input_cache_read_tokens",
1292
1328
  INPUT_CACHE_WRITE: "mastra_model_input_cache_write_tokens",
1329
+ INPUT_CACHE_WRITE_5M: "mastra_model_input_cache_write_5m_tokens",
1330
+ INPUT_CACHE_WRITE_1H: "mastra_model_input_cache_write_1h_tokens",
1293
1331
  INPUT_AUDIO: "mastra_model_input_audio_tokens",
1294
1332
  INPUT_IMAGE: "mastra_model_input_image_tokens",
1295
1333
  OUTPUT_TEXT: "mastra_model_output_text_tokens",
@@ -1319,6 +1357,8 @@ function getTokenMetricSamples(usage) {
1319
1357
  pushIfPositive(TokenMetrics.INPUT_TEXT, usage.inputDetails.text);
1320
1358
  pushIfPositive(TokenMetrics.INPUT_CACHE_READ, usage.inputDetails.cacheRead);
1321
1359
  pushIfPositive(TokenMetrics.INPUT_CACHE_WRITE, usage.inputDetails.cacheWrite);
1360
+ pushIfPositive(TokenMetrics.INPUT_CACHE_WRITE_5M, usage.inputDetails.cacheWrite5m);
1361
+ pushIfPositive(TokenMetrics.INPUT_CACHE_WRITE_1H, usage.inputDetails.cacheWrite1h);
1322
1362
  pushIfPositive(TokenMetrics.INPUT_AUDIO, usage.inputDetails.audio);
1323
1363
  pushIfPositive(TokenMetrics.INPUT_IMAGE, usage.inputDetails.image);
1324
1364
  }
@@ -1385,15 +1425,40 @@ function estimateCosts(args, pricingRegistry = PricingRegistry.getGlobal()) {
1385
1425
  results.set(TokenMetrics.INPUT_CACHE_READ, result.costContext);
1386
1426
  inputDetailResults.push(result);
1387
1427
  }
1388
- if (usage.inputDetails?.cacheWrite) {
1428
+ const cacheWriteDetailResults = [];
1429
+ const cacheWrite5m = usage.inputDetails?.cacheWrite5m ?? 0;
1430
+ const cacheWrite1h = usage.inputDetails?.cacheWrite1h ?? 0;
1431
+ if (cacheWrite5m > 0) {
1432
+ const result = estimateCostForMeter({
1433
+ meter: PricingMeter.INPUT_CACHE_WRITE_5M_TOKENS,
1434
+ tokenCount: cacheWrite5m,
1435
+ ...estimateFields
1436
+ });
1437
+ results.set(TokenMetrics.INPUT_CACHE_WRITE_5M, result.costContext);
1438
+ cacheWriteDetailResults.push(result);
1439
+ inputDetailResults.push(result);
1440
+ }
1441
+ if (cacheWrite1h > 0) {
1442
+ const result = estimateCostForMeter({
1443
+ meter: PricingMeter.INPUT_CACHE_WRITE_1H_TOKENS,
1444
+ tokenCount: cacheWrite1h,
1445
+ ...estimateFields
1446
+ });
1447
+ results.set(TokenMetrics.INPUT_CACHE_WRITE_1H, result.costContext);
1448
+ cacheWriteDetailResults.push(result);
1449
+ inputDetailResults.push(result);
1450
+ }
1451
+ const unclassifiedCacheWrite = Math.max(0, (usage.inputDetails?.cacheWrite ?? 0) - cacheWrite5m - cacheWrite1h);
1452
+ if (unclassifiedCacheWrite > 0) {
1389
1453
  const result = estimateCostForMeter({
1390
1454
  meter: PricingMeter.INPUT_CACHE_WRITE_TOKENS,
1391
- tokenCount: usage.inputDetails.cacheWrite,
1455
+ tokenCount: unclassifiedCacheWrite,
1392
1456
  ...estimateFields
1393
1457
  });
1394
- results.set(TokenMetrics.INPUT_CACHE_WRITE, result.costContext);
1458
+ cacheWriteDetailResults.push(result);
1395
1459
  inputDetailResults.push(result);
1396
1460
  }
1461
+ if (cacheWriteDetailResults.length > 0) setCombinedCostContext(results, TokenMetrics.INPUT_CACHE_WRITE, cacheWriteDetailResults, pricingModel, costMetadata);
1397
1462
  if (usage.inputDetails?.image) {
1398
1463
  const result = estimateCostForMeter({
1399
1464
  meter: PricingMeter.INPUT_IMAGE_TOKENS,
@@ -1470,6 +1535,20 @@ function estimateCosts(args, pricingRegistry = PricingRegistry.getGlobal()) {
1470
1535
  function applyErrorContextForUsage(results, usage, errorContext) {
1471
1536
  for (const sample of getTokenMetricSamples(usage)) results.set(sample.name, errorContext);
1472
1537
  }
1538
+ function setCombinedCostContext(results, metric, detailResults, pricingModel, costMetadata) {
1539
+ const estimatedCosts = detailResults.map((result) => result.costContext.estimatedCost).filter((value) => typeof value === "number");
1540
+ const hasFailedCost = detailResults.some((result) => !result.success);
1541
+ results.set(metric, {
1542
+ provider: pricingModel.provider,
1543
+ model: pricingModel.model,
1544
+ ...estimatedCosts.length > 0 && { estimatedCost: estimatedCosts.reduce((sum, value) => sum + value, 0) },
1545
+ ...estimatedCosts.length > 0 && { costUnit: pricingModel.currency },
1546
+ costMetadata: hasFailedCost ? {
1547
+ ...costMetadata,
1548
+ error: "partial_cost"
1549
+ } : { ...costMetadata }
1550
+ });
1551
+ }
1473
1552
  function setAggregateCostContext(args) {
1474
1553
  const { results, totalMetric, fallbackMeter, totalTokenCount, detailResults, pricingModel, pricingTier, costMetadata } = args;
1475
1554
  if (totalTokenCount == null) return;
@@ -1710,14 +1789,24 @@ function extractUsageMetrics(usage, providerMetadata) {
1710
1789
  if (isDefined(aiSdkDetails?.cacheReadTokens)) inputDetails.cacheRead = aiSdkDetails.cacheReadTokens;
1711
1790
  if (isDefined(aiSdkDetails?.cacheWriteTokens)) inputDetails.cacheWrite = aiSdkDetails.cacheWriteTokens;
1712
1791
  if (!isDefined(inputDetails.cacheRead) && isDefined(usage.cachedInputTokens)) inputDetails.cacheRead = usage.cachedInputTokens;
1792
+ if (isDefined(usage.cacheCreationInputTokens5m)) inputDetails.cacheWrite5m = usage.cacheCreationInputTokens5m;
1793
+ if (isDefined(usage.cacheCreationInputTokens1h)) inputDetails.cacheWrite1h = usage.cacheCreationInputTokens1h;
1713
1794
  if (!isDefined(inputDetails.cacheWrite) && isDefined(usage.cacheCreationInputTokens)) inputDetails.cacheWrite = usage.cacheCreationInputTokens;
1795
+ if (!isDefined(inputDetails.cacheWrite) && (isDefined(inputDetails.cacheWrite5m) || isDefined(inputDetails.cacheWrite1h))) inputDetails.cacheWrite = (inputDetails.cacheWrite5m ?? 0) + (inputDetails.cacheWrite1h ?? 0);
1714
1796
  if (isDefined(usage.reasoningTokens)) outputDetails.reasoning = usage.reasoningTokens;
1715
1797
  const anthropic = providerMetadata?.anthropic;
1716
1798
  if (anthropic) {
1717
1799
  const rawV3InputUsage = isV3RawUsage(usage.raw) ? usage.raw.inputTokens : void 0;
1718
1800
  const hasV3CachedTotals = rawV3InputUsage?.total !== void 0 && (rawV3InputUsage.cacheRead !== void 0 || rawV3InputUsage.cacheWrite !== void 0);
1719
1801
  if (!isDefined(inputDetails.cacheRead) && isDefined(anthropic.cacheReadInputTokens)) inputDetails.cacheRead = anthropic.cacheReadInputTokens;
1720
- if (!isDefined(inputDetails.cacheWrite) && isDefined(anthropic.cacheCreationInputTokens)) inputDetails.cacheWrite = anthropic.cacheCreationInputTokens;
1802
+ const cacheWrite5m = anthropic.cacheCreation?.ephemeral_5m_input_tokens ?? anthropic.cacheCreation?.ephemeral5mInputTokens;
1803
+ const cacheWrite1h = anthropic.cacheCreation?.ephemeral_1h_input_tokens ?? anthropic.cacheCreation?.ephemeral1hInputTokens;
1804
+ if (!isDefined(inputDetails.cacheWrite5m) && isDefined(cacheWrite5m)) inputDetails.cacheWrite5m = cacheWrite5m;
1805
+ if (!isDefined(inputDetails.cacheWrite1h) && isDefined(cacheWrite1h)) inputDetails.cacheWrite1h = cacheWrite1h;
1806
+ if (!isDefined(inputDetails.cacheWrite)) {
1807
+ if (isDefined(anthropic.cacheCreationInputTokens)) inputDetails.cacheWrite = anthropic.cacheCreationInputTokens;
1808
+ else if (isDefined(cacheWrite5m) || isDefined(cacheWrite1h)) inputDetails.cacheWrite = (cacheWrite5m ?? 0) + (cacheWrite1h ?? 0);
1809
+ }
1721
1810
  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);
1722
1811
  }
1723
1812
  const google = providerMetadata?.google;
@@ -1758,6 +1847,8 @@ function mergeInputDetails(a, b) {
1758
1847
  text: addOptional(a.text, b.text),
1759
1848
  cacheRead: addOptional(a.cacheRead, b.cacheRead),
1760
1849
  cacheWrite: addOptional(a.cacheWrite, b.cacheWrite),
1850
+ cacheWrite5m: addOptional(a.cacheWrite5m, b.cacheWrite5m),
1851
+ cacheWrite1h: addOptional(a.cacheWrite1h, b.cacheWrite1h),
1761
1852
  audio: addOptional(a.audio, b.audio),
1762
1853
  image: addOptional(a.image, b.image)
1763
1854
  };