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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -14,5 +14,6 @@ export * from './spans/index.js';
14
14
  export * from './exporters/index.js';
15
15
  export * from './span_processors/index.js';
16
16
  export * from './model-tracing.js';
17
+ export * from './features.js';
17
18
  export * from './tracing-options.js';
18
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C,cAAc,UAAU,CAAC;AAGzB,cAAc,OAAO,CAAC;AACtB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AAExB,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAGhC,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C,cAAc,UAAU,CAAC;AAGzB,cAAc,OAAO,CAAC;AACtB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AAExB,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAIhC,cAAc,YAAY,CAAC;AAG3B,cAAc,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import fs from 'fs';
8
8
  import { createRequire } from 'module';
9
9
  import path from 'path';
10
10
  import { TransformStream } from 'stream/web';
11
+ import { coreFeatures } from '@mastra/core/features';
11
12
 
12
13
  var __defProp = Object.defineProperty;
13
14
  var __export = (target, all) => {
@@ -37,6 +38,23 @@ function routeToHandler(handler, event, logger) {
37
38
  if (handler.onScoreEvent) {
38
39
  return catchAsyncResult(handler.onScoreEvent(event), handler.name, "score", logger);
39
40
  }
41
+ if (handler.addScoreToTrace) {
42
+ const score = event.score;
43
+ if (!score.traceId) break;
44
+ return catchAsyncResult(
45
+ handler.addScoreToTrace({
46
+ traceId: score.traceId,
47
+ ...score.spanId ? { spanId: score.spanId } : {},
48
+ score: score.score,
49
+ ...score.reason ? { reason: score.reason } : {},
50
+ scorerName: score.scorerName ?? score.scorerId,
51
+ ...score.metadata ? { metadata: score.metadata } : {}
52
+ }),
53
+ handler.name,
54
+ "score",
55
+ logger
56
+ );
57
+ }
40
58
  break;
41
59
  case "feedback":
42
60
  if (handler.onFeedbackEvent) {
@@ -48,6 +66,15 @@ function routeToHandler(handler, event, logger) {
48
66
  logger.error(`[Observability] Handler error [handler=${handler.name}]:`, err);
49
67
  }
50
68
  }
69
+ function routeDropToHandler(handler, event, logger) {
70
+ try {
71
+ if (handler.onDroppedEvent) {
72
+ return catchAsyncResult(handler.onDroppedEvent(event), handler.name, "drop", logger);
73
+ }
74
+ } catch (err) {
75
+ logger.error(`[Observability] Handler error [handler=${handler.name}]:`, err);
76
+ }
77
+ }
51
78
  function catchAsyncResult(result, handlerName, signal, logger) {
52
79
  if (result && typeof result.then === "function") {
53
80
  return result.catch((err) => {
@@ -15678,31 +15705,39 @@ var EventBuffer = class {
15678
15705
  break;
15679
15706
  }
15680
15707
  }
15681
- /** Re-add failed create events to the buffer, dropping those that exceed max retries. */
15708
+ /** Re-add failed create events to the buffer, returning events that exceed max retries. */
15682
15709
  reAddCreates(events) {
15683
15710
  const retryable = [];
15711
+ const dropped = [];
15684
15712
  for (const e of events) {
15685
15713
  if (++e.retryCount <= this.#maxRetries) {
15686
15714
  retryable.push(e);
15715
+ } else {
15716
+ dropped.push(e);
15687
15717
  }
15688
15718
  }
15689
15719
  if (retryable.length > 0) {
15690
15720
  this.setFirstEventTime();
15691
15721
  this.#creates.push(...retryable);
15692
15722
  }
15723
+ return dropped;
15693
15724
  }
15694
- /** Re-add failed update events to the buffer, dropping those that exceed max retries. */
15725
+ /** Re-add failed update events to the buffer, returning events that exceed max retries. */
15695
15726
  reAddUpdates(events) {
15696
15727
  const retryable = [];
15728
+ const dropped = [];
15697
15729
  for (const e of events) {
15698
15730
  if (++e.retryCount <= this.#maxRetries) {
15699
15731
  retryable.push(e);
15732
+ } else {
15733
+ dropped.push(e);
15700
15734
  }
15701
15735
  }
15702
15736
  if (retryable.length > 0) {
15703
15737
  this.setFirstEventTime();
15704
15738
  this.#updates.push(...retryable);
15705
15739
  }
15740
+ return dropped;
15706
15741
  }
15707
15742
  /** Snapshot of buffered create events. */
15708
15743
  get creates() {
@@ -15781,6 +15816,7 @@ var DefaultExporter = class extends BaseExporter {
15781
15816
  #observabilityStorage;
15782
15817
  #resolvedStrategy;
15783
15818
  #flushTimer;
15819
+ #emitDropEvent;
15784
15820
  // Signals whose storage methods threw "not implemented" — skip on future flushes
15785
15821
  #unsupportedSignals = /* @__PURE__ */ new Set();
15786
15822
  constructor(config2 = {}) {
@@ -15802,6 +15838,7 @@ var DefaultExporter = class extends BaseExporter {
15802
15838
  async init(options) {
15803
15839
  try {
15804
15840
  this.#isInitializing = true;
15841
+ this.#emitDropEvent = options.emitDropEvent;
15805
15842
  this.#storage = options.mastra?.getStorage();
15806
15843
  if (!this.#storage) {
15807
15844
  this.logger.warn("DefaultExporter disabled: Storage not available. Traces will not be persisted.");
@@ -15887,21 +15924,54 @@ var DefaultExporter = class extends BaseExporter {
15887
15924
  this.scheduleFlush();
15888
15925
  }
15889
15926
  }
15927
+ sanitizeDropError(error48) {
15928
+ if (error48 instanceof MastraError) {
15929
+ return {
15930
+ id: error48.id,
15931
+ domain: String(error48.domain),
15932
+ message: error48.message
15933
+ };
15934
+ }
15935
+ if (error48 instanceof Error) {
15936
+ return { message: error48.message };
15937
+ }
15938
+ return { message: String(error48) };
15939
+ }
15940
+ emitDrop(signal, reason, count, error48) {
15941
+ if (count === 0) return;
15942
+ const dropEvent = {
15943
+ type: "drop",
15944
+ signal,
15945
+ reason,
15946
+ count,
15947
+ timestamp: /* @__PURE__ */ new Date(),
15948
+ exporterName: this.name,
15949
+ ...this.#observabilityStorage ? { storageName: this.#observabilityStorage.constructor.name } : {},
15950
+ ...error48 === void 0 ? {} : { error: this.sanitizeDropError(error48) }
15951
+ };
15952
+ this.#emitDropEvent?.(dropEvent);
15953
+ }
15890
15954
  /**
15891
15955
  * Flush a batch of create events for a single signal type.
15892
15956
  * On "not implemented" errors, disables the signal for future flushes.
15893
15957
  * On other errors, re-adds events to the buffer for retry.
15894
15958
  */
15895
15959
  async flushCreates(signal, events, storageCall) {
15896
- if (this.#unsupportedSignals.has(signal) || events.length === 0) return;
15960
+ if (events.length === 0) return;
15961
+ if (this.#unsupportedSignals.has(signal)) {
15962
+ this.emitDrop(signal, "unsupported-storage", events.length);
15963
+ return;
15964
+ }
15897
15965
  try {
15898
15966
  await storageCall(events);
15899
15967
  } catch (error48) {
15900
15968
  if (error48 instanceof MastraError && error48.domain === ErrorDomain.MASTRA_OBSERVABILITY && error48.id.endsWith("_NOT_IMPLEMENTED")) {
15901
15969
  this.logger.warn(error48.message);
15902
15970
  this.#unsupportedSignals.add(signal);
15971
+ this.emitDrop(signal, "unsupported-storage", events.length, error48);
15903
15972
  } else {
15904
- this.#eventBuffer.reAddCreates(events);
15973
+ const dropped = this.#eventBuffer.reAddCreates(events);
15974
+ this.emitDrop(signal, "retry-exhausted", dropped.length, error48);
15905
15975
  }
15906
15976
  }
15907
15977
  }
@@ -15910,7 +15980,12 @@ var DefaultExporter = class extends BaseExporter {
15910
15980
  * When `isEnd` is true, successfully flushed spans are removed from tracking.
15911
15981
  */
15912
15982
  async flushSpanUpdates(events, deferredUpdates, isEnd) {
15913
- if (this.#unsupportedSignals.has("tracing") || events.length === 0) return;
15983
+ const deferredCountAtEntry = deferredUpdates.length;
15984
+ if (events.length === 0) return;
15985
+ if (this.#unsupportedSignals.has("tracing")) {
15986
+ this.emitDrop("tracing", "unsupported-storage", events.length);
15987
+ return;
15988
+ }
15914
15989
  const partials = [];
15915
15990
  for (const event of events) {
15916
15991
  const span = event.exportedSpan;
@@ -15934,9 +16009,12 @@ var DefaultExporter = class extends BaseExporter {
15934
16009
  if (error48 instanceof MastraError && error48.domain === ErrorDomain.MASTRA_OBSERVABILITY && error48.id.endsWith("_NOT_IMPLEMENTED")) {
15935
16010
  this.logger.warn(error48.message);
15936
16011
  this.#unsupportedSignals.add("tracing");
16012
+ deferredUpdates.length = 0;
16013
+ this.emitDrop("tracing", "unsupported-storage", events.length + deferredCountAtEntry, error48);
15937
16014
  } else {
15938
16015
  deferredUpdates.length = 0;
15939
- this.#eventBuffer.reAddUpdates(events);
16016
+ const dropped = this.#eventBuffer.reAddUpdates(events);
16017
+ this.emitDrop("tracing", "retry-exhausted", dropped.length, error48);
15940
16018
  }
15941
16019
  }
15942
16020
  }
@@ -16012,17 +16090,17 @@ var DefaultExporter = class extends BaseExporter {
16012
16090
  (events) => this.#observabilityStorage.batchCreateFeedback({ feedbacks: events.map((f) => buildFeedbackRecord(f)) })
16013
16091
  ),
16014
16092
  this.flushCreates(
16015
- "logs",
16093
+ "log",
16016
16094
  createLogEvents,
16017
16095
  (events) => this.#observabilityStorage.batchCreateLogs({ logs: events.map((l) => buildLogRecord(l)) })
16018
16096
  ),
16019
16097
  this.flushCreates(
16020
- "metrics",
16098
+ "metric",
16021
16099
  createMetricEvents,
16022
16100
  (events) => this.#observabilityStorage.batchCreateMetrics({ metrics: events.map((m) => buildMetricRecord(m)) })
16023
16101
  ),
16024
16102
  this.flushCreates(
16025
- "scores",
16103
+ "score",
16026
16104
  createScoreEvents,
16027
16105
  (events) => this.#observabilityStorage.batchCreateScores({ scores: events.map((s) => buildScoreRecord(s)) })
16028
16106
  ),
@@ -16036,7 +16114,13 @@ var DefaultExporter = class extends BaseExporter {
16036
16114
  await this.flushSpanUpdates(updateSpanEvents, deferredUpdates, false);
16037
16115
  await this.flushSpanUpdates(endSpanEvents, deferredUpdates, true);
16038
16116
  if (deferredUpdates.length > 0) {
16039
- this.#eventBuffer.reAddUpdates(deferredUpdates);
16117
+ if (this.#unsupportedSignals.has("tracing")) {
16118
+ this.emitDrop("tracing", "unsupported-storage", deferredUpdates.length);
16119
+ deferredUpdates.length = 0;
16120
+ } else {
16121
+ const dropped = this.#eventBuffer.reAddUpdates(deferredUpdates);
16122
+ this.emitDrop("tracing", "retry-exhausted", dropped.length);
16123
+ }
16040
16124
  }
16041
16125
  const elapsed = Date.now() - startTime;
16042
16126
  this.logger.debug("Batch flushed", {
@@ -17665,6 +17749,8 @@ var ObservabilityBus = class extends BaseObservabilityEventBus {
17665
17749
  bridge;
17666
17750
  /** In-flight handler promises from routeToHandler. Self-cleaning via .finally(). */
17667
17751
  pendingHandlers = /* @__PURE__ */ new Set();
17752
+ handlerBufferFlushDepth = 0;
17753
+ dropEventsEmittedDuringHandlerFlush = 0;
17668
17754
  /** Resolved deepClean options applied to non-tracing events before fan-out. */
17669
17755
  deepCleanOptions;
17670
17756
  constructor(opts) {
@@ -17751,6 +17837,23 @@ var ObservabilityBus = class extends BaseObservabilityEventBus {
17751
17837
  }
17752
17838
  super.emit(cleaned);
17753
17839
  }
17840
+ /**
17841
+ * Emit exporter pipeline drop events to exporters and the bridge.
17842
+ *
17843
+ * Drop events describe exporter health, not user observability data, so they
17844
+ * are intentionally not delivered to generic event-bus subscribers.
17845
+ */
17846
+ emitDropEvent(event) {
17847
+ if (this.handlerBufferFlushDepth > 0) {
17848
+ this.dropEventsEmittedDuringHandlerFlush++;
17849
+ }
17850
+ for (const exporter of this.exporters) {
17851
+ this.trackPromise(routeDropToHandler(exporter, event, this.logger));
17852
+ }
17853
+ if (this.bridge) {
17854
+ this.trackPromise(routeDropToHandler(this.bridge, event, this.logger));
17855
+ }
17856
+ }
17754
17857
  /**
17755
17858
  * Track an async handler promise so flush() can await it.
17756
17859
  * No-ops for sync (void) results.
@@ -17762,19 +17865,8 @@ var ObservabilityBus = class extends BaseObservabilityEventBus {
17762
17865
  void promise2.finally(() => this.pendingHandlers.delete(promise2));
17763
17866
  }
17764
17867
  }
17765
- /**
17766
- * Two-phase flush to ensure all observability data is fully exported.
17767
- *
17768
- * **Phase 1 — Delivery:** Await all in-flight handler promises (exporters,
17769
- * bridge, and base-class subscribers). After this resolves, all event data
17770
- * has been delivered to handler methods.
17771
- *
17772
- * **Phase 2 — Buffer drain:** Call flush() on each exporter and bridge to
17773
- * drain their SDK-internal buffers (e.g., OTEL BatchSpanProcessor, Langfuse
17774
- * client queue). Phases are sequential — Phase 2 must not start until
17775
- * Phase 1 completes, otherwise exporters would flush empty buffers.
17776
- */
17777
- async flush() {
17868
+ /** Await in-flight routed handler promises, draining until empty. */
17869
+ async drainPendingHandlers() {
17778
17870
  let iterations = 0;
17779
17871
  while (this.pendingHandlers.size > 0) {
17780
17872
  await Promise.allSettled([...this.pendingHandlers]);
@@ -17789,14 +17881,54 @@ var ObservabilityBus = class extends BaseObservabilityEventBus {
17789
17881
  break;
17790
17882
  }
17791
17883
  }
17792
- await super.flush();
17793
- const bufferFlushPromises = this.exporters.map((e) => e.flush());
17794
- if (this.bridge) {
17795
- bufferFlushPromises.push(this.bridge.flush());
17884
+ }
17885
+ /** Drain exporter and bridge SDK-internal buffers. */
17886
+ async flushHandlerBuffers() {
17887
+ const initialDropCount = this.dropEventsEmittedDuringHandlerFlush;
17888
+ this.handlerBufferFlushDepth++;
17889
+ try {
17890
+ const bufferFlushPromises = this.exporters.map((e) => e.flush());
17891
+ if (this.bridge) {
17892
+ bufferFlushPromises.push(this.bridge.flush());
17893
+ }
17894
+ if (bufferFlushPromises.length > 0) {
17895
+ await Promise.allSettled(bufferFlushPromises);
17896
+ }
17897
+ return this.dropEventsEmittedDuringHandlerFlush > initialDropCount;
17898
+ } finally {
17899
+ this.handlerBufferFlushDepth--;
17796
17900
  }
17797
- if (bufferFlushPromises.length > 0) {
17798
- await Promise.allSettled(bufferFlushPromises);
17901
+ }
17902
+ /**
17903
+ * Multi-phase flush to ensure all observability data is fully exported.
17904
+ *
17905
+ * **Phase 1 — Delivery:** Await all in-flight handler promises (exporters,
17906
+ * bridge, and base-class subscribers). After this resolves, all event data
17907
+ * has been delivered to handler methods.
17908
+ *
17909
+ * **Phase 2 — Buffer drain:** Call flush() on each exporter and bridge to
17910
+ * drain their SDK-internal buffers (e.g., OTEL BatchSpanProcessor, Langfuse
17911
+ * client queue). Phases are sequential — buffer drains must not start until
17912
+ * delivery completes, otherwise exporters would flush empty buffers.
17913
+ *
17914
+ * Exporter flushes can emit drop events. When that happens, flush loops
17915
+ * through delivery and buffer drain again so alerting integrations that buffer
17916
+ * drop notifications are drained before returning.
17917
+ */
17918
+ async flush() {
17919
+ await this.drainPendingHandlers();
17920
+ await super.flush();
17921
+ for (let iterations = 0; iterations < MAX_FLUSH_ITERATIONS; iterations++) {
17922
+ const emittedDropEvents = await this.flushHandlerBuffers();
17923
+ if (!emittedDropEvents && this.pendingHandlers.size === 0) {
17924
+ return;
17925
+ }
17926
+ await this.drainPendingHandlers();
17927
+ await super.flush();
17799
17928
  }
17929
+ this.logger.error(
17930
+ `[ObservabilityBus] flush() exceeded ${MAX_FLUSH_ITERATIONS} buffer drain iterations. Handlers may be emitting drop events during every flush.`
17931
+ );
17800
17932
  }
17801
17933
  /** Flush all pending events and exporter buffers, then clear subscribers. */
17802
17934
  async shutdown() {
@@ -18155,8 +18287,19 @@ function normalizeProvider(provider) {
18155
18287
  return dotIndex !== -1 ? normalized.substring(0, dotIndex) : normalized;
18156
18288
  }
18157
18289
  function getModelVariants(model) {
18158
- const dashed = model.replace(/\./g, "-");
18159
- return [model, dashed, stripDateSuffix(model), stripDateSuffix(dashed)];
18290
+ const variants = /* @__PURE__ */ new Set();
18291
+ const add = (v) => {
18292
+ variants.add(v);
18293
+ variants.add(stripDateSuffix(v));
18294
+ };
18295
+ add(model);
18296
+ add(model.replace(/\./g, "-"));
18297
+ add(model.replace(/[./]/g, "-"));
18298
+ const slashIndex = model.indexOf("/");
18299
+ if (slashIndex !== -1) {
18300
+ add(model.substring(slashIndex + 1));
18301
+ }
18302
+ return [...variants];
18160
18303
  }
18161
18304
  function stripDateSuffix(model) {
18162
18305
  let stripped = model.replace(/-20\d{2}-\d{2}-\d{2}$/, "");
@@ -18599,6 +18742,9 @@ function sumDefinedValues(obj, keys) {
18599
18742
  }
18600
18743
 
18601
18744
  // src/model-tracing.ts
18745
+ function supportsModelInference() {
18746
+ return coreFeatures.has("model-inference-span");
18747
+ }
18602
18748
  function formatPreviewLabel(label, fallback) {
18603
18749
  return typeof label === "string" && label.length > 0 ? label : fallback;
18604
18750
  }
@@ -18757,6 +18903,7 @@ function extractStepInput(payload) {
18757
18903
  var ModelSpanTracker = class {
18758
18904
  #modelSpan;
18759
18905
  #currentStepSpan;
18906
+ #currentInferenceSpan;
18760
18907
  #currentChunkSpan;
18761
18908
  #currentChunkType;
18762
18909
  #accumulator = {};
@@ -18768,9 +18915,18 @@ var ModelSpanTracker = class {
18768
18915
  #deferStepClose = false;
18769
18916
  /** Stored step-finish payload when defer mode is enabled */
18770
18917
  #pendingStepFinishPayload;
18918
+ /** Static request-side context applied to every MODEL_INFERENCE span */
18919
+ #inferenceContext;
18771
18920
  constructor(modelSpan) {
18772
18921
  this.#modelSpan = modelSpan;
18773
18922
  }
18923
+ /**
18924
+ * Set request-side context applied to subsequent MODEL_INFERENCE spans.
18925
+ * No-op when paired with an older @mastra/core that lacks the feature flag.
18926
+ */
18927
+ setInferenceContext(context) {
18928
+ this.#inferenceContext = context;
18929
+ }
18774
18930
  /**
18775
18931
  * Capture the completion start time (time to first token) when the first content chunk arrives.
18776
18932
  */
@@ -18852,6 +19008,11 @@ var ModelSpanTracker = class {
18852
19008
  * Start a new Model execution step.
18853
19009
  * This should be called at the beginning of LLM execution to capture accurate startTime.
18854
19010
  * The step-start chunk payload can be passed later via updateStep() if needed.
19011
+ *
19012
+ * Note: this only opens MODEL_STEP. The MODEL_INFERENCE child span is opened
19013
+ * separately via startInference() so its duration excludes input processor work.
19014
+ * Callers that don't call startInference() explicitly will get one auto-created
19015
+ * when the first model chunk arrives.
18855
19016
  */
18856
19017
  startStep(payload) {
18857
19018
  if (this.#currentStepSpan) {
@@ -18871,6 +19032,71 @@ var ModelSpanTracker = class {
18871
19032
  this.#currentStepInputIsFinal = Array.isArray(payload?.inputMessages);
18872
19033
  this.#chunkSequence = 0;
18873
19034
  }
19035
+ /**
19036
+ * End the current MODEL_INFERENCE span when the provider stream finishes.
19037
+ * Fields are duplicated onto MODEL_STEP (in #endStepSpan) so existing
19038
+ * integrations that read usage/finishReason from the step span continue
19039
+ * to work unchanged.
19040
+ *
19041
+ * Safe to call multiple times - no-ops if the span is already closed.
19042
+ */
19043
+ #endInferenceSpan(payload) {
19044
+ if (!this.#currentInferenceSpan) return;
19045
+ const { usage: rawUsage, ...otherOutput } = payload.output;
19046
+ const usage = extractUsageMetrics(rawUsage, payload.metadata?.providerMetadata);
19047
+ this.#currentInferenceSpan.end({
19048
+ output: otherOutput,
19049
+ attributes: {
19050
+ usage,
19051
+ finishReason: payload.stepResult.reason,
19052
+ warnings: payload.stepResult.warnings,
19053
+ completionStartTime: this.#completionStartTime
19054
+ }
19055
+ });
19056
+ this.#currentInferenceSpan = void 0;
19057
+ }
19058
+ /**
19059
+ * Open the MODEL_INFERENCE span for the current step. Chunks (including tool-call
19060
+ * chunks emitted by the model) parent under this span so its duration reflects
19061
+ * pure model latency.
19062
+ *
19063
+ * Should be called immediately before invoking the model — after any input
19064
+ * processors / `prepareStep` work has completed — so the span's startTime
19065
+ * does not include processor time. The latest `#inferenceContext` (set via
19066
+ * setInferenceContext) is snapshotted onto the span at creation.
19067
+ *
19068
+ * No-ops when the installed @mastra/core lacks the `model-inference-span`
19069
+ * feature flag, or when called without an active step span. Auto-invoked from
19070
+ * chunk handlers as a safety net; explicit callers get the most accurate
19071
+ * start time.
19072
+ */
19073
+ startInference(payload) {
19074
+ if (!supportsModelInference()) {
19075
+ return;
19076
+ }
19077
+ if (!this.#currentStepSpan || this.#currentInferenceSpan) {
19078
+ return;
19079
+ }
19080
+ const input = extractStepInput(payload);
19081
+ const generationAttrs = this.#modelSpan?.attributes;
19082
+ const ctx = this.#inferenceContext;
19083
+ this.#currentInferenceSpan = this.#currentStepSpan.createChildSpan({
19084
+ name: `inference: ${this.#stepIndex}`,
19085
+ type: SpanType.MODEL_INFERENCE,
19086
+ attributes: {
19087
+ stepIndex: this.#stepIndex,
19088
+ model: generationAttrs?.model,
19089
+ provider: generationAttrs?.provider,
19090
+ streaming: generationAttrs?.streaming,
19091
+ ...ctx?.parameters !== void 0 ? { parameters: ctx.parameters } : {},
19092
+ ...ctx?.providerOptions !== void 0 ? { providerOptions: ctx.providerOptions } : {},
19093
+ ...ctx?.availableTools !== void 0 ? { availableTools: ctx.availableTools } : {},
19094
+ ...ctx?.toolChoice !== void 0 ? { toolChoice: ctx.toolChoice } : {},
19095
+ ...ctx?.responseFormat !== void 0 ? { responseFormat: ctx.responseFormat } : {}
19096
+ },
19097
+ input
19098
+ });
19099
+ }
18874
19100
  /**
18875
19101
  * Update the current step span with additional payload data.
18876
19102
  * Called when step-start chunk arrives with request/warnings info.
@@ -18909,6 +19135,7 @@ var ModelSpanTracker = class {
18909
19135
  delete cleanMetadata[key];
18910
19136
  }
18911
19137
  }
19138
+ this.#endInferenceSpan(payload);
18912
19139
  this.#currentStepSpan.end({
18913
19140
  output: otherOutput,
18914
19141
  attributes: {
@@ -18926,14 +19153,35 @@ var ModelSpanTracker = class {
18926
19153
  this.#stepIndex++;
18927
19154
  }
18928
19155
  /**
18929
- * Create a new chunk span (for multi-part chunks like text-start/delta/end)
19156
+ * Returns the parent span for chunks. Chunks parent under MODEL_INFERENCE
19157
+ * (the provider call) when available, falling back to MODEL_STEP only if
19158
+ * startStep() was bypassed.
18930
19159
  */
18931
- #startChunkSpan(chunkType, initialData) {
18932
- this.#endChunkSpan();
19160
+ #chunkParent() {
19161
+ return this.#currentInferenceSpan ?? this.#currentStepSpan;
19162
+ }
19163
+ /**
19164
+ * Safety-net invoked from chunk handlers: auto-create MODEL_STEP and
19165
+ * MODEL_INFERENCE if a chunk arrives before the loop has explicitly opened
19166
+ * them, so chunks parent under MODEL_INFERENCE rather than falling through
19167
+ * to MODEL_STEP. Idempotent — each public start* method is itself a no-op
19168
+ * when its span is already live.
19169
+ */
19170
+ #ensureStepAndInference() {
18933
19171
  if (!this.#currentStepSpan) {
18934
19172
  this.startStep();
18935
19173
  }
18936
- this.#currentChunkSpan = this.#currentStepSpan?.createChildSpan({
19174
+ if (!this.#currentInferenceSpan) {
19175
+ this.startInference();
19176
+ }
19177
+ }
19178
+ /**
19179
+ * Create a new chunk span (for multi-part chunks like text-start/delta/end)
19180
+ */
19181
+ #startChunkSpan(chunkType, initialData) {
19182
+ this.#endChunkSpan();
19183
+ this.#ensureStepAndInference();
19184
+ this.#currentChunkSpan = this.#chunkParent()?.createChildSpan({
18937
19185
  name: `chunk: '${chunkType}'`,
18938
19186
  type: SpanType.MODEL_CHUNK,
18939
19187
  attributes: {
@@ -18972,10 +19220,8 @@ var ModelSpanTracker = class {
18972
19220
  * Create an event span (for single chunks like tool-call)
18973
19221
  */
18974
19222
  #createEventSpan(chunkType, output, options) {
18975
- if (!this.#currentStepSpan) {
18976
- this.startStep();
18977
- }
18978
- const span = this.#currentStepSpan?.createEventSpan({
19223
+ this.#ensureStepAndInference();
19224
+ const span = this.#chunkParent()?.createEventSpan({
18979
19225
  name: `chunk: '${chunkType}'`,
18980
19226
  type: SpanType.MODEL_CHUNK,
18981
19227
  attributes: {
@@ -19096,10 +19342,8 @@ var ModelSpanTracker = class {
19096
19342
  #handleToolApprovalChunk(chunk) {
19097
19343
  if (chunk.type !== "tool-call-approval") return;
19098
19344
  const payload = chunk.payload;
19099
- if (!this.#currentStepSpan) {
19100
- this.startStep();
19101
- }
19102
- const span = this.#currentStepSpan?.createEventSpan({
19345
+ this.#ensureStepAndInference();
19346
+ const span = this.#chunkParent()?.createEventSpan({
19103
19347
  name: `chunk: 'tool-call-approval'`,
19104
19348
  type: SpanType.MODEL_CHUNK,
19105
19349
  attributes: {
@@ -19157,10 +19401,15 @@ var ModelSpanTracker = class {
19157
19401
  } else {
19158
19402
  this.startStep(chunk.payload);
19159
19403
  }
19404
+ if (!this.#currentInferenceSpan) {
19405
+ this.startInference(chunk.payload);
19406
+ }
19160
19407
  break;
19161
19408
  case "step-finish":
19162
19409
  if (this.#deferStepClose) {
19163
19410
  this.#pendingStepFinishPayload = chunk.payload;
19411
+ this.#endChunkSpan();
19412
+ this.#endInferenceSpan(chunk.payload);
19164
19413
  } else {
19165
19414
  this.#endStepSpan(chunk.payload);
19166
19415
  }
@@ -19256,6 +19505,7 @@ function isSpanInternal(spanType, flags) {
19256
19505
  // Model-related spans
19257
19506
  case SpanType.MODEL_GENERATION:
19258
19507
  case SpanType.MODEL_STEP:
19508
+ case SpanType.MODEL_INFERENCE:
19259
19509
  case SpanType.MODEL_CHUNK:
19260
19510
  return (flags & InternalSpans.MODEL) !== 0;
19261
19511
  // Default: never internal
@@ -20348,6 +20598,7 @@ function buildScoreEvent(args) {
20348
20598
  traceId,
20349
20599
  spanId,
20350
20600
  scorerId: score.scorerId,
20601
+ scorerName: score.scorerName,
20351
20602
  scorerVersion: score.scorerVersion,
20352
20603
  source: score.source,
20353
20604
  scoreSource: score.scoreSource,
@@ -20355,6 +20606,7 @@ function buildScoreEvent(args) {
20355
20606
  reason: score.reason,
20356
20607
  experimentId: score.experimentId,
20357
20608
  scoreTraceId: score.scoreTraceId,
20609
+ targetEntityType: score.targetEntityType,
20358
20610
  correlationContext,
20359
20611
  metadata: mergeMetadata(inheritedMetadata, score.metadata)
20360
20612
  }
@@ -20953,10 +21205,11 @@ var Observability = class extends MastraBase {
20953
21205
  instance.__setMastraEnvironment?.(mastraEnvironment);
20954
21206
  const config2 = instance.getConfig();
20955
21207
  const exporters = instance.getExporters();
21208
+ const emitDropEvent = instance instanceof BaseObservabilityInstance ? (event) => instance.getObservabilityBus().emitDropEvent(event) : void 0;
20956
21209
  exporters.forEach((exporter) => {
20957
21210
  if ("init" in exporter && typeof exporter.init === "function") {
20958
21211
  try {
20959
- exporter.init({ mastra, config: config2 });
21212
+ exporter.init({ mastra, config: config2, emitDropEvent });
20960
21213
  } catch (error48) {
20961
21214
  this.logger?.warn("Failed to initialize observability exporter", {
20962
21215
  exporterName: exporter.name,
@@ -21144,11 +21397,14 @@ var Observability = class extends MastraBase {
21144
21397
  }
21145
21398
  };
21146
21399
 
21400
+ // src/features.ts
21401
+ var observabilityFeatures = /* @__PURE__ */ new Set(["model-inference-span"]);
21402
+
21147
21403
  // src/tracing-options.ts
21148
21404
  function buildTracingOptions(...updaters) {
21149
21405
  return updaters.reduce((opts, updater) => updater(opts), {});
21150
21406
  }
21151
21407
 
21152
- 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, isSerializedMap, mergeSerializationOptions, observabilityConfigValueSchema, observabilityInstanceConfigSchema, observabilityRegistryConfigSchema, reconstructSerializedMap, routeToHandler, samplingStrategySchema, serializationOptionsSchema, truncateString };
21408
+ 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, isSerializedMap, mergeSerializationOptions, observabilityConfigValueSchema, observabilityFeatures, observabilityInstanceConfigSchema, observabilityRegistryConfigSchema, reconstructSerializedMap, routeToHandler, samplingStrategySchema, serializationOptionsSchema, truncateString };
21153
21409
  //# sourceMappingURL=index.js.map
21154
21410
  //# sourceMappingURL=index.js.map