@mastra/observability 1.5.1 → 1.6.0-alpha.0

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
@@ -1,9 +1,12 @@
1
1
  import { MastraBase } from '@mastra/core/base';
2
2
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
3
  import { ConsoleLogger, LogLevel, RegisteredLogger } from '@mastra/core/logger';
4
- import { TracingEventType, SpanType, DEFAULT_BLOCKED_LABELS, InternalSpans } from '@mastra/core/observability';
4
+ import { TracingEventType, DEFAULT_BLOCKED_LABELS, SpanType, InternalSpans } from '@mastra/core/observability';
5
5
  import { fetchWithRetry, getNestedValue, setNestedValue } from '@mastra/core/utils';
6
6
  import { buildUpdateSpanRecord, buildFeedbackRecord, buildLogRecord, buildMetricRecord, buildScoreRecord, buildCreateSpanRecord } from '@mastra/core/storage';
7
+ import fs from 'fs';
8
+ import { createRequire } from 'module';
9
+ import path from 'path';
7
10
  import { TransformStream } from 'stream/web';
8
11
 
9
12
  var __defProp = Object.defineProperty;
@@ -777,10 +780,10 @@ function mergeDefs(...defs) {
777
780
  function cloneDef(schema) {
778
781
  return mergeDefs(schema._zod.def);
779
782
  }
780
- function getElementAtPath(obj, path) {
781
- if (!path)
783
+ function getElementAtPath(obj, path2) {
784
+ if (!path2)
782
785
  return obj;
783
- return path.reduce((acc, key) => acc?.[key], obj);
786
+ return path2.reduce((acc, key) => acc?.[key], obj);
784
787
  }
785
788
  function promiseAllObject(promisesObj) {
786
789
  const keys = Object.keys(promisesObj);
@@ -1163,11 +1166,11 @@ function aborted(x, startIndex = 0) {
1163
1166
  }
1164
1167
  return false;
1165
1168
  }
1166
- function prefixIssues(path, issues) {
1169
+ function prefixIssues(path2, issues) {
1167
1170
  return issues.map((iss) => {
1168
1171
  var _a2;
1169
1172
  (_a2 = iss).path ?? (_a2.path = []);
1170
- iss.path.unshift(path);
1173
+ iss.path.unshift(path2);
1171
1174
  return iss;
1172
1175
  });
1173
1176
  }
@@ -1350,7 +1353,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
1350
1353
  }
1351
1354
  function treeifyError(error48, mapper = (issue2) => issue2.message) {
1352
1355
  const result = { errors: [] };
1353
- const processError = (error49, path = []) => {
1356
+ const processError = (error49, path2 = []) => {
1354
1357
  var _a2, _b;
1355
1358
  for (const issue2 of error49.issues) {
1356
1359
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -1360,7 +1363,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
1360
1363
  } else if (issue2.code === "invalid_element") {
1361
1364
  processError({ issues: issue2.issues }, issue2.path);
1362
1365
  } else {
1363
- const fullpath = [...path, ...issue2.path];
1366
+ const fullpath = [...path2, ...issue2.path];
1364
1367
  if (fullpath.length === 0) {
1365
1368
  result.errors.push(mapper(issue2));
1366
1369
  continue;
@@ -1392,8 +1395,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
1392
1395
  }
1393
1396
  function toDotPath(_path) {
1394
1397
  const segs = [];
1395
- const path = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1396
- for (const seg of path) {
1398
+ const path2 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1399
+ for (const seg of path2) {
1397
1400
  if (typeof seg === "number")
1398
1401
  segs.push(`[${seg}]`);
1399
1402
  else if (typeof seg === "symbol")
@@ -13365,13 +13368,13 @@ function resolveRef(ref, ctx) {
13365
13368
  if (!ref.startsWith("#")) {
13366
13369
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
13367
13370
  }
13368
- const path = ref.slice(1).split("/").filter(Boolean);
13369
- if (path.length === 0) {
13371
+ const path2 = ref.slice(1).split("/").filter(Boolean);
13372
+ if (path2.length === 0) {
13370
13373
  return ctx.rootSchema;
13371
13374
  }
13372
13375
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
13373
- if (path[0] === defsKey) {
13374
- const key = path[1];
13376
+ if (path2[0] === defsKey) {
13377
+ const key = path2[1];
13375
13378
  if (!key || !ctx.defs[key]) {
13376
13379
  throw new Error(`Reference not found: ${ref}`);
13377
13380
  }
@@ -15890,7 +15893,8 @@ var TestExporter = class extends BaseExporter {
15890
15893
  this.#trackEvent("log");
15891
15894
  if (this.#config.storeLogs) {
15892
15895
  const log = event.log;
15893
- const logMessage = `[TestExporter] log.${log.level}: "${log.message}"${log.traceId ? ` (trace: ${log.traceId.slice(-8)})` : ""}`;
15896
+ const traceId = log.correlationContext?.traceId;
15897
+ const logMessage = `[TestExporter] log.${log.level}: "${log.message}"${traceId ? ` (trace: ${traceId.slice(-8)})` : ""}`;
15894
15898
  this.#debugLogs.push(logMessage);
15895
15899
  }
15896
15900
  this.#logEvents.push(event);
@@ -16007,7 +16011,7 @@ var TestExporter = class extends BaseExporter {
16007
16011
  getByTraceId(traceId) {
16008
16012
  const events = this.#tracingEvents.filter((e) => e.exportedSpan.traceId === traceId);
16009
16013
  const spans = this.#getUniqueSpansFromEvents(events);
16010
- const logs = this.#logEvents.filter((e) => e.log.traceId === traceId).map((e) => e.log);
16014
+ const logs = this.#logEvents.filter((e) => e.log.correlationContext?.traceId === traceId).map((e) => e.log);
16011
16015
  const scores = this.#scoreEvents.filter((e) => e.score.traceId === traceId).map((e) => e.score);
16012
16016
  const feedback = this.#feedbackEvents.filter((e) => e.feedback.traceId === traceId).map((e) => e.feedback);
16013
16017
  return { events, spans, logs, scores, feedback };
@@ -16074,7 +16078,7 @@ var TestExporter = class extends BaseExporter {
16074
16078
  traceIds.add(event.exportedSpan.traceId);
16075
16079
  }
16076
16080
  for (const event of this.#logEvents) {
16077
- if (event.log.traceId) traceIds.add(event.log.traceId);
16081
+ if (event.log.correlationContext?.traceId) traceIds.add(event.log.correlationContext.traceId);
16078
16082
  }
16079
16083
  for (const event of this.#scoreEvents) {
16080
16084
  traceIds.add(event.score.traceId);
@@ -16109,7 +16113,7 @@ var TestExporter = class extends BaseExporter {
16109
16113
  * Get logs for a specific trace
16110
16114
  */
16111
16115
  getLogsByTraceId(traceId) {
16112
- return this.#logEvents.filter((e) => e.log.traceId === traceId).map((e) => e.log);
16116
+ return this.#logEvents.filter((e) => e.log.correlationContext?.traceId === traceId).map((e) => e.log);
16113
16117
  }
16114
16118
  // ============================================================================
16115
16119
  // Metric Query Methods
@@ -16692,23 +16696,23 @@ Run with { updateSnapshot: true } to update.`
16692
16696
  * Deep compare two values, supporting special markers like __or__ and __any__.
16693
16697
  * Collects all mismatches into the provided array.
16694
16698
  */
16695
- #deepCompareWithMarkers(actual, expected, path, mismatches) {
16699
+ #deepCompareWithMarkers(actual, expected, path2, mismatches) {
16696
16700
  if (this.#isOrMarker(expected)) {
16697
16701
  const allowedValues = expected.__or__;
16698
16702
  const matches = allowedValues.some((allowed) => {
16699
16703
  const tempMismatches = [];
16700
- this.#deepCompareWithMarkers(actual, allowed, path, tempMismatches);
16704
+ this.#deepCompareWithMarkers(actual, allowed, path2, tempMismatches);
16701
16705
  return tempMismatches.length === 0;
16702
16706
  });
16703
16707
  if (!matches) {
16704
- mismatches.push({ path, expected: { __or__: allowedValues }, actual });
16708
+ mismatches.push({ path: path2, expected: { __or__: allowedValues }, actual });
16705
16709
  }
16706
16710
  return;
16707
16711
  }
16708
16712
  if (this.#isAnyMarker(expected)) {
16709
16713
  const typeConstraint = expected.__any__;
16710
16714
  if (actual === null || actual === void 0) {
16711
- mismatches.push({ path, expected: { __any__: typeConstraint }, actual });
16715
+ mismatches.push({ path: path2, expected: { __any__: typeConstraint }, actual });
16712
16716
  return;
16713
16717
  }
16714
16718
  if (typeConstraint === true) {
@@ -16717,7 +16721,7 @@ Run with { updateSnapshot: true } to update.`
16717
16721
  const actualType = Array.isArray(actual) ? "array" : typeof actual;
16718
16722
  if (actualType !== typeConstraint) {
16719
16723
  mismatches.push({
16720
- path,
16724
+ path: path2,
16721
16725
  expected: { __any__: typeConstraint },
16722
16726
  actual: `(${actualType}) ${JSON.stringify(actual).slice(0, 50)}...`
16723
16727
  });
@@ -16726,21 +16730,21 @@ Run with { updateSnapshot: true } to update.`
16726
16730
  }
16727
16731
  if (Array.isArray(expected)) {
16728
16732
  if (!Array.isArray(actual)) {
16729
- mismatches.push({ path, expected, actual });
16733
+ mismatches.push({ path: path2, expected, actual });
16730
16734
  return;
16731
16735
  }
16732
16736
  if (actual.length !== expected.length) {
16733
- mismatches.push({ path: `${path}.length`, expected: expected.length, actual: actual.length });
16737
+ mismatches.push({ path: `${path2}.length`, expected: expected.length, actual: actual.length });
16734
16738
  return;
16735
16739
  }
16736
16740
  for (let i = 0; i < expected.length; i++) {
16737
- this.#deepCompareWithMarkers(actual[i], expected[i], `${path}[${i}]`, mismatches);
16741
+ this.#deepCompareWithMarkers(actual[i], expected[i], `${path2}[${i}]`, mismatches);
16738
16742
  }
16739
16743
  return;
16740
16744
  }
16741
16745
  if (expected !== null && typeof expected === "object") {
16742
16746
  if (actual === null || typeof actual !== "object" || Array.isArray(actual)) {
16743
- mismatches.push({ path, expected, actual });
16747
+ mismatches.push({ path: path2, expected, actual });
16744
16748
  return;
16745
16749
  }
16746
16750
  const expectedObj = expected;
@@ -16751,11 +16755,11 @@ Run with { updateSnapshot: true } to update.`
16751
16755
  }
16752
16756
  if (!(key in actualObj)) {
16753
16757
  if (expectedObj[key] !== void 0) {
16754
- mismatches.push({ path: `${path}.${key}`, expected: expectedObj[key], actual: void 0 });
16758
+ mismatches.push({ path: `${path2}.${key}`, expected: expectedObj[key], actual: void 0 });
16755
16759
  }
16756
16760
  continue;
16757
16761
  }
16758
- this.#deepCompareWithMarkers(actualObj[key], expectedObj[key], `${path}.${key}`, mismatches);
16762
+ this.#deepCompareWithMarkers(actualObj[key], expectedObj[key], `${path2}.${key}`, mismatches);
16759
16763
  }
16760
16764
  for (const key of Object.keys(actualObj)) {
16761
16765
  if (this.#isMetadataKey(key)) {
@@ -16763,14 +16767,14 @@ Run with { updateSnapshot: true } to update.`
16763
16767
  }
16764
16768
  if (!(key in expectedObj)) {
16765
16769
  if (actualObj[key] !== void 0) {
16766
- mismatches.push({ path: `${path}.${key}`, expected: void 0, actual: actualObj[key] });
16770
+ mismatches.push({ path: `${path2}.${key}`, expected: void 0, actual: actualObj[key] });
16767
16771
  }
16768
16772
  }
16769
16773
  }
16770
16774
  return;
16771
16775
  }
16772
16776
  if (actual !== expected) {
16773
- mismatches.push({ path, expected, actual });
16777
+ mismatches.push({ path: path2, expected, actual });
16774
16778
  }
16775
16779
  }
16776
16780
  /**
@@ -16983,120 +16987,6 @@ var BaseObservabilityEventBus = class _BaseObservabilityEventBus extends MastraB
16983
16987
  this.subscribers.clear();
16984
16988
  }
16985
16989
  };
16986
- var AutoExtractedMetrics = class {
16987
- constructor(observabilityBus) {
16988
- this.observabilityBus = observabilityBus;
16989
- }
16990
- /**
16991
- * Route a tracing event to the appropriate handler.
16992
- * SPAN_ENDED emits duration and token metrics (for model spans).
16993
- */
16994
- processTracingEvent(event) {
16995
- if (event.type === TracingEventType.SPAN_ENDED) {
16996
- this.onSpanEnded(event.exportedSpan);
16997
- }
16998
- }
16999
- /** Emit duration and token metrics when a span ends. */
17000
- onSpanEnded(span) {
17001
- const labels = this.extractLabels(span);
17002
- const durationMetricName = this.getDurationMetricName(span);
17003
- if (durationMetricName && span.startTime && span.endTime) {
17004
- const durationMs = span.endTime.getTime() - span.startTime.getTime();
17005
- const durationLabels = { ...labels };
17006
- durationLabels.status = span.errorInfo ? "error" : "ok";
17007
- this.observabilityBus.emitMetric(durationMetricName, durationMs, durationLabels);
17008
- }
17009
- if (span.type === SpanType.MODEL_GENERATION) {
17010
- const attrs = span.attributes;
17011
- if (attrs?.usage) {
17012
- this.extractTokenMetrics(attrs.usage, labels);
17013
- }
17014
- }
17015
- }
17016
- /** Build base metric labels from a span's entity and model attributes. */
17017
- extractLabels(span) {
17018
- const labels = {};
17019
- if (span.entityType) labels.entity_type = span.entityType;
17020
- const entityName = span.entityName ?? span.entityId;
17021
- if (entityName) labels.entity_name = entityName;
17022
- if (span.type === SpanType.MODEL_GENERATION) {
17023
- const attrs = span.attributes;
17024
- if (attrs?.model) labels.model = attrs.model;
17025
- if (attrs?.provider) labels.provider = attrs.provider;
17026
- }
17027
- return labels;
17028
- }
17029
- /** Emit token usage metrics from UsageStats. */
17030
- extractTokenMetrics(usage, labels) {
17031
- const emit = (name, value) => this.observabilityBus.emitMetric(name, value, labels);
17032
- const emitNonZero = (name, value) => {
17033
- if (value > 0) emit(name, value);
17034
- };
17035
- emit("mastra_model_total_input_tokens", usage.inputTokens ?? 0);
17036
- emit("mastra_model_total_output_tokens", usage.outputTokens ?? 0);
17037
- if (usage.inputDetails) {
17038
- emitNonZero("mastra_model_input_text_tokens", usage.inputDetails.text ?? 0);
17039
- emitNonZero("mastra_model_input_cache_read_tokens", usage.inputDetails.cacheRead ?? 0);
17040
- emitNonZero("mastra_model_input_cache_write_tokens", usage.inputDetails.cacheWrite ?? 0);
17041
- emitNonZero("mastra_model_input_audio_tokens", usage.inputDetails.audio ?? 0);
17042
- emitNonZero("mastra_model_input_image_tokens", usage.inputDetails.image ?? 0);
17043
- }
17044
- if (usage.outputDetails) {
17045
- emitNonZero("mastra_model_output_text_tokens", usage.outputDetails.text ?? 0);
17046
- emitNonZero("mastra_model_output_reasoning_tokens", usage.outputDetails.reasoning ?? 0);
17047
- emitNonZero("mastra_model_output_audio_tokens", usage.outputDetails.audio ?? 0);
17048
- emitNonZero("mastra_model_output_image_tokens", usage.outputDetails.image ?? 0);
17049
- }
17050
- }
17051
- /** Map a span type to its `*_duration_ms` metric name, or `null` for unsupported types. */
17052
- getDurationMetricName(span) {
17053
- switch (span.type) {
17054
- case SpanType.AGENT_RUN:
17055
- return "mastra_agent_duration_ms";
17056
- case SpanType.TOOL_CALL:
17057
- return "mastra_tool_duration_ms";
17058
- case SpanType.WORKFLOW_RUN:
17059
- return "mastra_workflow_duration_ms";
17060
- case SpanType.MODEL_GENERATION:
17061
- return "mastra_model_duration_ms";
17062
- default:
17063
- return null;
17064
- }
17065
- }
17066
- };
17067
- var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
17068
- var CardinalityFilter = class {
17069
- blockedLabels;
17070
- blockUUIDs;
17071
- /**
17072
- * @param config - Optional configuration. When omitted, uses
17073
- * {@link DEFAULT_BLOCKED_LABELS} and blocks UUID-valued labels.
17074
- */
17075
- constructor(config2) {
17076
- const blocked = config2?.blockedLabels ?? [...DEFAULT_BLOCKED_LABELS];
17077
- this.blockedLabels = new Set(blocked.map((l) => l.toLowerCase()));
17078
- this.blockUUIDs = config2?.blockUUIDs ?? true;
17079
- }
17080
- /**
17081
- * Return a copy of `labels` with blocked keys and UUID values removed.
17082
- *
17083
- * @param labels - Raw metric labels to filter.
17084
- * @returns A new object containing only the allowed labels.
17085
- */
17086
- filterLabels(labels) {
17087
- const filtered = {};
17088
- for (const [key, value] of Object.entries(labels)) {
17089
- if (this.blockedLabels.has(key.toLowerCase())) {
17090
- continue;
17091
- }
17092
- if (this.blockUUIDs && UUID_REGEX.test(value)) {
17093
- continue;
17094
- }
17095
- filtered[key] = value;
17096
- }
17097
- return filtered;
17098
- }
17099
- };
17100
16990
  function routeToHandler(handler, event, logger) {
17101
16991
  try {
17102
16992
  switch (event.type) {
@@ -17141,39 +17031,13 @@ function catchAsyncResult(result, handlerName, signal, logger) {
17141
17031
 
17142
17032
  // src/bus/observability-bus.ts
17143
17033
  var MAX_FLUSH_ITERATIONS = 3;
17144
- function isTracingEvent(event) {
17145
- return event.type === TracingEventType.SPAN_STARTED || event.type === TracingEventType.SPAN_UPDATED || event.type === TracingEventType.SPAN_ENDED;
17146
- }
17147
17034
  var ObservabilityBus = class extends BaseObservabilityEventBus {
17148
17035
  exporters = [];
17149
17036
  bridge;
17150
- autoExtractor;
17151
- cardinalityFilter;
17152
17037
  /** In-flight handler promises from routeToHandler. Self-cleaning via .finally(). */
17153
17038
  pendingHandlers = /* @__PURE__ */ new Set();
17154
- constructor(config2) {
17039
+ constructor() {
17155
17040
  super({ name: "ObservabilityBus" });
17156
- this.cardinalityFilter = config2?.cardinalityFilter ?? new CardinalityFilter();
17157
- if (config2?.autoExtractMetrics !== false) {
17158
- this.autoExtractor = new AutoExtractedMetrics(this);
17159
- }
17160
- }
17161
- /**
17162
- * Emit a metric event with validation and cardinality filtering.
17163
- * Non-finite or negative values are silently dropped.
17164
- * This is the single entry point for all metric emission (auto-extracted and user-defined).
17165
- */
17166
- emitMetric(name, value, labels) {
17167
- if (!Number.isFinite(value) || value < 0) return;
17168
- const filteredLabels = this.cardinalityFilter.filterLabels(labels);
17169
- const exportedMetric = {
17170
- timestamp: /* @__PURE__ */ new Date(),
17171
- name,
17172
- value,
17173
- labels: filteredLabels
17174
- };
17175
- const event = { type: "metric", metric: exportedMetric };
17176
- this.emit(event);
17177
17041
  }
17178
17042
  /**
17179
17043
  * Register an exporter to receive routed events.
@@ -17239,8 +17103,8 @@ var ObservabilityBus = class extends BaseObservabilityEventBus {
17239
17103
  return this.bridge;
17240
17104
  }
17241
17105
  /**
17242
- * Emit an event: route to exporter/bridge handlers, run auto-extraction,
17243
- * then forward to base class for subscriber delivery.
17106
+ * Emit an event: route to exporter/bridge handlers, then forward to base
17107
+ * class for subscriber delivery.
17244
17108
  *
17245
17109
  * emit() is synchronous — async handler promises are tracked internally
17246
17110
  * and can be drained via flush().
@@ -17252,13 +17116,6 @@ var ObservabilityBus = class extends BaseObservabilityEventBus {
17252
17116
  if (this.bridge) {
17253
17117
  this.trackPromise(routeToHandler(this.bridge, event, this.logger));
17254
17118
  }
17255
- if (this.autoExtractor && isTracingEvent(event)) {
17256
- try {
17257
- this.autoExtractor.processTracingEvent(event);
17258
- } catch (err) {
17259
- this.logger.error("[ObservabilityBus] Auto-extraction error:", err);
17260
- }
17261
- }
17262
17119
  super.emit(event);
17263
17120
  }
17264
17121
  /**
@@ -17326,13 +17183,13 @@ var LOG_LEVEL_PRIORITY = {
17326
17183
  var LoggerContextImpl = class {
17327
17184
  config;
17328
17185
  /**
17329
- * Create a logger context. Tags and metadata are defensively copied so
17186
+ * Create a logger context. Context and metadata are defensively copied so
17330
17187
  * mutations after construction do not affect emitted logs.
17331
17188
  */
17332
17189
  constructor(config2) {
17333
17190
  this.config = {
17334
17191
  ...config2,
17335
- tags: config2.tags ? [...config2.tags] : void 0,
17192
+ correlationContext: config2.correlationContext ? { ...config2.correlationContext } : void 0,
17336
17193
  metadata: config2.metadata ? structuredClone(config2.metadata) : void 0
17337
17194
  };
17338
17195
  }
@@ -17369,9 +17226,7 @@ var LoggerContextImpl = class {
17369
17226
  level,
17370
17227
  message,
17371
17228
  data,
17372
- traceId: this.config.traceId,
17373
- spanId: this.config.spanId,
17374
- tags: this.config.tags,
17229
+ correlationContext: this.config.correlationContext,
17375
17230
  metadata: this.config.metadata
17376
17231
  };
17377
17232
  const event = { type: "log", log: exportedLog };
@@ -17381,20 +17236,38 @@ var LoggerContextImpl = class {
17381
17236
 
17382
17237
  // src/context/metrics.ts
17383
17238
  var MetricsContextImpl = class {
17384
- baseLabels;
17239
+ correlationContext;
17240
+ metadata;
17241
+ cardinalityFilter;
17385
17242
  observabilityBus;
17386
17243
  /**
17387
- * Create a metrics context. Base labels are defensively copied so
17388
- * mutations after construction do not affect emitted metrics.
17244
+ * Create a metrics context. Correlation context and metadata are defensively
17245
+ * copied so mutations after construction do not affect emitted metrics.
17389
17246
  */
17390
17247
  constructor(config2) {
17391
- this.baseLabels = config2.labels ? { ...config2.labels } : {};
17248
+ this.correlationContext = config2.correlationContext ? { ...config2.correlationContext } : void 0;
17249
+ this.metadata = config2.metadata ? structuredClone(config2.metadata) : void 0;
17250
+ this.cardinalityFilter = config2.cardinalityFilter;
17392
17251
  this.observabilityBus = config2.observabilityBus;
17393
17252
  }
17394
17253
  /** Emit a metric observation. */
17395
- emit(name, value, labels) {
17396
- const allLabels = { ...this.baseLabels, ...labels };
17397
- this.observabilityBus.emitMetric(name, value, allLabels);
17254
+ emit(name, value, labels, options) {
17255
+ if (!Number.isFinite(value) || value < 0) {
17256
+ return;
17257
+ }
17258
+ const filteredLabels = labels ? this.cardinalityFilter.filterLabels(labels) : {};
17259
+ const costContext = options?.costContext ? cloneCostContext(options.costContext) : void 0;
17260
+ const exportedMetric = {
17261
+ timestamp: /* @__PURE__ */ new Date(),
17262
+ name,
17263
+ value,
17264
+ labels: filteredLabels,
17265
+ correlationContext: this.correlationContext,
17266
+ costContext,
17267
+ metadata: this.metadata
17268
+ };
17269
+ const event = { type: "metric", metric: exportedMetric };
17270
+ this.observabilityBus.emit(event);
17398
17271
  }
17399
17272
  /** @deprecated Use `emit()` instead. */
17400
17273
  counter(name) {
@@ -17421,6 +17294,563 @@ var MetricsContextImpl = class {
17421
17294
  };
17422
17295
  }
17423
17296
  };
17297
+ function cloneCostContext(costContext) {
17298
+ return {
17299
+ provider: costContext.provider,
17300
+ model: costContext.model,
17301
+ estimatedCost: costContext.estimatedCost,
17302
+ costUnit: costContext.costUnit,
17303
+ costMetadata: costContext.costMetadata ? structuredClone(costContext.costMetadata) : void 0
17304
+ };
17305
+ }
17306
+
17307
+ // src/metrics/pricing-model.ts
17308
+ var PricingTier = class {
17309
+ index;
17310
+ when;
17311
+ rates;
17312
+ constructor(args) {
17313
+ this.index = args.index;
17314
+ this.when = args.when;
17315
+ this.rates = args.rates;
17316
+ }
17317
+ matchesUsage(usage) {
17318
+ if (!this.when || this.when.length === 0) {
17319
+ return true;
17320
+ }
17321
+ return this.when.every((condition) => this.matchesCondition(condition, usage));
17322
+ }
17323
+ hasMatchingMeterForUsage(meter) {
17324
+ return Boolean(meter && typeof this.rates[meter] === "number");
17325
+ }
17326
+ matchesCondition(condition, usage) {
17327
+ const left = this.getConditionFieldValue(condition.field, usage);
17328
+ if (left == null) {
17329
+ return false;
17330
+ }
17331
+ switch (condition.op) {
17332
+ case "gt":
17333
+ return left > condition.value;
17334
+ case "gte":
17335
+ return left >= condition.value;
17336
+ case "lt":
17337
+ return left < condition.value;
17338
+ case "lte":
17339
+ return left <= condition.value;
17340
+ case "eq":
17341
+ return left === condition.value;
17342
+ case "neq":
17343
+ return left !== condition.value;
17344
+ default:
17345
+ return false;
17346
+ }
17347
+ }
17348
+ getConditionFieldValue(field, usage) {
17349
+ switch (field) {
17350
+ case "total_input_tokens":
17351
+ return typeof usage.inputTokens === "number" ? usage.inputTokens : null;
17352
+ default:
17353
+ return null;
17354
+ }
17355
+ }
17356
+ };
17357
+ var PricingModel = class {
17358
+ id;
17359
+ provider;
17360
+ model;
17361
+ schema;
17362
+ currency;
17363
+ tiers;
17364
+ constructor(args) {
17365
+ this.id = args.id;
17366
+ this.provider = args.provider;
17367
+ this.model = args.model;
17368
+ this.schema = args.schema;
17369
+ this.currency = args.currency;
17370
+ this.tiers = args.tiers;
17371
+ }
17372
+ getPricingTierForUsage(usage) {
17373
+ for (const tier of this.tiers) {
17374
+ if (tier.when && tier.when.length > 0 && tier.matchesUsage(usage)) {
17375
+ return tier;
17376
+ }
17377
+ }
17378
+ return this.getBasePricingTier();
17379
+ }
17380
+ getBasePricingTier() {
17381
+ return this.tiers[0] ?? null;
17382
+ }
17383
+ };
17384
+
17385
+ // src/metrics/pricing-registry.ts
17386
+ var DATA_FILE_NAME = "pricing-data.jsonl";
17387
+ var MINIFIED_METER_TO_CANONICAL = {
17388
+ it: "input_tokens",
17389
+ ot: "output_tokens",
17390
+ icrt: "input_cache_read_tokens",
17391
+ icwt: "input_cache_write_tokens",
17392
+ iat: "input_audio_tokens",
17393
+ oat: "output_audio_tokens",
17394
+ ort: "output_reasoning_tokens"
17395
+ };
17396
+ var MINIFIED_CONDITION_FIELD_TO_CANONICAL = {
17397
+ tit: "total_input_tokens"
17398
+ };
17399
+ var cachedLoadError = null;
17400
+ var PricingRegistry = class _PricingRegistry {
17401
+ constructor(pricingModels) {
17402
+ this.pricingModels = pricingModels;
17403
+ }
17404
+ static globalRegistry = null;
17405
+ static fromText(pricingModelText) {
17406
+ return new _PricingRegistry(parsePricingModelText(pricingModelText));
17407
+ }
17408
+ static getGlobal() {
17409
+ if (_PricingRegistry.globalRegistry) {
17410
+ return _PricingRegistry.globalRegistry;
17411
+ }
17412
+ const pricingModels = loadPricingModels();
17413
+ if (!pricingModels) {
17414
+ return null;
17415
+ }
17416
+ _PricingRegistry.globalRegistry = new _PricingRegistry(pricingModels);
17417
+ return _PricingRegistry.globalRegistry;
17418
+ }
17419
+ get(args) {
17420
+ return this.pricingModels.get(makePricingKey(args)) ?? null;
17421
+ }
17422
+ };
17423
+ function loadPricingModels() {
17424
+ if (cachedLoadError) {
17425
+ return null;
17426
+ }
17427
+ try {
17428
+ const content = fs.readFileSync(resolvePricingModelPath(), "utf-8");
17429
+ return parsePricingModelText(content);
17430
+ } catch (error48) {
17431
+ cachedLoadError = error48 instanceof Error ? error48.message : String(error48);
17432
+ return null;
17433
+ }
17434
+ }
17435
+ function parsePricingModelText(content) {
17436
+ const pricingModels = /* @__PURE__ */ new Map();
17437
+ for (const line of content.split("\n")) {
17438
+ const trimmed = line.trim();
17439
+ if (!trimmed) {
17440
+ continue;
17441
+ }
17442
+ const parsed = JSON.parse(trimmed);
17443
+ const pricingModel = expandPricingModelRow(parsed);
17444
+ pricingModels.set(makePricingKey(pricingModel), pricingModel);
17445
+ }
17446
+ return pricingModels;
17447
+ }
17448
+ function expandPricingModelRow(row) {
17449
+ return new PricingModel({
17450
+ id: row.i,
17451
+ provider: row.p,
17452
+ model: row.m,
17453
+ schema: row.s.v,
17454
+ currency: row.s.d.u,
17455
+ tiers: row.s.d.t.map(
17456
+ (tier, index) => new PricingTier({
17457
+ index,
17458
+ when: tier.w?.map((condition) => ({
17459
+ field: MINIFIED_CONDITION_FIELD_TO_CANONICAL[condition.f],
17460
+ op: condition.op,
17461
+ value: condition.value
17462
+ })),
17463
+ rates: Object.fromEntries(
17464
+ Object.entries(tier.r).map(([meter, value]) => [
17465
+ MINIFIED_METER_TO_CANONICAL[meter],
17466
+ value.c
17467
+ ])
17468
+ )
17469
+ })
17470
+ )
17471
+ });
17472
+ }
17473
+ function resolvePricingModelPath() {
17474
+ const packageRoot = getPackageRoot();
17475
+ const candidates = [
17476
+ path.join(packageRoot, "dist", "metrics", DATA_FILE_NAME),
17477
+ path.join(packageRoot, "src", "metrics", DATA_FILE_NAME),
17478
+ path.join(process.cwd(), "observability", "mastra", "src", "metrics", DATA_FILE_NAME),
17479
+ path.join(process.cwd(), "src", "metrics", DATA_FILE_NAME)
17480
+ ];
17481
+ for (const candidate of candidates) {
17482
+ if (fs.existsSync(candidate)) {
17483
+ return candidate;
17484
+ }
17485
+ }
17486
+ throw new Error(`Unable to locate pricing data JSONL at any known path: ${candidates.join(", ")}`);
17487
+ }
17488
+ function getPackageRoot() {
17489
+ try {
17490
+ const require2 = createRequire(import.meta.url || "file://");
17491
+ const packageJsonPath = require2.resolve("@mastra/observability/package.json");
17492
+ return path.dirname(packageJsonPath);
17493
+ } catch {
17494
+ return process.cwd();
17495
+ }
17496
+ }
17497
+ function makePricingKey(args) {
17498
+ return `${normalizeKeyPart(args.provider)}::${normalizeKeyPart(args.model)}`;
17499
+ }
17500
+ function normalizeKeyPart(value) {
17501
+ return value.trim().toLowerCase();
17502
+ }
17503
+
17504
+ // src/metrics/types.ts
17505
+ var PricingMeter = {
17506
+ INPUT_TOKENS: "input_tokens",
17507
+ INPUT_AUDIO_TOKENS: "input_audio_tokens",
17508
+ INPUT_CACHE_READ_TOKENS: "input_cache_read_tokens",
17509
+ INPUT_CACHE_WRITE_TOKENS: "input_cache_write_tokens",
17510
+ INPUT_IMAGE_TOKENS: "input_image_tokens",
17511
+ OUTPUT_TOKENS: "output_tokens",
17512
+ OUTPUT_AUDIO_TOKENS: "output_audio_tokens",
17513
+ OUTPUT_IMAGE_TOKENS: "output_image_tokens",
17514
+ OUTPUT_REASONING_TOKENS: "output_reasoning_tokens"
17515
+ };
17516
+ var TokenMetrics = {
17517
+ TOTAL_INPUT: "mastra_model_total_input_tokens",
17518
+ TOTAL_OUTPUT: "mastra_model_total_output_tokens",
17519
+ INPUT_TEXT: "mastra_model_input_text_tokens",
17520
+ INPUT_CACHE_READ: "mastra_model_input_cache_read_tokens",
17521
+ INPUT_CACHE_WRITE: "mastra_model_input_cache_write_tokens",
17522
+ INPUT_AUDIO: "mastra_model_input_audio_tokens",
17523
+ INPUT_IMAGE: "mastra_model_input_image_tokens",
17524
+ OUTPUT_TEXT: "mastra_model_output_text_tokens",
17525
+ OUTPUT_REASONING: "mastra_model_output_reasoning_tokens",
17526
+ OUTPUT_AUDIO: "mastra_model_output_audio_tokens",
17527
+ OUTPUT_IMAGE: "mastra_model_output_image_tokens"
17528
+ };
17529
+
17530
+ // src/metrics/usage-metrics.ts
17531
+ function getTokenMetricSamples(usage) {
17532
+ const samples = [];
17533
+ const pushIfDefined = (name, value) => {
17534
+ if (value != null) {
17535
+ samples.push({ name, value });
17536
+ }
17537
+ };
17538
+ const pushIfPositive = (name, value) => {
17539
+ if (value != null && value > 0) {
17540
+ samples.push({ name, value });
17541
+ }
17542
+ };
17543
+ pushIfDefined(TokenMetrics.TOTAL_INPUT, usage.inputTokens);
17544
+ pushIfDefined(TokenMetrics.TOTAL_OUTPUT, usage.outputTokens);
17545
+ if (usage.inputDetails) {
17546
+ pushIfPositive(TokenMetrics.INPUT_TEXT, usage.inputDetails.text);
17547
+ pushIfPositive(TokenMetrics.INPUT_CACHE_READ, usage.inputDetails.cacheRead);
17548
+ pushIfPositive(TokenMetrics.INPUT_CACHE_WRITE, usage.inputDetails.cacheWrite);
17549
+ pushIfPositive(TokenMetrics.INPUT_AUDIO, usage.inputDetails.audio);
17550
+ pushIfPositive(TokenMetrics.INPUT_IMAGE, usage.inputDetails.image);
17551
+ }
17552
+ if (usage.outputDetails) {
17553
+ pushIfPositive(TokenMetrics.OUTPUT_TEXT, usage.outputDetails.text);
17554
+ pushIfPositive(TokenMetrics.OUTPUT_REASONING, usage.outputDetails.reasoning);
17555
+ pushIfPositive(TokenMetrics.OUTPUT_AUDIO, usage.outputDetails.audio);
17556
+ pushIfPositive(TokenMetrics.OUTPUT_IMAGE, usage.outputDetails.image);
17557
+ }
17558
+ return samples;
17559
+ }
17560
+
17561
+ // src/metrics/estimator.ts
17562
+ function estimateCosts(args, pricingRegistry = PricingRegistry.getGlobal()) {
17563
+ const { provider, model, usage } = args;
17564
+ const results = /* @__PURE__ */ new Map();
17565
+ const pricingModel = pricingRegistry?.get({ provider, model });
17566
+ if (!pricingModel) {
17567
+ const errorContext = { costMetadata: { error: "no_matching_model" }, provider, model };
17568
+ applyErrorContextForUsage(results, usage, errorContext);
17569
+ return results;
17570
+ }
17571
+ const costMetadata = { pricing_id: pricingModel.id };
17572
+ const pricingTier = pricingModel.getPricingTierForUsage(usage);
17573
+ if (!pricingTier) {
17574
+ const errorContext = { costMetadata: { ...costMetadata, error: "no_matching_tier" }, provider, model };
17575
+ applyErrorContextForUsage(results, usage, errorContext);
17576
+ return results;
17577
+ }
17578
+ costMetadata["tier_index"] = pricingTier.index;
17579
+ const estimateFields = {
17580
+ pricingModel,
17581
+ pricingTier,
17582
+ costMetadata
17583
+ };
17584
+ const inputDetailResults = [];
17585
+ if (usage.inputDetails?.audio) {
17586
+ const result = estimateCostForMeter({
17587
+ meter: PricingMeter.INPUT_AUDIO_TOKENS,
17588
+ tokenCount: usage.inputDetails.audio,
17589
+ ...estimateFields
17590
+ });
17591
+ results.set(TokenMetrics.INPUT_AUDIO, result.costContext);
17592
+ inputDetailResults.push(result);
17593
+ }
17594
+ if (usage.inputDetails?.cacheRead) {
17595
+ const result = estimateCostForMeter({
17596
+ meter: PricingMeter.INPUT_CACHE_READ_TOKENS,
17597
+ tokenCount: usage.inputDetails.cacheRead,
17598
+ ...estimateFields
17599
+ });
17600
+ results.set(TokenMetrics.INPUT_CACHE_READ, result.costContext);
17601
+ inputDetailResults.push(result);
17602
+ }
17603
+ if (usage.inputDetails?.cacheWrite) {
17604
+ const result = estimateCostForMeter({
17605
+ meter: PricingMeter.INPUT_CACHE_WRITE_TOKENS,
17606
+ tokenCount: usage.inputDetails.cacheWrite,
17607
+ ...estimateFields
17608
+ });
17609
+ results.set(TokenMetrics.INPUT_CACHE_WRITE, result.costContext);
17610
+ inputDetailResults.push(result);
17611
+ }
17612
+ if (usage.inputDetails?.image) {
17613
+ const result = estimateCostForMeter({
17614
+ meter: PricingMeter.INPUT_IMAGE_TOKENS,
17615
+ tokenCount: usage.inputDetails.image,
17616
+ ...estimateFields
17617
+ });
17618
+ results.set(TokenMetrics.INPUT_IMAGE, result.costContext);
17619
+ inputDetailResults.push(result);
17620
+ }
17621
+ if (usage.inputDetails?.text) {
17622
+ const result = estimateCostForMeter({
17623
+ meter: PricingMeter.INPUT_TOKENS,
17624
+ tokenCount: usage.inputDetails.text,
17625
+ ...estimateFields
17626
+ });
17627
+ results.set(TokenMetrics.INPUT_TEXT, result.costContext);
17628
+ inputDetailResults.push(result);
17629
+ }
17630
+ setAggregateCostContext({
17631
+ results,
17632
+ totalMetric: TokenMetrics.TOTAL_INPUT,
17633
+ fallbackMeter: PricingMeter.INPUT_TOKENS,
17634
+ totalTokenCount: usage.inputTokens,
17635
+ detailResults: inputDetailResults,
17636
+ ...estimateFields
17637
+ });
17638
+ const outputDetailResults = [];
17639
+ if (usage.outputDetails?.audio) {
17640
+ const result = estimateCostForMeter({
17641
+ meter: PricingMeter.OUTPUT_AUDIO_TOKENS,
17642
+ tokenCount: usage.outputDetails.audio,
17643
+ ...estimateFields
17644
+ });
17645
+ results.set(TokenMetrics.OUTPUT_AUDIO, result.costContext);
17646
+ outputDetailResults.push(result);
17647
+ }
17648
+ if (usage.outputDetails?.image) {
17649
+ const result = estimateCostForMeter({
17650
+ meter: PricingMeter.OUTPUT_IMAGE_TOKENS,
17651
+ tokenCount: usage.outputDetails.image,
17652
+ ...estimateFields
17653
+ });
17654
+ results.set(TokenMetrics.OUTPUT_IMAGE, result.costContext);
17655
+ outputDetailResults.push(result);
17656
+ }
17657
+ if (usage.outputDetails?.reasoning) {
17658
+ const result = estimateCostForMeter({
17659
+ meter: PricingMeter.OUTPUT_REASONING_TOKENS,
17660
+ tokenCount: usage.outputDetails.reasoning,
17661
+ ...estimateFields
17662
+ });
17663
+ results.set(TokenMetrics.OUTPUT_REASONING, result.costContext);
17664
+ outputDetailResults.push(result);
17665
+ }
17666
+ if (usage.outputDetails?.text) {
17667
+ const result = estimateCostForMeter({
17668
+ meter: PricingMeter.OUTPUT_TOKENS,
17669
+ tokenCount: usage.outputDetails.text,
17670
+ ...estimateFields
17671
+ });
17672
+ results.set(TokenMetrics.OUTPUT_TEXT, result.costContext);
17673
+ outputDetailResults.push(result);
17674
+ }
17675
+ setAggregateCostContext({
17676
+ results,
17677
+ totalMetric: TokenMetrics.TOTAL_OUTPUT,
17678
+ fallbackMeter: PricingMeter.OUTPUT_TOKENS,
17679
+ totalTokenCount: usage.outputTokens,
17680
+ detailResults: outputDetailResults,
17681
+ ...estimateFields
17682
+ });
17683
+ return results;
17684
+ }
17685
+ function applyErrorContextForUsage(results, usage, errorContext) {
17686
+ for (const sample of getTokenMetricSamples(usage)) {
17687
+ results.set(sample.name, errorContext);
17688
+ }
17689
+ }
17690
+ function setAggregateCostContext(args) {
17691
+ const {
17692
+ results,
17693
+ totalMetric,
17694
+ fallbackMeter,
17695
+ totalTokenCount,
17696
+ detailResults,
17697
+ pricingModel,
17698
+ pricingTier,
17699
+ costMetadata
17700
+ } = args;
17701
+ if (totalTokenCount == null) {
17702
+ return;
17703
+ }
17704
+ const successfulDetailCosts = detailResults.filter((result) => result.success).map((result) => result.costContext.estimatedCost).filter((value) => typeof value === "number");
17705
+ if (successfulDetailCosts.length > 0) {
17706
+ const hasFailedDetailCost = detailResults.some((result) => !result.success);
17707
+ results.set(totalMetric, {
17708
+ provider: pricingModel.provider,
17709
+ model: pricingModel.model,
17710
+ estimatedCost: successfulDetailCosts.reduce((sum, value) => sum + value, 0),
17711
+ costUnit: pricingModel.currency,
17712
+ costMetadata: hasFailedDetailCost ? { ...costMetadata, error: "partial_cost" } : { ...costMetadata }
17713
+ });
17714
+ return;
17715
+ }
17716
+ const fallbackResult = estimateCostForMeter({
17717
+ meter: fallbackMeter,
17718
+ tokenCount: totalTokenCount,
17719
+ pricingModel,
17720
+ pricingTier,
17721
+ costMetadata
17722
+ });
17723
+ results.set(totalMetric, fallbackResult.costContext);
17724
+ }
17725
+ function estimateCostForMeter(args) {
17726
+ const { pricingModel, pricingTier, meter, tokenCount, costMetadata } = args;
17727
+ const costContext = {
17728
+ provider: pricingModel.provider,
17729
+ model: pricingModel.model
17730
+ };
17731
+ const pricePerUnit = pricingTier.rates[meter];
17732
+ if (typeof pricePerUnit !== "number") {
17733
+ return {
17734
+ success: false,
17735
+ costContext: {
17736
+ ...costContext,
17737
+ costMetadata: { ...costMetadata, error: "no_pricing_for_usage_type" }
17738
+ }
17739
+ };
17740
+ }
17741
+ return {
17742
+ success: true,
17743
+ costContext: {
17744
+ ...costContext,
17745
+ estimatedCost: tokenCount * pricePerUnit,
17746
+ costUnit: pricingModel.currency,
17747
+ costMetadata: { ...costMetadata }
17748
+ }
17749
+ };
17750
+ }
17751
+
17752
+ // src/metrics/auto-extract.ts
17753
+ function emitDurationMetrics(span, metrics) {
17754
+ const durationMetricName = getDurationMetricName(span);
17755
+ if (!durationMetricName || !span.startTime || !span.endTime) {
17756
+ return;
17757
+ }
17758
+ const durationMs = span.endTime.getTime() - span.startTime.getTime();
17759
+ metrics.emit(durationMetricName, durationMs, {
17760
+ status: span.errorInfo ? "error" : "ok"
17761
+ });
17762
+ }
17763
+ function emitTokenMetrics(span, metrics) {
17764
+ if (span.type !== SpanType.MODEL_GENERATION) {
17765
+ return;
17766
+ }
17767
+ const attrs = span.attributes;
17768
+ if (!attrs?.usage) {
17769
+ return;
17770
+ }
17771
+ emitUsageMetrics(attrs, attrs.usage, metrics);
17772
+ }
17773
+ function emitAutoExtractedMetrics(span, metrics) {
17774
+ emitDurationMetrics(span, metrics);
17775
+ emitTokenMetrics(span, metrics);
17776
+ }
17777
+ function emitUsageMetrics(attrs, usage, metrics) {
17778
+ let metricCosts = /* @__PURE__ */ new Map();
17779
+ try {
17780
+ const provider = attrs.provider;
17781
+ const model = attrs.responseModel ?? attrs.model;
17782
+ if (provider && model) {
17783
+ metricCosts = estimateCosts({
17784
+ provider,
17785
+ model,
17786
+ usage
17787
+ });
17788
+ }
17789
+ } catch {
17790
+ metricCosts = /* @__PURE__ */ new Map();
17791
+ }
17792
+ const emit = (name, value) => {
17793
+ const costContext = metricCosts.get(name);
17794
+ if (!costContext) {
17795
+ metrics.emit(name, value);
17796
+ return;
17797
+ }
17798
+ metrics.emit(name, value, void 0, { costContext });
17799
+ };
17800
+ for (const sample of getTokenMetricSamples(usage)) {
17801
+ emit(sample.name, sample.value);
17802
+ }
17803
+ }
17804
+ function getDurationMetricName(span) {
17805
+ switch (span.type) {
17806
+ case SpanType.AGENT_RUN:
17807
+ return "mastra_agent_duration_ms";
17808
+ case SpanType.TOOL_CALL:
17809
+ case SpanType.MCP_TOOL_CALL:
17810
+ return "mastra_tool_duration_ms";
17811
+ case SpanType.WORKFLOW_RUN:
17812
+ return "mastra_workflow_duration_ms";
17813
+ case SpanType.MODEL_GENERATION:
17814
+ return "mastra_model_duration_ms";
17815
+ case SpanType.PROCESSOR_RUN:
17816
+ return "mastra_processor_duration_ms";
17817
+ default:
17818
+ return null;
17819
+ }
17820
+ }
17821
+ var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
17822
+ var CardinalityFilter = class {
17823
+ blockedLabels;
17824
+ blockUUIDs;
17825
+ /**
17826
+ * @param config - Optional configuration. When omitted, uses
17827
+ * {@link DEFAULT_BLOCKED_LABELS} and blocks UUID-valued labels.
17828
+ */
17829
+ constructor(config2) {
17830
+ const blocked = config2?.blockedLabels ?? [...DEFAULT_BLOCKED_LABELS];
17831
+ this.blockedLabels = new Set(blocked.map((l) => l.toLowerCase()));
17832
+ this.blockUUIDs = config2?.blockUUIDs ?? true;
17833
+ }
17834
+ /**
17835
+ * Return a copy of `labels` with blocked keys and UUID values removed.
17836
+ *
17837
+ * @param labels - Raw metric labels to filter.
17838
+ * @returns A new object containing only the allowed labels.
17839
+ */
17840
+ filterLabels(labels) {
17841
+ const filtered = {};
17842
+ for (const [key, value] of Object.entries(labels)) {
17843
+ if (this.blockedLabels.has(key.toLowerCase())) {
17844
+ continue;
17845
+ }
17846
+ if (this.blockUUIDs && UUID_REGEX.test(value)) {
17847
+ continue;
17848
+ }
17849
+ filtered[key] = value;
17850
+ }
17851
+ return filtered;
17852
+ }
17853
+ };
17424
17854
 
17425
17855
  // src/usage.ts
17426
17856
  function isDefined(value) {
@@ -17456,7 +17886,6 @@ function extractUsageMetrics(usage, providerMetadata) {
17456
17886
  inputDetails.cacheWrite = anthropic.cacheCreationInputTokens;
17457
17887
  }
17458
17888
  if (isDefined(inputDetails.cacheRead) || isDefined(inputDetails.cacheWrite)) {
17459
- inputDetails.text = usage.inputTokens;
17460
17889
  inputTokens = (usage.inputTokens ?? 0) + (inputDetails.cacheRead ?? 0) + (inputDetails.cacheWrite ?? 0);
17461
17890
  }
17462
17891
  }
@@ -17469,6 +17898,15 @@ function extractUsageMetrics(usage, providerMetadata) {
17469
17898
  outputDetails.reasoning = google.usageMetadata.thoughtsTokenCount;
17470
17899
  }
17471
17900
  }
17901
+ if (isDefined(inputTokens)) {
17902
+ inputDetails.text = Math.max(
17903
+ 0,
17904
+ inputTokens - sumDefinedValues(inputDetails, ["cacheRead", "cacheWrite", "audio", "image"])
17905
+ );
17906
+ }
17907
+ if (isDefined(outputTokens)) {
17908
+ outputDetails.text = Math.max(0, outputTokens - sumDefinedValues(outputDetails, ["reasoning", "audio", "image"]));
17909
+ }
17472
17910
  const result = {
17473
17911
  inputTokens,
17474
17912
  outputTokens
@@ -17481,6 +17919,9 @@ function extractUsageMetrics(usage, providerMetadata) {
17481
17919
  }
17482
17920
  return result;
17483
17921
  }
17922
+ function sumDefinedValues(obj, keys) {
17923
+ return keys.reduce((sum, key) => sum + (obj[key] ?? 0), 0);
17924
+ }
17484
17925
 
17485
17926
  // src/model-tracing.ts
17486
17927
  var ModelSpanTracker = class {
@@ -18187,6 +18628,8 @@ var BaseSpan = class {
18187
18628
  parentSpanId;
18188
18629
  /** Deep clean options for serialization */
18189
18630
  deepCleanOptions;
18631
+ /** Cached canonical correlation context for this live span */
18632
+ correlationContext;
18190
18633
  constructor(options, observabilityInstance) {
18191
18634
  const serializationOptions = observabilityInstance.getConfig().serializationOptions;
18192
18635
  this.deepCleanOptions = mergeSerializationOptions(serializationOptions);
@@ -18268,6 +18711,46 @@ var BaseSpan = class {
18268
18711
  }
18269
18712
  return void 0;
18270
18713
  }
18714
+ /** Build and cache the canonical correlation context for this live span. */
18715
+ getCorrelationContext() {
18716
+ if (this.correlationContext) {
18717
+ return this.correlationContext;
18718
+ }
18719
+ const metadata = this.metadata ?? {};
18720
+ const getMetadataString = (key) => typeof metadata[key] === "string" ? metadata[key] : void 0;
18721
+ const parentSpan = this.getParentSpan(false);
18722
+ let rootSpan = this;
18723
+ while (rootSpan.parent) {
18724
+ rootSpan = rootSpan.parent;
18725
+ }
18726
+ const rootTags = rootSpan.tags?.length ? [...rootSpan.tags] : void 0;
18727
+ this.correlationContext = {
18728
+ traceId: this.traceId,
18729
+ spanId: this.id,
18730
+ tags: rootTags,
18731
+ entityType: this.entityType,
18732
+ entityId: this.entityId,
18733
+ entityName: this.entityName,
18734
+ parentEntityType: parentSpan?.entityType,
18735
+ parentEntityId: parentSpan?.entityId,
18736
+ parentEntityName: parentSpan?.entityName,
18737
+ rootEntityType: rootSpan.entityType,
18738
+ rootEntityId: rootSpan.entityId,
18739
+ rootEntityName: rootSpan.entityName,
18740
+ userId: getMetadataString("userId"),
18741
+ organizationId: getMetadataString("organizationId"),
18742
+ resourceId: getMetadataString("resourceId"),
18743
+ runId: getMetadataString("runId"),
18744
+ sessionId: getMetadataString("sessionId"),
18745
+ threadId: getMetadataString("threadId"),
18746
+ requestId: getMetadataString("requestId"),
18747
+ environment: getMetadataString("environment"),
18748
+ source: getMetadataString("source"),
18749
+ serviceName: getMetadataString("serviceName") ?? this.observabilityInstance.getConfig().serviceName,
18750
+ experimentId: getMetadataString("experimentId")
18751
+ };
18752
+ return this.correlationContext;
18753
+ }
18271
18754
  /** Returns a lightweight span ready for export */
18272
18755
  exportSpan(includeInternalSpans) {
18273
18756
  const hideInput = this.traceState?.hideInput ?? false;
@@ -18522,9 +19005,7 @@ var BaseObservabilityInstance = class extends MastraBase {
18522
19005
  serializationOptions: config2.serializationOptions
18523
19006
  };
18524
19007
  this.cardinalityFilter = new CardinalityFilter(config2.cardinality);
18525
- this.observabilityBus = new ObservabilityBus({
18526
- cardinalityFilter: this.cardinalityFilter
18527
- });
19008
+ this.observabilityBus = new ObservabilityBus();
18528
19009
  for (const exporter of this.exporters) {
18529
19010
  this.observabilityBus.registerExporter(exporter);
18530
19011
  }
@@ -18695,85 +19176,32 @@ var BaseObservabilityInstance = class extends MastraBase {
18695
19176
  // ============================================================================
18696
19177
  // Context-factory bridge methods
18697
19178
  // ============================================================================
18698
- /**
18699
- * Extract entity context labels from a span's entity hierarchy by
18700
- * walking the parent chain.
18701
- *
18702
- * Returns labels for: entity_type/name, parent_type/name, root_type/name.
18703
- * Internal spans are skipped when resolving parent and root entities.
18704
- */
18705
- extractEntityLabels(span) {
18706
- const labels = {};
18707
- if (span.entityType) labels.entity_type = span.entityType;
18708
- if (span.entityName) labels.entity_name = span.entityName;
18709
- let parentSpan = span.parent;
18710
- while (parentSpan && parentSpan.isInternal) {
18711
- parentSpan = parentSpan.parent;
18712
- }
18713
- if (parentSpan?.entityType && parentSpan.entityName) {
18714
- labels.parent_type = parentSpan.entityType;
18715
- labels.parent_name = parentSpan.entityName;
18716
- let rootEntity = parentSpan;
18717
- let current = parentSpan.parent;
18718
- while (current) {
18719
- if (!current.isInternal && current.entityType && current.entityName) {
18720
- rootEntity = current;
18721
- }
18722
- current = current.parent;
18723
- }
18724
- if (rootEntity !== parentSpan) {
18725
- labels.root_type = rootEntity.entityType;
18726
- labels.root_name = rootEntity.entityName;
18727
- }
18728
- }
18729
- return labels;
18730
- }
18731
- /**
18732
- * Resolve tags for a span. Uses the span's own tags if present,
18733
- * otherwise walks to the root span to inherit its tags.
18734
- */
18735
- resolveSpanTags(span) {
18736
- if (span.tags) return span.tags;
18737
- let root = span;
18738
- while (root.parent) {
18739
- root = root.parent;
18740
- }
18741
- return root.tags;
18742
- }
18743
19179
  /**
18744
19180
  * Get a LoggerContext correlated to a span.
18745
19181
  * Called by the context-factory in core (deriveLoggerContext) so that
18746
19182
  * `observabilityContext.loggerVNext` is a real logger instead of no-op.
18747
19183
  */
18748
19184
  getLoggerContext(span) {
18749
- const entityLabels = span ? this.extractEntityLabels(span) : void 0;
18750
- const hasEntityLabels = entityLabels && Object.keys(entityLabels).length > 0;
18751
- const metadata = hasEntityLabels || span?.metadata || this.config.serviceName ? {
18752
- ...hasEntityLabels ? entityLabels : void 0,
18753
- ...span?.metadata,
18754
- ...this.config.serviceName ? { serviceName: this.config.serviceName } : void 0
18755
- } : void 0;
19185
+ const correlationContext = span?.getCorrelationContext?.();
19186
+ const metadata = span?.metadata ? structuredClone(span.metadata) : void 0;
18756
19187
  return new LoggerContextImpl({
18757
- traceId: span?.traceId,
18758
- spanId: span?.id,
18759
- tags: span ? this.resolveSpanTags(span) : void 0,
19188
+ correlationContext,
18760
19189
  metadata,
18761
19190
  observabilityBus: this.observabilityBus
18762
19191
  });
18763
19192
  }
18764
19193
  /**
18765
- * Get a MetricsContext, optionally tagged from a span's entity info.
19194
+ * Get a MetricsContext correlated to a span.
18766
19195
  * Called by the context-factory in core (deriveMetricsContext) so that
18767
19196
  * `observabilityContext.metrics` is a real metrics context instead of no-op.
18768
19197
  */
18769
19198
  getMetricsContext(span) {
18770
- const labels = span ? this.extractEntityLabels(span) : {};
18771
- const attrs = span?.attributes;
18772
- if (attrs?.model && typeof attrs.model === "string") labels.model = attrs.model;
18773
- if (attrs?.provider && typeof attrs.provider === "string") labels.provider = attrs.provider;
18774
- if (this.config.serviceName) labels.service_name = this.config.serviceName;
19199
+ const correlationContext = span?.getCorrelationContext?.();
19200
+ const metadata = span?.metadata ? structuredClone(span.metadata) : void 0;
18775
19201
  return new MetricsContextImpl({
18776
- labels: Object.keys(labels).length > 0 ? labels : void 0,
19202
+ correlationContext,
19203
+ metadata,
19204
+ cardinalityFilter: this.cardinalityFilter,
18777
19205
  observabilityBus: this.observabilityBus
18778
19206
  });
18779
19207
  }
@@ -18930,8 +19358,7 @@ var BaseObservabilityInstance = class extends MastraBase {
18930
19358
  }
18931
19359
  /**
18932
19360
  * Emit a span started event.
18933
- * Routes through the ObservabilityBus so exporters receive it via onTracingEvent
18934
- * and auto-extracted metrics are generated.
19361
+ * Routes through the ObservabilityBus so exporters receive it via onTracingEvent.
18935
19362
  */
18936
19363
  emitSpanStarted(span) {
18937
19364
  const exportedSpan = this.getSpanForExport(span);
@@ -18942,20 +19369,24 @@ var BaseObservabilityInstance = class extends MastraBase {
18942
19369
  }
18943
19370
  /**
18944
19371
  * Emit a span ended event (called automatically when spans end).
18945
- * Routes through the ObservabilityBus so exporters receive it via onTracingEvent
18946
- * and auto-extracted metrics are generated.
19372
+ * Emits any auto-extracted metrics while the live span tree is still available,
19373
+ * then routes the exported tracing event through the ObservabilityBus.
18947
19374
  */
18948
19375
  emitSpanEnded(span) {
18949
19376
  const exportedSpan = this.getSpanForExport(span);
18950
19377
  if (exportedSpan) {
19378
+ try {
19379
+ emitAutoExtractedMetrics(span, this.getMetricsContext(span));
19380
+ } catch (err) {
19381
+ this.logger.error("[Observability] Auto-extraction error:", err);
19382
+ }
18951
19383
  const event = { type: TracingEventType.SPAN_ENDED, exportedSpan };
18952
19384
  this.emitTracingEvent(event);
18953
19385
  }
18954
19386
  }
18955
19387
  /**
18956
19388
  * Emit a span updated event.
18957
- * Routes through the ObservabilityBus so exporters receive it via onTracingEvent
18958
- * and auto-extracted metrics are generated.
19389
+ * Routes through the ObservabilityBus so exporters receive it via onTracingEvent.
18959
19390
  */
18960
19391
  emitSpanUpdated(span) {
18961
19392
  const exportedSpan = this.getSpanForExport(span);
@@ -18968,8 +19399,7 @@ var BaseObservabilityInstance = class extends MastraBase {
18968
19399
  * Emit a tracing event through the bus.
18969
19400
  *
18970
19401
  * The bus routes the event to each registered exporter's and bridge's
18971
- * onTracingEvent handler and triggers auto-extracted metrics (e.g.,
18972
- * mastra_agent_duration_ms, mastra_model_duration_ms).
19402
+ * onTracingEvent handler.
18973
19403
  */
18974
19404
  emitTracingEvent(event) {
18975
19405
  this.observabilityBus.emit(event);
@@ -19442,6 +19872,6 @@ function buildTracingOptions(...updaters) {
19442
19872
  return updaters.reduce((opts, updater) => updater(opts), {});
19443
19873
  }
19444
19874
 
19445
- export { AutoExtractedMetrics, BaseExporter, BaseObservabilityEventBus, BaseObservabilityInstance, BaseSpan, CardinalityFilter, CloudExporter, ConsoleExporter, DEFAULT_DEEP_CLEAN_OPTIONS, DEFAULT_KEYS_TO_STRIP, DefaultExporter, DefaultObservabilityInstance, DefaultSpan, JsonExporter, LoggerContextImpl, MetricsContextImpl, ModelSpanTracker, NoOpSpan, Observability, ObservabilityBus, SamplingStrategyType, SensitiveDataFilter, TestExporter, TraceData, TrackingExporter, buildTracingOptions, chainFormatters, deepClean, getExternalParentId, mergeSerializationOptions, observabilityConfigValueSchema, observabilityInstanceConfigSchema, observabilityRegistryConfigSchema, routeToHandler, samplingStrategySchema, serializationOptionsSchema, truncateString };
19875
+ export { BaseExporter, BaseObservabilityEventBus, BaseObservabilityInstance, BaseSpan, CardinalityFilter, CloudExporter, ConsoleExporter, DEFAULT_DEEP_CLEAN_OPTIONS, DEFAULT_KEYS_TO_STRIP, DefaultExporter, DefaultObservabilityInstance, DefaultSpan, JsonExporter, LoggerContextImpl, MetricsContextImpl, ModelSpanTracker, NoOpSpan, Observability, ObservabilityBus, SamplingStrategyType, SensitiveDataFilter, TestExporter, TraceData, TrackingExporter, buildTracingOptions, chainFormatters, deepClean, getExternalParentId, mergeSerializationOptions, observabilityConfigValueSchema, observabilityInstanceConfigSchema, observabilityRegistryConfigSchema, routeToHandler, samplingStrategySchema, serializationOptionsSchema, truncateString };
19446
19876
  //# sourceMappingURL=index.js.map
19447
19877
  //# sourceMappingURL=index.js.map