@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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
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
+
12
+ ## 1.17.1-alpha.0
13
+
14
+ ### Patch Changes
15
+
16
+ - Fixed span serialization so internal tracing fields are removed only from framework-owned payloads while preserving user data with the same key names. ([#21332](https://github.com/mastra-ai/mastra/pull/21332))
17
+
18
+ - Advertise quota-pause support to the Mastra platform. `MastraPlatformExporter` now sends `x-mastra-observability-capabilities: quota-pause-v1` on every request (batch uploads for all five signal types, plus the traces recovery probe), letting the platform respond with `402 Payment Required` to clients that understand the quota-pause contract while shielding legacy clients from retry loops. No configuration change is required: ([#21447](https://github.com/mastra-ai/mastra/pull/21447))
19
+
20
+ ```ts
21
+ import { MastraPlatformExporter } from '@mastra/observability';
22
+
23
+ const exporter = new MastraPlatformExporter({
24
+ accessToken: process.env.MASTRA_PLATFORM_ACCESS_TOKEN,
25
+ });
26
+ ```
27
+
28
+ - Updated dependencies [[`15101bb`](https://github.com/mastra-ai/mastra/commit/15101bb53c0d934f31af6b8813b88191e382a5e5), [`c2c3deb`](https://github.com/mastra-ai/mastra/commit/c2c3debcf670c7082d0a5e553aa99818a864698c), [`33374ba`](https://github.com/mastra-ai/mastra/commit/33374ba359e4fb13eaa918ae925fe167a3c55414), [`c5f964d`](https://github.com/mastra-ai/mastra/commit/c5f964d3f77064e978f8066ec506eed77ba5c63c), [`f8f653f`](https://github.com/mastra-ai/mastra/commit/f8f653f10980d01a73706cc3c8689ca5e40ce808)]:
29
+ - @mastra/core@1.60.0-alpha.1
30
+
3
31
  ## 1.17.0
4
32
 
5
33
  ### Minor 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
@@ -14,6 +14,8 @@ export interface MastraPlatformExporterConfig extends BaseExporterConfig {
14
14
  scoresEndpoint?: string;
15
15
  feedbackEndpoint?: string;
16
16
  }
17
+ export declare const OBSERVABILITY_CAPABILITIES_HEADER = "x-mastra-observability-capabilities";
18
+ export declare const QUOTA_PAUSE_CAPABILITY = "quota-pause-v1";
17
19
  export declare class MastraPlatformExporter extends BaseExporter {
18
20
  name: string;
19
21
  private readonly platformConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"mastra-platform.d.ts","sourceRoot":"","sources":["../../src/exporters/mastra-platform.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,YAAY,EAEZ,QAAQ,EACR,WAAW,EACX,UAAU,EACV,aAAa,EACd,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAEjD,MAAM,WAAW,4BAA6B,SAAQ,kBAAkB;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAkOD,qBAAa,sBAAuB,SAAQ,YAAY;IACtD,IAAI,SAA8B;IAElC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAmC;IAClE,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAsB;IAC1D,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,yBAAyB,CAAwC;IACzE,OAAO,CAAC,YAAY,CAAS;gBAEjB,MAAM,GAAE,4BAAiC;cAyErC,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBjE,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAa1C,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAahD,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAa9C,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAa1D,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,cAAc;YAMR,mBAAmB;IAYjC,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,aAAa;YAoBP,WAAW;IA4EzB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,gBAAgB;YAIV,WAAW;IAoDzB;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAsBvB,OAAO,CAAC,kBAAkB;IAkB1B;;;;;OAKG;YACW,gBAAgB;YAoDhB,gBAAgB;IAmC9B,OAAO,CAAC,WAAW;IAUnB;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA2BtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAsChC"}
1
+ {"version":3,"file":"mastra-platform.d.ts","sourceRoot":"","sources":["../../src/exporters/mastra-platform.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,YAAY,EAEZ,QAAQ,EACR,WAAW,EACX,UAAU,EACV,aAAa,EACd,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAEjD,MAAM,WAAW,4BAA6B,SAAQ,kBAAkB;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAyBD,eAAO,MAAM,iCAAiC,wCAAwC,CAAC;AACvF,eAAO,MAAM,sBAAsB,mBAAmB,CAAC;AA6MvD,qBAAa,sBAAuB,SAAQ,YAAY;IACtD,IAAI,SAA8B;IAElC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAmC;IAClE,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAsB;IAC1D,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,yBAAyB,CAAwC;IACzE,OAAO,CAAC,YAAY,CAAS;gBAEjB,MAAM,GAAE,4BAAiC;cAyErC,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBjE,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAa1C,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAahD,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAa9C,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAa1D,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,cAAc;YAMR,mBAAmB;IAYjC,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,aAAa;YAoBP,WAAW;IA4EzB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,gBAAgB;YAIV,WAAW;IAoDzB;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAsBvB,OAAO,CAAC,kBAAkB;IAkB1B;;;;;OAKG;YACW,gBAAgB;YAoDhB,gBAAgB;IAmC9B,OAAO,CAAC,WAAW;IAUnB;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA2BtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAsChC"}
package/dist/index.cjs CHANGED
@@ -210,21 +210,15 @@ var BaseObservabilityEventBus = class BaseObservabilityEventBus extends _mastra_
210
210
  * }
211
211
  * ```
212
212
  */
213
- /**
214
- * Default keys to strip from objects during deep cleaning.
215
- * These are typically internal/sensitive fields that shouldn't be traced.
216
- */
217
- const DEFAULT_KEYS_TO_STRIP = /* @__PURE__ */ new Set([
218
- "logger",
219
- "experimental_providerMetadata",
220
- "providerMetadata",
221
- "steps",
222
- "tracingContext",
223
- "execute",
224
- "validate"
225
- ]);
213
+ const FUNCTION_KEYS_TO_STRIP = /* @__PURE__ */ new Set(["execute", "validate"]);
214
+ const LOGGER_METHODS = [
215
+ "debug",
216
+ "info",
217
+ "warn",
218
+ "error"
219
+ ];
226
220
  const DEFAULT_DEEP_CLEAN_OPTIONS = Object.freeze({
227
- keysToStrip: DEFAULT_KEYS_TO_STRIP,
221
+ keysToStrip: [],
228
222
  maxDepth: 8,
229
223
  maxStringLength: 128 * 1024,
230
224
  maxArrayLength: 50,
@@ -237,7 +231,7 @@ const DEFAULT_DEEP_CLEAN_OPTIONS = Object.freeze({
237
231
  function mergeSerializationOptions(userOptions) {
238
232
  if (!userOptions) return DEFAULT_DEEP_CLEAN_OPTIONS;
239
233
  return {
240
- keysToStrip: DEFAULT_KEYS_TO_STRIP,
234
+ keysToStrip: DEFAULT_DEEP_CLEAN_OPTIONS.keysToStrip,
241
235
  maxDepth: userOptions.maxDepth ?? DEFAULT_DEEP_CLEAN_OPTIONS.maxDepth,
242
236
  maxStringLength: userOptions.maxStringLength ?? DEFAULT_DEEP_CLEAN_OPTIONS.maxStringLength,
243
237
  maxArrayLength: userOptions.maxArrayLength ?? DEFAULT_DEEP_CLEAN_OPTIONS.maxArrayLength,
@@ -263,6 +257,47 @@ function getMapKeyType(key) {
263
257
  if (key instanceof Error) return "error";
264
258
  return typeof key;
265
259
  }
260
+ function hasOnlyKnownKeys(value, keys) {
261
+ try {
262
+ return Object.keys(value).every((key) => keys.includes(key));
263
+ } catch {
264
+ return false;
265
+ }
266
+ }
267
+ function isSpanLike(value) {
268
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
269
+ const span = value;
270
+ try {
271
+ return typeof span.id === "string" && typeof span.traceId === "string" && typeof span.type === "string" && typeof span.name === "string";
272
+ } catch {
273
+ return false;
274
+ }
275
+ }
276
+ function isTracingContextLike(value) {
277
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
278
+ const context = value;
279
+ if (!hasOnlyKnownKeys(context, ["currentSpan"])) return false;
280
+ try {
281
+ return context.currentSpan === void 0 || isSpanLike(context.currentSpan);
282
+ } catch {
283
+ return false;
284
+ }
285
+ }
286
+ function isLoggerLike(value) {
287
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
288
+ const logger = value;
289
+ try {
290
+ return LOGGER_METHODS.some((method) => typeof logger[method] === "function");
291
+ } catch {
292
+ return false;
293
+ }
294
+ }
295
+ function shouldStripEntry(key, value, stripSet) {
296
+ if (stripSet.has(key)) return true;
297
+ if (key === "tracingContext" && isTracingContextLike(value)) return true;
298
+ if (key === "logger") return typeof value === "function" || isLoggerLike(value);
299
+ return FUNCTION_KEYS_TO_STRIP.has(key) && typeof value === "function";
300
+ }
266
301
  function restoreSerializedMapKey(keyType, key) {
267
302
  switch (keyType) {
268
303
  case "undefined": return;
@@ -365,7 +400,7 @@ function deepClean(value, options = DEFAULT_DEEP_CLEAN_OPTIONS) {
365
400
  let mapKeyCount = 0;
366
401
  let omittedMapEntries = 0;
367
402
  for (const [mapKey, mapVal] of val) {
368
- if (typeof mapKey === "string" && stripSet.has(mapKey)) continue;
403
+ if (typeof mapKey === "string" && shouldStripEntry(mapKey, mapVal, stripSet)) continue;
369
404
  if (mapKeyCount >= maxObjectKeys) {
370
405
  omittedMapEntries++;
371
406
  continue;
@@ -441,15 +476,34 @@ function deepClean(value, options = DEFAULT_DEEP_CLEAN_OPTIONS) {
441
476
  }
442
477
  if (looksLikeJsonSchema) return val;
443
478
  const cleaned = {};
444
- const keys = Object.keys(val).filter((key) => !stripSet.has(key));
479
+ let keys;
480
+ try {
481
+ keys = Object.keys(val);
482
+ } catch (error) {
483
+ return formatSerializationError(error);
484
+ }
445
485
  let keyCount = 0;
446
486
  for (const key of keys) {
487
+ if (stripSet.has(key)) continue;
488
+ let rawValue;
489
+ try {
490
+ rawValue = val[key];
491
+ } catch (error) {
492
+ if (keyCount >= maxObjectKeys) {
493
+ cleaned["__truncated"] = `${keys.length - keyCount} more keys omitted`;
494
+ break;
495
+ }
496
+ cleaned[key] = formatSerializationError(error);
497
+ keyCount++;
498
+ continue;
499
+ }
500
+ if (shouldStripEntry(key, rawValue, stripSet)) continue;
447
501
  if (keyCount >= maxObjectKeys) {
448
502
  cleaned["__truncated"] = `${keys.length - keyCount} more keys omitted`;
449
503
  break;
450
504
  }
451
505
  try {
452
- cleaned[key] = helper(val[key], depth + 1);
506
+ cleaned[key] = helper(rawValue, depth + 1);
453
507
  keyCount++;
454
508
  } catch (error) {
455
509
  cleaned[key] = formatSerializationError(error);
@@ -1048,6 +1102,8 @@ const MINIFIED_METER_TO_CANONICAL = {
1048
1102
  ot: "output_tokens",
1049
1103
  icrt: "input_cache_read_tokens",
1050
1104
  icwt: "input_cache_write_tokens",
1105
+ icwt5m: "input_cache_write_5m_tokens",
1106
+ icwt1h: "input_cache_write_1h_tokens",
1051
1107
  iat: "input_audio_tokens",
1052
1108
  oat: "output_audio_tokens",
1053
1109
  ort: "output_reasoning_tokens"
@@ -1104,6 +1160,15 @@ function parsePricingModelText(content) {
1104
1160
  }
1105
1161
  return pricingModels;
1106
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
+ }
1107
1172
  function expandPricingModelRow(row) {
1108
1173
  return new PricingModel({
1109
1174
  id: row.i,
@@ -1118,7 +1183,7 @@ function expandPricingModelRow(row) {
1118
1183
  op: condition.op,
1119
1184
  value: condition.value
1120
1185
  })),
1121
- rates: Object.fromEntries(Object.entries(tier.r).map(([meter, value]) => [MINIFIED_METER_TO_CANONICAL[meter], value.c]))
1186
+ rates: expandRates(row, tier)
1122
1187
  }))
1123
1188
  });
1124
1189
  }
@@ -1249,6 +1314,8 @@ const PricingMeter = {
1249
1314
  INPUT_AUDIO_TOKENS: "input_audio_tokens",
1250
1315
  INPUT_CACHE_READ_TOKENS: "input_cache_read_tokens",
1251
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",
1252
1319
  INPUT_IMAGE_TOKENS: "input_image_tokens",
1253
1320
  OUTPUT_TOKENS: "output_tokens",
1254
1321
  OUTPUT_AUDIO_TOKENS: "output_audio_tokens",
@@ -1261,6 +1328,8 @@ const TokenMetrics = {
1261
1328
  INPUT_TEXT: "mastra_model_input_text_tokens",
1262
1329
  INPUT_CACHE_READ: "mastra_model_input_cache_read_tokens",
1263
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",
1264
1333
  INPUT_AUDIO: "mastra_model_input_audio_tokens",
1265
1334
  INPUT_IMAGE: "mastra_model_input_image_tokens",
1266
1335
  OUTPUT_TEXT: "mastra_model_output_text_tokens",
@@ -1290,6 +1359,8 @@ function getTokenMetricSamples(usage) {
1290
1359
  pushIfPositive(TokenMetrics.INPUT_TEXT, usage.inputDetails.text);
1291
1360
  pushIfPositive(TokenMetrics.INPUT_CACHE_READ, usage.inputDetails.cacheRead);
1292
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);
1293
1364
  pushIfPositive(TokenMetrics.INPUT_AUDIO, usage.inputDetails.audio);
1294
1365
  pushIfPositive(TokenMetrics.INPUT_IMAGE, usage.inputDetails.image);
1295
1366
  }
@@ -1356,15 +1427,40 @@ function estimateCosts(args, pricingRegistry = PricingRegistry.getGlobal()) {
1356
1427
  results.set(TokenMetrics.INPUT_CACHE_READ, result.costContext);
1357
1428
  inputDetailResults.push(result);
1358
1429
  }
1359
- 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) {
1360
1455
  const result = estimateCostForMeter({
1361
1456
  meter: PricingMeter.INPUT_CACHE_WRITE_TOKENS,
1362
- tokenCount: usage.inputDetails.cacheWrite,
1457
+ tokenCount: unclassifiedCacheWrite,
1363
1458
  ...estimateFields
1364
1459
  });
1365
- results.set(TokenMetrics.INPUT_CACHE_WRITE, result.costContext);
1460
+ cacheWriteDetailResults.push(result);
1366
1461
  inputDetailResults.push(result);
1367
1462
  }
1463
+ if (cacheWriteDetailResults.length > 0) setCombinedCostContext(results, TokenMetrics.INPUT_CACHE_WRITE, cacheWriteDetailResults, pricingModel, costMetadata);
1368
1464
  if (usage.inputDetails?.image) {
1369
1465
  const result = estimateCostForMeter({
1370
1466
  meter: PricingMeter.INPUT_IMAGE_TOKENS,
@@ -1441,6 +1537,20 @@ function estimateCosts(args, pricingRegistry = PricingRegistry.getGlobal()) {
1441
1537
  function applyErrorContextForUsage(results, usage, errorContext) {
1442
1538
  for (const sample of getTokenMetricSamples(usage)) results.set(sample.name, errorContext);
1443
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
+ }
1444
1554
  function setAggregateCostContext(args) {
1445
1555
  const { results, totalMetric, fallbackMeter, totalTokenCount, detailResults, pricingModel, pricingTier, costMetadata } = args;
1446
1556
  if (totalTokenCount == null) return;
@@ -1681,14 +1791,24 @@ function extractUsageMetrics(usage, providerMetadata) {
1681
1791
  if (isDefined(aiSdkDetails?.cacheReadTokens)) inputDetails.cacheRead = aiSdkDetails.cacheReadTokens;
1682
1792
  if (isDefined(aiSdkDetails?.cacheWriteTokens)) inputDetails.cacheWrite = aiSdkDetails.cacheWriteTokens;
1683
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;
1684
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);
1685
1798
  if (isDefined(usage.reasoningTokens)) outputDetails.reasoning = usage.reasoningTokens;
1686
1799
  const anthropic = providerMetadata?.anthropic;
1687
1800
  if (anthropic) {
1688
1801
  const rawV3InputUsage = isV3RawUsage(usage.raw) ? usage.raw.inputTokens : void 0;
1689
1802
  const hasV3CachedTotals = rawV3InputUsage?.total !== void 0 && (rawV3InputUsage.cacheRead !== void 0 || rawV3InputUsage.cacheWrite !== void 0);
1690
1803
  if (!isDefined(inputDetails.cacheRead) && isDefined(anthropic.cacheReadInputTokens)) inputDetails.cacheRead = anthropic.cacheReadInputTokens;
1691
- 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
+ }
1692
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);
1693
1813
  }
1694
1814
  const google = providerMetadata?.google;
@@ -1729,6 +1849,8 @@ function mergeInputDetails(a, b) {
1729
1849
  text: addOptional(a.text, b.text),
1730
1850
  cacheRead: addOptional(a.cacheRead, b.cacheRead),
1731
1851
  cacheWrite: addOptional(a.cacheWrite, b.cacheWrite),
1852
+ cacheWrite5m: addOptional(a.cacheWrite5m, b.cacheWrite5m),
1853
+ cacheWrite1h: addOptional(a.cacheWrite1h, b.cacheWrite1h),
1732
1854
  audio: addOptional(a.audio, b.audio),
1733
1855
  image: addOptional(a.image, b.image)
1734
1856
  };
@@ -2462,6 +2584,49 @@ var ModelSpanTracker = class {
2462
2584
  }
2463
2585
  };
2464
2586
  //#endregion
2587
+ //#region src/spans/metadata.ts
2588
+ /**
2589
+ * Shared span-metadata helpers.
2590
+ *
2591
+ * Used by both the observability instance (`instances/base.ts`) and the span
2592
+ * base class (`spans/base.ts`) so the plain-record check and the
2593
+ * descriptor-preserving merge stay in a single place. Both feed the same span
2594
+ * metadata pipeline, so keeping one implementation avoids divergence.
2595
+ */
2596
+ /**
2597
+ * Returns true only for plain object records (prototype is `Object.prototype`
2598
+ * or `null`). Maps, Dates, class instances, and arrays return false so callers
2599
+ * can preserve their original shape instead of shallow-copying them into `{}`.
2600
+ */
2601
+ function isPlainRecord(value) {
2602
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
2603
+ try {
2604
+ const prototype = Object.getPrototypeOf(value);
2605
+ return prototype === Object.prototype || prototype === null;
2606
+ } catch {
2607
+ return false;
2608
+ }
2609
+ }
2610
+ /**
2611
+ * Merges two metadata values while preserving property descriptors (so getters
2612
+ * are copied as accessors rather than eagerly invoked). Only plain records are
2613
+ * merged; if either side is non-plain the second argument is returned as-is,
2614
+ * matching the previous per-module behavior.
2615
+ */
2616
+ function mergeMetadata$1(parentMetadata, metadata) {
2617
+ if (!parentMetadata) return metadata;
2618
+ if (!metadata) return parentMetadata;
2619
+ if (!isPlainRecord(parentMetadata) || !isPlainRecord(metadata)) return metadata;
2620
+ try {
2621
+ const merged = {};
2622
+ Object.defineProperties(merged, Object.getOwnPropertyDescriptors(parentMetadata));
2623
+ Object.defineProperties(merged, Object.getOwnPropertyDescriptors(metadata));
2624
+ return merged;
2625
+ } catch {
2626
+ return metadata;
2627
+ }
2628
+ }
2629
+ //#endregion
2465
2630
  //#region src/spans/base.ts
2466
2631
  /**
2467
2632
  * Determines if a span type should be considered internal based on flags.
@@ -2586,10 +2751,7 @@ var BaseSpan = class {
2586
2751
  this.type = options.type;
2587
2752
  this.isInternal = isSpanInternal(this.type, options.tracingPolicy?.internal);
2588
2753
  this.isExcluded = this.alwaysExcluded || observabilityConfig.excludeSpanTypes?.includes(this.type) === true || this.isInternal && !observabilityConfig.includeInternalSpans;
2589
- this.metadata = deepClean(options.parent?.metadata || options.metadata ? {
2590
- ...options.parent?.metadata,
2591
- ...options.metadata
2592
- } : void 0, this.deepCleanOptions);
2754
+ this.metadata = deepClean(this.prepareSpanMetadata(mergeMetadata$1(options.parent?.metadata, options.metadata)), this.deepCleanOptions);
2593
2755
  this.parent = options.parent;
2594
2756
  this.startTime = options.startTime ?? /* @__PURE__ */ new Date();
2595
2757
  this.observabilityInstance = observabilityInstance;
@@ -2607,9 +2769,32 @@ var BaseSpan = class {
2607
2769
  }
2608
2770
  this.attributes = deepClean(options.attributes, this.deepCleanOptions) || {};
2609
2771
  if (options.requestContext && options.requestContext.size() > 0) this.requestContext = deepClean(options.requestContext, this.deepCleanOptions);
2610
- if (this.isEvent) this.output = deepClean(options.output, this.deepCleanOptions);
2772
+ if (this.isEvent) this.output = deepClean(this.prepareSpanOutput(options.output), this.deepCleanOptions);
2611
2773
  else this.input = deepClean(options.input, this.deepCleanOptions);
2612
2774
  }
2775
+ prepareSpanOutput(value) {
2776
+ if (!isPlainRecord(value)) return value;
2777
+ if (this.type !== _mastra_core_observability.SpanType.MODEL_STEP && this.type !== _mastra_core_observability.SpanType.MODEL_INFERENCE) return value;
2778
+ try {
2779
+ const prepared = { ...value };
2780
+ delete prepared.steps;
2781
+ return prepared;
2782
+ } catch {
2783
+ return value;
2784
+ }
2785
+ }
2786
+ prepareSpanMetadata(value) {
2787
+ if (!isPlainRecord(value)) return value;
2788
+ if (this.type !== _mastra_core_observability.SpanType.MODEL_STEP) return value;
2789
+ try {
2790
+ const prepared = { ...value };
2791
+ delete prepared.providerMetadata;
2792
+ delete prepared.experimental_providerMetadata;
2793
+ return prepared;
2794
+ } catch {
2795
+ return value;
2796
+ }
2797
+ }
2613
2798
  createChildSpan(options) {
2614
2799
  return this.observabilityInstance.startSpan({
2615
2800
  ...options,
@@ -2816,10 +3001,10 @@ var DefaultSpan = class extends BaseSpan {
2816
3001
  this.endTime = /* @__PURE__ */ new Date();
2817
3002
  if (options?.metadata) this.metadata = {
2818
3003
  ...this.metadata,
2819
- ...deepClean(options.metadata, this.deepCleanOptions)
3004
+ ...deepClean(this.prepareSpanMetadata(options.metadata), this.deepCleanOptions)
2820
3005
  };
2821
3006
  if (this.isExcluded) return;
2822
- if (options?.output !== void 0) this.output = deepClean(options.output, this.deepCleanOptions);
3007
+ if (options?.output !== void 0) this.output = deepClean(this.prepareSpanOutput(options.output), this.deepCleanOptions);
2823
3008
  if (options?.attributes) this.attributes = {
2824
3009
  ...this.attributes,
2825
3010
  ...deepClean(options.attributes, this.deepCleanOptions)
@@ -2830,7 +3015,7 @@ var DefaultSpan = class extends BaseSpan {
2830
3015
  const { error, endSpan = true, attributes, metadata } = options;
2831
3016
  if (metadata) this.metadata = {
2832
3017
  ...this.metadata,
2833
- ...deepClean(metadata, this.deepCleanOptions)
3018
+ ...deepClean(this.prepareSpanMetadata(metadata), this.deepCleanOptions)
2834
3019
  };
2835
3020
  if (!this.isExcluded) {
2836
3021
  this.errorInfo = deepClean(error instanceof _mastra_core_error.MastraError ? {
@@ -2859,11 +3044,11 @@ var DefaultSpan = class extends BaseSpan {
2859
3044
  if (options.name !== void 0) this.name = options.name;
2860
3045
  if (options.metadata) this.metadata = {
2861
3046
  ...this.metadata,
2862
- ...deepClean(options.metadata, this.deepCleanOptions)
3047
+ ...deepClean(this.prepareSpanMetadata(options.metadata), this.deepCleanOptions)
2863
3048
  };
2864
3049
  if (this.isExcluded) return;
2865
3050
  if (options.input !== void 0) this.input = deepClean(options.input, this.deepCleanOptions);
2866
- if (options.output !== void 0) this.output = deepClean(options.output, this.deepCleanOptions);
3051
+ if (options.output !== void 0) this.output = deepClean(this.prepareSpanOutput(options.output), this.deepCleanOptions);
2867
3052
  if (options.attributes) this.attributes = {
2868
3053
  ...this.attributes,
2869
3054
  ...deepClean(options.attributes, this.deepCleanOptions)
@@ -2951,6 +3136,19 @@ var NoOpSpan = class extends BaseSpan {
2951
3136
  /**
2952
3137
  * BaseObservability - Abstract base class for Observability implementations
2953
3138
  */
3139
+ function hasMetadataKey(metadata, key) {
3140
+ if (!metadata || typeof metadata !== "object") return false;
3141
+ try {
3142
+ return Object.prototype.hasOwnProperty.call(Object.getOwnPropertyDescriptors(metadata), key);
3143
+ } catch {
3144
+ return true;
3145
+ }
3146
+ }
3147
+ function injectEnvironmentMetadata(metadata, environment) {
3148
+ if (environment === void 0 || hasMetadataKey(metadata, "environment")) return metadata;
3149
+ if (metadata && !isPlainRecord(metadata)) return metadata;
3150
+ return mergeMetadata$1(metadata, { environment });
3151
+ }
2954
3152
  /**
2955
3153
  * Abstract base class for all Observability implementations in Mastra.
2956
3154
  */
@@ -3046,16 +3244,9 @@ var BaseObservabilityInstance = class extends _mastra_core_base.MastraBase {
3046
3244
  let traceState;
3047
3245
  if (options.parent) traceState = options.parent.traceState;
3048
3246
  else traceState = this.computeTraceState(tracingOptions);
3049
- const tracingMetadata = !options.parent ? tracingOptions?.metadata : void 0;
3050
- const mergedMetadata = metadata || tracingMetadata ? {
3051
- ...metadata,
3052
- ...tracingMetadata
3053
- } : void 0;
3247
+ const mergedMetadata = mergeMetadata$1(metadata, !options.parent ? tracingOptions?.metadata : void 0);
3054
3248
  const enrichedMetadata = this.extractMetadataFromRequestContext(requestContext, mergedMetadata, traceState);
3055
- const finalMetadata = !options.parent && this.#mastraEnvironment !== void 0 && (enrichedMetadata === void 0 || enrichedMetadata.environment === void 0) ? {
3056
- ...enrichedMetadata ?? {},
3057
- environment: this.#mastraEnvironment
3058
- } : enrichedMetadata;
3249
+ const finalMetadata = !options.parent ? injectEnvironmentMetadata(enrichedMetadata, this.#mastraEnvironment) : enrichedMetadata;
3059
3250
  const tags = !options.parent ? tracingOptions?.tags : void 0;
3060
3251
  const traceId = !options.parent ? options.traceId ?? tracingOptions?.traceId : options.traceId;
3061
3252
  const parentSpanId = options.parentSpanId;
@@ -3320,10 +3511,7 @@ var BaseObservabilityInstance = class extends _mastra_core_base.MastraBase {
3320
3511
  if (!requestContext || !traceState || traceState.requestContextKeys.length === 0) return explicitMetadata;
3321
3512
  const extracted = this.extractKeys(requestContext, traceState.requestContextKeys);
3322
3513
  if (Object.keys(extracted).length === 0 && !explicitMetadata) return;
3323
- return {
3324
- ...extracted,
3325
- ...explicitMetadata
3326
- };
3514
+ return mergeMetadata$1(extracted, explicitMetadata);
3327
3515
  }
3328
3516
  /**
3329
3517
  * Extract specific keys from RequestContext
@@ -6370,6 +6558,8 @@ const QUOTA_EXCEEDED_STATUS = 402;
6370
6558
  const OBSERVABILITY_STATUS_HEADER = "x-mastra-observability";
6371
6559
  const OBSERVABILITY_DISABLED_VALUE = "disabled";
6372
6560
  const OBSERVABILITY_RETRY_AFTER_HEADER = "x-mastra-observability-retry-after";
6561
+ const OBSERVABILITY_CAPABILITIES_HEADER = "x-mastra-observability-capabilities";
6562
+ const QUOTA_PAUSE_CAPABILITY = "quota-pause-v1";
6373
6563
  const DEFAULT_QUOTA_PROBE_INTERVAL_SECONDS = 300;
6374
6564
  const MAX_QUOTA_PROBE_INTERVAL_SECONDS = Math.floor(2147483647 / 1e3);
6375
6565
  function isObservabilityDisabled(response) {
@@ -6696,7 +6886,8 @@ var MastraPlatformExporter = class extends BaseExporter {
6696
6886
  buildPublishHeaders() {
6697
6887
  return {
6698
6888
  Authorization: `Bearer ${this.platformConfig.accessToken}`,
6699
- "Content-Type": "application/json"
6889
+ "Content-Type": "application/json",
6890
+ [OBSERVABILITY_CAPABILITIES_HEADER]: QUOTA_PAUSE_CAPABILITY
6700
6891
  };
6701
6892
  }
6702
6893
  buildPublishBody(signal, records) {
@@ -6749,7 +6940,7 @@ var MastraPlatformExporter = class extends BaseExporter {
6749
6940
  this.flushTimer = null;
6750
6941
  }
6751
6942
  this.resetBuffer();
6752
- this.logger.warn(`Mastra observability paused: quota exhausted, dropping telemetry and probing every ${retryAfterSeconds}s`);
6943
+ 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.`);
6753
6944
  this.scheduleQuotaProbe();
6754
6945
  }
6755
6946
  scheduleQuotaProbe() {
@@ -9332,7 +9523,6 @@ exports.CardinalityFilter = CardinalityFilter;
9332
9523
  exports.CloudExporter = CloudExporter;
9333
9524
  exports.ConsoleExporter = ConsoleExporter;
9334
9525
  exports.DEFAULT_DEEP_CLEAN_OPTIONS = DEFAULT_DEEP_CLEAN_OPTIONS;
9335
- exports.DEFAULT_KEYS_TO_STRIP = DEFAULT_KEYS_TO_STRIP;
9336
9526
  exports.DEFAULT_LIMITS = DEFAULT_LIMITS;
9337
9527
  exports.DefaultExporter = DefaultExporter;
9338
9528
  exports.DefaultObservabilityInstance = DefaultObservabilityInstance;
@@ -9344,8 +9534,10 @@ exports.MastraStorageExporter = MastraStorageExporter;
9344
9534
  exports.MetricsContextImpl = MetricsContextImpl;
9345
9535
  exports.ModelSpanTracker = ModelSpanTracker;
9346
9536
  exports.NoOpSpan = NoOpSpan;
9537
+ exports.OBSERVABILITY_CAPABILITIES_HEADER = OBSERVABILITY_CAPABILITIES_HEADER;
9347
9538
  exports.Observability = Observability;
9348
9539
  exports.ObservabilityBus = ObservabilityBus;
9540
+ exports.QUOTA_PAUSE_CAPABILITY = QUOTA_PAUSE_CAPABILITY;
9349
9541
  exports.SamplingStrategyType = SamplingStrategyType;
9350
9542
  exports.SensitiveDataFilter = SensitiveDataFilter;
9351
9543
  exports.TestExporter = TestExporter;