@mastra/observability 1.17.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/dist/index.js CHANGED
@@ -185,21 +185,15 @@ var BaseObservabilityEventBus = class BaseObservabilityEventBus extends MastraBa
185
185
  * }
186
186
  * ```
187
187
  */
188
- /**
189
- * Default keys to strip from objects during deep cleaning.
190
- * These are typically internal/sensitive fields that shouldn't be traced.
191
- */
192
- const DEFAULT_KEYS_TO_STRIP = /* @__PURE__ */ new Set([
193
- "logger",
194
- "experimental_providerMetadata",
195
- "providerMetadata",
196
- "steps",
197
- "tracingContext",
198
- "execute",
199
- "validate"
200
- ]);
188
+ const FUNCTION_KEYS_TO_STRIP = /* @__PURE__ */ new Set(["execute", "validate"]);
189
+ const LOGGER_METHODS = [
190
+ "debug",
191
+ "info",
192
+ "warn",
193
+ "error"
194
+ ];
201
195
  const DEFAULT_DEEP_CLEAN_OPTIONS = Object.freeze({
202
- keysToStrip: DEFAULT_KEYS_TO_STRIP,
196
+ keysToStrip: [],
203
197
  maxDepth: 8,
204
198
  maxStringLength: 128 * 1024,
205
199
  maxArrayLength: 50,
@@ -212,7 +206,7 @@ const DEFAULT_DEEP_CLEAN_OPTIONS = Object.freeze({
212
206
  function mergeSerializationOptions(userOptions) {
213
207
  if (!userOptions) return DEFAULT_DEEP_CLEAN_OPTIONS;
214
208
  return {
215
- keysToStrip: DEFAULT_KEYS_TO_STRIP,
209
+ keysToStrip: DEFAULT_DEEP_CLEAN_OPTIONS.keysToStrip,
216
210
  maxDepth: userOptions.maxDepth ?? DEFAULT_DEEP_CLEAN_OPTIONS.maxDepth,
217
211
  maxStringLength: userOptions.maxStringLength ?? DEFAULT_DEEP_CLEAN_OPTIONS.maxStringLength,
218
212
  maxArrayLength: userOptions.maxArrayLength ?? DEFAULT_DEEP_CLEAN_OPTIONS.maxArrayLength,
@@ -238,6 +232,47 @@ function getMapKeyType(key) {
238
232
  if (key instanceof Error) return "error";
239
233
  return typeof key;
240
234
  }
235
+ function hasOnlyKnownKeys(value, keys) {
236
+ try {
237
+ return Object.keys(value).every((key) => keys.includes(key));
238
+ } catch {
239
+ return false;
240
+ }
241
+ }
242
+ function isSpanLike(value) {
243
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
244
+ const span = value;
245
+ try {
246
+ return typeof span.id === "string" && typeof span.traceId === "string" && typeof span.type === "string" && typeof span.name === "string";
247
+ } catch {
248
+ return false;
249
+ }
250
+ }
251
+ function isTracingContextLike(value) {
252
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
253
+ const context = value;
254
+ if (!hasOnlyKnownKeys(context, ["currentSpan"])) return false;
255
+ try {
256
+ return context.currentSpan === void 0 || isSpanLike(context.currentSpan);
257
+ } catch {
258
+ return false;
259
+ }
260
+ }
261
+ function isLoggerLike(value) {
262
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
263
+ const logger = value;
264
+ try {
265
+ return LOGGER_METHODS.some((method) => typeof logger[method] === "function");
266
+ } catch {
267
+ return false;
268
+ }
269
+ }
270
+ function shouldStripEntry(key, value, stripSet) {
271
+ if (stripSet.has(key)) return true;
272
+ if (key === "tracingContext" && isTracingContextLike(value)) return true;
273
+ if (key === "logger") return typeof value === "function" || isLoggerLike(value);
274
+ return FUNCTION_KEYS_TO_STRIP.has(key) && typeof value === "function";
275
+ }
241
276
  function restoreSerializedMapKey(keyType, key) {
242
277
  switch (keyType) {
243
278
  case "undefined": return;
@@ -340,7 +375,7 @@ function deepClean(value, options = DEFAULT_DEEP_CLEAN_OPTIONS) {
340
375
  let mapKeyCount = 0;
341
376
  let omittedMapEntries = 0;
342
377
  for (const [mapKey, mapVal] of val) {
343
- if (typeof mapKey === "string" && stripSet.has(mapKey)) continue;
378
+ if (typeof mapKey === "string" && shouldStripEntry(mapKey, mapVal, stripSet)) continue;
344
379
  if (mapKeyCount >= maxObjectKeys) {
345
380
  omittedMapEntries++;
346
381
  continue;
@@ -416,15 +451,34 @@ function deepClean(value, options = DEFAULT_DEEP_CLEAN_OPTIONS) {
416
451
  }
417
452
  if (looksLikeJsonSchema) return val;
418
453
  const cleaned = {};
419
- const keys = Object.keys(val).filter((key) => !stripSet.has(key));
454
+ let keys;
455
+ try {
456
+ keys = Object.keys(val);
457
+ } catch (error) {
458
+ return formatSerializationError(error);
459
+ }
420
460
  let keyCount = 0;
421
461
  for (const key of keys) {
462
+ if (stripSet.has(key)) continue;
463
+ let rawValue;
464
+ try {
465
+ rawValue = val[key];
466
+ } catch (error) {
467
+ if (keyCount >= maxObjectKeys) {
468
+ cleaned["__truncated"] = `${keys.length - keyCount} more keys omitted`;
469
+ break;
470
+ }
471
+ cleaned[key] = formatSerializationError(error);
472
+ keyCount++;
473
+ continue;
474
+ }
475
+ if (shouldStripEntry(key, rawValue, stripSet)) continue;
422
476
  if (keyCount >= maxObjectKeys) {
423
477
  cleaned["__truncated"] = `${keys.length - keyCount} more keys omitted`;
424
478
  break;
425
479
  }
426
480
  try {
427
- cleaned[key] = helper(val[key], depth + 1);
481
+ cleaned[key] = helper(rawValue, depth + 1);
428
482
  keyCount++;
429
483
  } catch (error) {
430
484
  cleaned[key] = formatSerializationError(error);
@@ -1023,6 +1077,8 @@ const MINIFIED_METER_TO_CANONICAL = {
1023
1077
  ot: "output_tokens",
1024
1078
  icrt: "input_cache_read_tokens",
1025
1079
  icwt: "input_cache_write_tokens",
1080
+ icwt5m: "input_cache_write_5m_tokens",
1081
+ icwt1h: "input_cache_write_1h_tokens",
1026
1082
  iat: "input_audio_tokens",
1027
1083
  oat: "output_audio_tokens",
1028
1084
  ort: "output_reasoning_tokens"
@@ -1079,6 +1135,15 @@ function parsePricingModelText(content) {
1079
1135
  }
1080
1136
  return pricingModels;
1081
1137
  }
1138
+ function expandRates(row, tier) {
1139
+ const rates = Object.fromEntries(Object.entries(tier.r).map(([meter, value]) => [MINIFIED_METER_TO_CANONICAL[meter], value.c]));
1140
+ const cacheWriteRate = rates.input_cache_write_tokens;
1141
+ if (row.m.includes("claude") && typeof cacheWriteRate === "number") {
1142
+ rates.input_cache_write_5m_tokens ??= cacheWriteRate;
1143
+ rates.input_cache_write_1h_tokens ??= cacheWriteRate * 1.6;
1144
+ }
1145
+ return rates;
1146
+ }
1082
1147
  function expandPricingModelRow(row) {
1083
1148
  return new PricingModel({
1084
1149
  id: row.i,
@@ -1093,7 +1158,7 @@ function expandPricingModelRow(row) {
1093
1158
  op: condition.op,
1094
1159
  value: condition.value
1095
1160
  })),
1096
- rates: Object.fromEntries(Object.entries(tier.r).map(([meter, value]) => [MINIFIED_METER_TO_CANONICAL[meter], value.c]))
1161
+ rates: expandRates(row, tier)
1097
1162
  }))
1098
1163
  });
1099
1164
  }
@@ -1224,6 +1289,8 @@ const PricingMeter = {
1224
1289
  INPUT_AUDIO_TOKENS: "input_audio_tokens",
1225
1290
  INPUT_CACHE_READ_TOKENS: "input_cache_read_tokens",
1226
1291
  INPUT_CACHE_WRITE_TOKENS: "input_cache_write_tokens",
1292
+ INPUT_CACHE_WRITE_5M_TOKENS: "input_cache_write_5m_tokens",
1293
+ INPUT_CACHE_WRITE_1H_TOKENS: "input_cache_write_1h_tokens",
1227
1294
  INPUT_IMAGE_TOKENS: "input_image_tokens",
1228
1295
  OUTPUT_TOKENS: "output_tokens",
1229
1296
  OUTPUT_AUDIO_TOKENS: "output_audio_tokens",
@@ -1236,6 +1303,8 @@ const TokenMetrics = {
1236
1303
  INPUT_TEXT: "mastra_model_input_text_tokens",
1237
1304
  INPUT_CACHE_READ: "mastra_model_input_cache_read_tokens",
1238
1305
  INPUT_CACHE_WRITE: "mastra_model_input_cache_write_tokens",
1306
+ INPUT_CACHE_WRITE_5M: "mastra_model_input_cache_write_5m_tokens",
1307
+ INPUT_CACHE_WRITE_1H: "mastra_model_input_cache_write_1h_tokens",
1239
1308
  INPUT_AUDIO: "mastra_model_input_audio_tokens",
1240
1309
  INPUT_IMAGE: "mastra_model_input_image_tokens",
1241
1310
  OUTPUT_TEXT: "mastra_model_output_text_tokens",
@@ -1265,6 +1334,8 @@ function getTokenMetricSamples(usage) {
1265
1334
  pushIfPositive(TokenMetrics.INPUT_TEXT, usage.inputDetails.text);
1266
1335
  pushIfPositive(TokenMetrics.INPUT_CACHE_READ, usage.inputDetails.cacheRead);
1267
1336
  pushIfPositive(TokenMetrics.INPUT_CACHE_WRITE, usage.inputDetails.cacheWrite);
1337
+ pushIfPositive(TokenMetrics.INPUT_CACHE_WRITE_5M, usage.inputDetails.cacheWrite5m);
1338
+ pushIfPositive(TokenMetrics.INPUT_CACHE_WRITE_1H, usage.inputDetails.cacheWrite1h);
1268
1339
  pushIfPositive(TokenMetrics.INPUT_AUDIO, usage.inputDetails.audio);
1269
1340
  pushIfPositive(TokenMetrics.INPUT_IMAGE, usage.inputDetails.image);
1270
1341
  }
@@ -1331,15 +1402,40 @@ function estimateCosts(args, pricingRegistry = PricingRegistry.getGlobal()) {
1331
1402
  results.set(TokenMetrics.INPUT_CACHE_READ, result.costContext);
1332
1403
  inputDetailResults.push(result);
1333
1404
  }
1334
- if (usage.inputDetails?.cacheWrite) {
1405
+ const cacheWriteDetailResults = [];
1406
+ const cacheWrite5m = usage.inputDetails?.cacheWrite5m ?? 0;
1407
+ const cacheWrite1h = usage.inputDetails?.cacheWrite1h ?? 0;
1408
+ if (cacheWrite5m > 0) {
1409
+ const result = estimateCostForMeter({
1410
+ meter: PricingMeter.INPUT_CACHE_WRITE_5M_TOKENS,
1411
+ tokenCount: cacheWrite5m,
1412
+ ...estimateFields
1413
+ });
1414
+ results.set(TokenMetrics.INPUT_CACHE_WRITE_5M, result.costContext);
1415
+ cacheWriteDetailResults.push(result);
1416
+ inputDetailResults.push(result);
1417
+ }
1418
+ if (cacheWrite1h > 0) {
1419
+ const result = estimateCostForMeter({
1420
+ meter: PricingMeter.INPUT_CACHE_WRITE_1H_TOKENS,
1421
+ tokenCount: cacheWrite1h,
1422
+ ...estimateFields
1423
+ });
1424
+ results.set(TokenMetrics.INPUT_CACHE_WRITE_1H, result.costContext);
1425
+ cacheWriteDetailResults.push(result);
1426
+ inputDetailResults.push(result);
1427
+ }
1428
+ const unclassifiedCacheWrite = Math.max(0, (usage.inputDetails?.cacheWrite ?? 0) - cacheWrite5m - cacheWrite1h);
1429
+ if (unclassifiedCacheWrite > 0) {
1335
1430
  const result = estimateCostForMeter({
1336
1431
  meter: PricingMeter.INPUT_CACHE_WRITE_TOKENS,
1337
- tokenCount: usage.inputDetails.cacheWrite,
1432
+ tokenCount: unclassifiedCacheWrite,
1338
1433
  ...estimateFields
1339
1434
  });
1340
- results.set(TokenMetrics.INPUT_CACHE_WRITE, result.costContext);
1435
+ cacheWriteDetailResults.push(result);
1341
1436
  inputDetailResults.push(result);
1342
1437
  }
1438
+ if (cacheWriteDetailResults.length > 0) setCombinedCostContext(results, TokenMetrics.INPUT_CACHE_WRITE, cacheWriteDetailResults, pricingModel, costMetadata);
1343
1439
  if (usage.inputDetails?.image) {
1344
1440
  const result = estimateCostForMeter({
1345
1441
  meter: PricingMeter.INPUT_IMAGE_TOKENS,
@@ -1416,6 +1512,20 @@ function estimateCosts(args, pricingRegistry = PricingRegistry.getGlobal()) {
1416
1512
  function applyErrorContextForUsage(results, usage, errorContext) {
1417
1513
  for (const sample of getTokenMetricSamples(usage)) results.set(sample.name, errorContext);
1418
1514
  }
1515
+ function setCombinedCostContext(results, metric, detailResults, pricingModel, costMetadata) {
1516
+ const estimatedCosts = detailResults.map((result) => result.costContext.estimatedCost).filter((value) => typeof value === "number");
1517
+ const hasFailedCost = detailResults.some((result) => !result.success);
1518
+ results.set(metric, {
1519
+ provider: pricingModel.provider,
1520
+ model: pricingModel.model,
1521
+ ...estimatedCosts.length > 0 && { estimatedCost: estimatedCosts.reduce((sum, value) => sum + value, 0) },
1522
+ ...estimatedCosts.length > 0 && { costUnit: pricingModel.currency },
1523
+ costMetadata: hasFailedCost ? {
1524
+ ...costMetadata,
1525
+ error: "partial_cost"
1526
+ } : { ...costMetadata }
1527
+ });
1528
+ }
1419
1529
  function setAggregateCostContext(args) {
1420
1530
  const { results, totalMetric, fallbackMeter, totalTokenCount, detailResults, pricingModel, pricingTier, costMetadata } = args;
1421
1531
  if (totalTokenCount == null) return;
@@ -1656,14 +1766,24 @@ function extractUsageMetrics(usage, providerMetadata) {
1656
1766
  if (isDefined(aiSdkDetails?.cacheReadTokens)) inputDetails.cacheRead = aiSdkDetails.cacheReadTokens;
1657
1767
  if (isDefined(aiSdkDetails?.cacheWriteTokens)) inputDetails.cacheWrite = aiSdkDetails.cacheWriteTokens;
1658
1768
  if (!isDefined(inputDetails.cacheRead) && isDefined(usage.cachedInputTokens)) inputDetails.cacheRead = usage.cachedInputTokens;
1769
+ if (isDefined(usage.cacheCreationInputTokens5m)) inputDetails.cacheWrite5m = usage.cacheCreationInputTokens5m;
1770
+ if (isDefined(usage.cacheCreationInputTokens1h)) inputDetails.cacheWrite1h = usage.cacheCreationInputTokens1h;
1659
1771
  if (!isDefined(inputDetails.cacheWrite) && isDefined(usage.cacheCreationInputTokens)) inputDetails.cacheWrite = usage.cacheCreationInputTokens;
1772
+ if (!isDefined(inputDetails.cacheWrite) && (isDefined(inputDetails.cacheWrite5m) || isDefined(inputDetails.cacheWrite1h))) inputDetails.cacheWrite = (inputDetails.cacheWrite5m ?? 0) + (inputDetails.cacheWrite1h ?? 0);
1660
1773
  if (isDefined(usage.reasoningTokens)) outputDetails.reasoning = usage.reasoningTokens;
1661
1774
  const anthropic = providerMetadata?.anthropic;
1662
1775
  if (anthropic) {
1663
1776
  const rawV3InputUsage = isV3RawUsage(usage.raw) ? usage.raw.inputTokens : void 0;
1664
1777
  const hasV3CachedTotals = rawV3InputUsage?.total !== void 0 && (rawV3InputUsage.cacheRead !== void 0 || rawV3InputUsage.cacheWrite !== void 0);
1665
1778
  if (!isDefined(inputDetails.cacheRead) && isDefined(anthropic.cacheReadInputTokens)) inputDetails.cacheRead = anthropic.cacheReadInputTokens;
1666
- if (!isDefined(inputDetails.cacheWrite) && isDefined(anthropic.cacheCreationInputTokens)) inputDetails.cacheWrite = anthropic.cacheCreationInputTokens;
1779
+ const cacheWrite5m = anthropic.cacheCreation?.ephemeral_5m_input_tokens ?? anthropic.cacheCreation?.ephemeral5mInputTokens;
1780
+ const cacheWrite1h = anthropic.cacheCreation?.ephemeral_1h_input_tokens ?? anthropic.cacheCreation?.ephemeral1hInputTokens;
1781
+ if (!isDefined(inputDetails.cacheWrite5m) && isDefined(cacheWrite5m)) inputDetails.cacheWrite5m = cacheWrite5m;
1782
+ if (!isDefined(inputDetails.cacheWrite1h) && isDefined(cacheWrite1h)) inputDetails.cacheWrite1h = cacheWrite1h;
1783
+ if (!isDefined(inputDetails.cacheWrite)) {
1784
+ if (isDefined(anthropic.cacheCreationInputTokens)) inputDetails.cacheWrite = anthropic.cacheCreationInputTokens;
1785
+ else if (isDefined(cacheWrite5m) || isDefined(cacheWrite1h)) inputDetails.cacheWrite = (cacheWrite5m ?? 0) + (cacheWrite1h ?? 0);
1786
+ }
1667
1787
  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);
1668
1788
  }
1669
1789
  const google = providerMetadata?.google;
@@ -1704,6 +1824,8 @@ function mergeInputDetails(a, b) {
1704
1824
  text: addOptional(a.text, b.text),
1705
1825
  cacheRead: addOptional(a.cacheRead, b.cacheRead),
1706
1826
  cacheWrite: addOptional(a.cacheWrite, b.cacheWrite),
1827
+ cacheWrite5m: addOptional(a.cacheWrite5m, b.cacheWrite5m),
1828
+ cacheWrite1h: addOptional(a.cacheWrite1h, b.cacheWrite1h),
1707
1829
  audio: addOptional(a.audio, b.audio),
1708
1830
  image: addOptional(a.image, b.image)
1709
1831
  };
@@ -2437,6 +2559,49 @@ var ModelSpanTracker = class {
2437
2559
  }
2438
2560
  };
2439
2561
  //#endregion
2562
+ //#region src/spans/metadata.ts
2563
+ /**
2564
+ * Shared span-metadata helpers.
2565
+ *
2566
+ * Used by both the observability instance (`instances/base.ts`) and the span
2567
+ * base class (`spans/base.ts`) so the plain-record check and the
2568
+ * descriptor-preserving merge stay in a single place. Both feed the same span
2569
+ * metadata pipeline, so keeping one implementation avoids divergence.
2570
+ */
2571
+ /**
2572
+ * Returns true only for plain object records (prototype is `Object.prototype`
2573
+ * or `null`). Maps, Dates, class instances, and arrays return false so callers
2574
+ * can preserve their original shape instead of shallow-copying them into `{}`.
2575
+ */
2576
+ function isPlainRecord(value) {
2577
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
2578
+ try {
2579
+ const prototype = Object.getPrototypeOf(value);
2580
+ return prototype === Object.prototype || prototype === null;
2581
+ } catch {
2582
+ return false;
2583
+ }
2584
+ }
2585
+ /**
2586
+ * Merges two metadata values while preserving property descriptors (so getters
2587
+ * are copied as accessors rather than eagerly invoked). Only plain records are
2588
+ * merged; if either side is non-plain the second argument is returned as-is,
2589
+ * matching the previous per-module behavior.
2590
+ */
2591
+ function mergeMetadata$1(parentMetadata, metadata) {
2592
+ if (!parentMetadata) return metadata;
2593
+ if (!metadata) return parentMetadata;
2594
+ if (!isPlainRecord(parentMetadata) || !isPlainRecord(metadata)) return metadata;
2595
+ try {
2596
+ const merged = {};
2597
+ Object.defineProperties(merged, Object.getOwnPropertyDescriptors(parentMetadata));
2598
+ Object.defineProperties(merged, Object.getOwnPropertyDescriptors(metadata));
2599
+ return merged;
2600
+ } catch {
2601
+ return metadata;
2602
+ }
2603
+ }
2604
+ //#endregion
2440
2605
  //#region src/spans/base.ts
2441
2606
  /**
2442
2607
  * Determines if a span type should be considered internal based on flags.
@@ -2561,10 +2726,7 @@ var BaseSpan = class {
2561
2726
  this.type = options.type;
2562
2727
  this.isInternal = isSpanInternal(this.type, options.tracingPolicy?.internal);
2563
2728
  this.isExcluded = this.alwaysExcluded || observabilityConfig.excludeSpanTypes?.includes(this.type) === true || this.isInternal && !observabilityConfig.includeInternalSpans;
2564
- this.metadata = deepClean(options.parent?.metadata || options.metadata ? {
2565
- ...options.parent?.metadata,
2566
- ...options.metadata
2567
- } : void 0, this.deepCleanOptions);
2729
+ this.metadata = deepClean(this.prepareSpanMetadata(mergeMetadata$1(options.parent?.metadata, options.metadata)), this.deepCleanOptions);
2568
2730
  this.parent = options.parent;
2569
2731
  this.startTime = options.startTime ?? /* @__PURE__ */ new Date();
2570
2732
  this.observabilityInstance = observabilityInstance;
@@ -2582,9 +2744,32 @@ var BaseSpan = class {
2582
2744
  }
2583
2745
  this.attributes = deepClean(options.attributes, this.deepCleanOptions) || {};
2584
2746
  if (options.requestContext && options.requestContext.size() > 0) this.requestContext = deepClean(options.requestContext, this.deepCleanOptions);
2585
- if (this.isEvent) this.output = deepClean(options.output, this.deepCleanOptions);
2747
+ if (this.isEvent) this.output = deepClean(this.prepareSpanOutput(options.output), this.deepCleanOptions);
2586
2748
  else this.input = deepClean(options.input, this.deepCleanOptions);
2587
2749
  }
2750
+ prepareSpanOutput(value) {
2751
+ if (!isPlainRecord(value)) return value;
2752
+ if (this.type !== SpanType.MODEL_STEP && this.type !== SpanType.MODEL_INFERENCE) return value;
2753
+ try {
2754
+ const prepared = { ...value };
2755
+ delete prepared.steps;
2756
+ return prepared;
2757
+ } catch {
2758
+ return value;
2759
+ }
2760
+ }
2761
+ prepareSpanMetadata(value) {
2762
+ if (!isPlainRecord(value)) return value;
2763
+ if (this.type !== SpanType.MODEL_STEP) return value;
2764
+ try {
2765
+ const prepared = { ...value };
2766
+ delete prepared.providerMetadata;
2767
+ delete prepared.experimental_providerMetadata;
2768
+ return prepared;
2769
+ } catch {
2770
+ return value;
2771
+ }
2772
+ }
2588
2773
  createChildSpan(options) {
2589
2774
  return this.observabilityInstance.startSpan({
2590
2775
  ...options,
@@ -2791,10 +2976,10 @@ var DefaultSpan = class extends BaseSpan {
2791
2976
  this.endTime = /* @__PURE__ */ new Date();
2792
2977
  if (options?.metadata) this.metadata = {
2793
2978
  ...this.metadata,
2794
- ...deepClean(options.metadata, this.deepCleanOptions)
2979
+ ...deepClean(this.prepareSpanMetadata(options.metadata), this.deepCleanOptions)
2795
2980
  };
2796
2981
  if (this.isExcluded) return;
2797
- if (options?.output !== void 0) this.output = deepClean(options.output, this.deepCleanOptions);
2982
+ if (options?.output !== void 0) this.output = deepClean(this.prepareSpanOutput(options.output), this.deepCleanOptions);
2798
2983
  if (options?.attributes) this.attributes = {
2799
2984
  ...this.attributes,
2800
2985
  ...deepClean(options.attributes, this.deepCleanOptions)
@@ -2805,7 +2990,7 @@ var DefaultSpan = class extends BaseSpan {
2805
2990
  const { error, endSpan = true, attributes, metadata } = options;
2806
2991
  if (metadata) this.metadata = {
2807
2992
  ...this.metadata,
2808
- ...deepClean(metadata, this.deepCleanOptions)
2993
+ ...deepClean(this.prepareSpanMetadata(metadata), this.deepCleanOptions)
2809
2994
  };
2810
2995
  if (!this.isExcluded) {
2811
2996
  this.errorInfo = deepClean(error instanceof MastraError ? {
@@ -2834,11 +3019,11 @@ var DefaultSpan = class extends BaseSpan {
2834
3019
  if (options.name !== void 0) this.name = options.name;
2835
3020
  if (options.metadata) this.metadata = {
2836
3021
  ...this.metadata,
2837
- ...deepClean(options.metadata, this.deepCleanOptions)
3022
+ ...deepClean(this.prepareSpanMetadata(options.metadata), this.deepCleanOptions)
2838
3023
  };
2839
3024
  if (this.isExcluded) return;
2840
3025
  if (options.input !== void 0) this.input = deepClean(options.input, this.deepCleanOptions);
2841
- if (options.output !== void 0) this.output = deepClean(options.output, this.deepCleanOptions);
3026
+ if (options.output !== void 0) this.output = deepClean(this.prepareSpanOutput(options.output), this.deepCleanOptions);
2842
3027
  if (options.attributes) this.attributes = {
2843
3028
  ...this.attributes,
2844
3029
  ...deepClean(options.attributes, this.deepCleanOptions)
@@ -2926,6 +3111,19 @@ var NoOpSpan = class extends BaseSpan {
2926
3111
  /**
2927
3112
  * BaseObservability - Abstract base class for Observability implementations
2928
3113
  */
3114
+ function hasMetadataKey(metadata, key) {
3115
+ if (!metadata || typeof metadata !== "object") return false;
3116
+ try {
3117
+ return Object.prototype.hasOwnProperty.call(Object.getOwnPropertyDescriptors(metadata), key);
3118
+ } catch {
3119
+ return true;
3120
+ }
3121
+ }
3122
+ function injectEnvironmentMetadata(metadata, environment) {
3123
+ if (environment === void 0 || hasMetadataKey(metadata, "environment")) return metadata;
3124
+ if (metadata && !isPlainRecord(metadata)) return metadata;
3125
+ return mergeMetadata$1(metadata, { environment });
3126
+ }
2929
3127
  /**
2930
3128
  * Abstract base class for all Observability implementations in Mastra.
2931
3129
  */
@@ -3021,16 +3219,9 @@ var BaseObservabilityInstance = class extends MastraBase {
3021
3219
  let traceState;
3022
3220
  if (options.parent) traceState = options.parent.traceState;
3023
3221
  else traceState = this.computeTraceState(tracingOptions);
3024
- const tracingMetadata = !options.parent ? tracingOptions?.metadata : void 0;
3025
- const mergedMetadata = metadata || tracingMetadata ? {
3026
- ...metadata,
3027
- ...tracingMetadata
3028
- } : void 0;
3222
+ const mergedMetadata = mergeMetadata$1(metadata, !options.parent ? tracingOptions?.metadata : void 0);
3029
3223
  const enrichedMetadata = this.extractMetadataFromRequestContext(requestContext, mergedMetadata, traceState);
3030
- const finalMetadata = !options.parent && this.#mastraEnvironment !== void 0 && (enrichedMetadata === void 0 || enrichedMetadata.environment === void 0) ? {
3031
- ...enrichedMetadata ?? {},
3032
- environment: this.#mastraEnvironment
3033
- } : enrichedMetadata;
3224
+ const finalMetadata = !options.parent ? injectEnvironmentMetadata(enrichedMetadata, this.#mastraEnvironment) : enrichedMetadata;
3034
3225
  const tags = !options.parent ? tracingOptions?.tags : void 0;
3035
3226
  const traceId = !options.parent ? options.traceId ?? tracingOptions?.traceId : options.traceId;
3036
3227
  const parentSpanId = options.parentSpanId;
@@ -3295,10 +3486,7 @@ var BaseObservabilityInstance = class extends MastraBase {
3295
3486
  if (!requestContext || !traceState || traceState.requestContextKeys.length === 0) return explicitMetadata;
3296
3487
  const extracted = this.extractKeys(requestContext, traceState.requestContextKeys);
3297
3488
  if (Object.keys(extracted).length === 0 && !explicitMetadata) return;
3298
- return {
3299
- ...extracted,
3300
- ...explicitMetadata
3301
- };
3489
+ return mergeMetadata$1(extracted, explicitMetadata);
3302
3490
  }
3303
3491
  /**
3304
3492
  * Extract specific keys from RequestContext
@@ -6345,6 +6533,8 @@ const QUOTA_EXCEEDED_STATUS = 402;
6345
6533
  const OBSERVABILITY_STATUS_HEADER = "x-mastra-observability";
6346
6534
  const OBSERVABILITY_DISABLED_VALUE = "disabled";
6347
6535
  const OBSERVABILITY_RETRY_AFTER_HEADER = "x-mastra-observability-retry-after";
6536
+ const OBSERVABILITY_CAPABILITIES_HEADER = "x-mastra-observability-capabilities";
6537
+ const QUOTA_PAUSE_CAPABILITY = "quota-pause-v1";
6348
6538
  const DEFAULT_QUOTA_PROBE_INTERVAL_SECONDS = 300;
6349
6539
  const MAX_QUOTA_PROBE_INTERVAL_SECONDS = Math.floor(2147483647 / 1e3);
6350
6540
  function isObservabilityDisabled(response) {
@@ -6671,7 +6861,8 @@ var MastraPlatformExporter = class extends BaseExporter {
6671
6861
  buildPublishHeaders() {
6672
6862
  return {
6673
6863
  Authorization: `Bearer ${this.platformConfig.accessToken}`,
6674
- "Content-Type": "application/json"
6864
+ "Content-Type": "application/json",
6865
+ [OBSERVABILITY_CAPABILITIES_HEADER]: QUOTA_PAUSE_CAPABILITY
6675
6866
  };
6676
6867
  }
6677
6868
  buildPublishBody(signal, records) {
@@ -6724,7 +6915,7 @@ var MastraPlatformExporter = class extends BaseExporter {
6724
6915
  this.flushTimer = null;
6725
6916
  }
6726
6917
  this.resetBuffer();
6727
- this.logger.warn(`Mastra observability paused: quota exhausted, dropping telemetry and probing every ${retryAfterSeconds}s`);
6918
+ this.logger.warn(`Mastra observability export paused: platform quota exhausted (OBSERVABILITY_QUOTA_EXCEEDED). Dropping telemetry and retrying in ${retryAfterSeconds} seconds. Check Platform billing/usage to restore telemetry.`);
6728
6919
  this.scheduleQuotaProbe();
6729
6920
  }
6730
6921
  scheduleQuotaProbe() {
@@ -9299,6 +9490,6 @@ function buildTracingOptions(...updaters) {
9299
9490
  return updaters.reduce((opts, updater) => updater(opts), {});
9300
9491
  }
9301
9492
  //#endregion
9302
- export { BaseExporter, BaseObservabilityEventBus, BaseObservabilityInstance, BaseSpan, CardinalityFilter, CloudExporter, ConsoleExporter, DEFAULT_DEEP_CLEAN_OPTIONS, DEFAULT_KEYS_TO_STRIP, DEFAULT_LIMITS, DefaultExporter, DefaultObservabilityInstance, DefaultSpan, JsonExporter, LoggerContextImpl, MastraPlatformExporter, MastraStorageExporter, MetricsContextImpl, ModelSpanTracker, NoOpSpan, Observability, ObservabilityBus, SamplingStrategyType, SensitiveDataFilter, TestExporter, TraceData, TrackingExporter, buildExportedLog, buildExportedSpan, buildTracingOptions, chainFormatters, createClientObservabilityProxy, decodeResourceLogs, decodeResourceSpans, deepClean, formatBaggage, formatTraceparent, getExternalParentId, isSerializedMap, mergeSerializationOptions, observabilityConfigValueSchema, observabilityFeatures, observabilityInstanceConfigSchema, observabilityRegistryConfigSchema, otlpSeverityToLogLevel, parseBaggage, parseTraceparent, reconstructSerializedMap, routeToHandler, samplingStrategySchema, serializationOptionsSchema, truncateString };
9493
+ export { BaseExporter, BaseObservabilityEventBus, BaseObservabilityInstance, BaseSpan, CardinalityFilter, CloudExporter, ConsoleExporter, DEFAULT_DEEP_CLEAN_OPTIONS, DEFAULT_LIMITS, DefaultExporter, DefaultObservabilityInstance, DefaultSpan, JsonExporter, LoggerContextImpl, MastraPlatformExporter, MastraStorageExporter, MetricsContextImpl, ModelSpanTracker, NoOpSpan, OBSERVABILITY_CAPABILITIES_HEADER, Observability, ObservabilityBus, QUOTA_PAUSE_CAPABILITY, SamplingStrategyType, SensitiveDataFilter, TestExporter, TraceData, TrackingExporter, buildExportedLog, buildExportedSpan, buildTracingOptions, chainFormatters, createClientObservabilityProxy, decodeResourceLogs, decodeResourceSpans, deepClean, formatBaggage, formatTraceparent, getExternalParentId, isSerializedMap, mergeSerializationOptions, observabilityConfigValueSchema, observabilityFeatures, observabilityInstanceConfigSchema, observabilityRegistryConfigSchema, otlpSeverityToLogLevel, parseBaggage, parseTraceparent, reconstructSerializedMap, routeToHandler, samplingStrategySchema, serializationOptionsSchema, truncateString };
9303
9494
 
9304
9495
  //# sourceMappingURL=index.js.map