@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.cjs CHANGED
@@ -10,6 +10,7 @@ var fs = require('fs');
10
10
  var module$1 = require('module');
11
11
  var path = require('path');
12
12
  var web = require('stream/web');
13
+ var features = require('@mastra/core/features');
13
14
 
14
15
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
15
16
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -45,6 +46,23 @@ function routeToHandler(handler, event, logger) {
45
46
  if (handler.onScoreEvent) {
46
47
  return catchAsyncResult(handler.onScoreEvent(event), handler.name, "score", logger);
47
48
  }
49
+ if (handler.addScoreToTrace) {
50
+ const score = event.score;
51
+ if (!score.traceId) break;
52
+ return catchAsyncResult(
53
+ handler.addScoreToTrace({
54
+ traceId: score.traceId,
55
+ ...score.spanId ? { spanId: score.spanId } : {},
56
+ score: score.score,
57
+ ...score.reason ? { reason: score.reason } : {},
58
+ scorerName: score.scorerName ?? score.scorerId,
59
+ ...score.metadata ? { metadata: score.metadata } : {}
60
+ }),
61
+ handler.name,
62
+ "score",
63
+ logger
64
+ );
65
+ }
48
66
  break;
49
67
  case "feedback":
50
68
  if (handler.onFeedbackEvent) {
@@ -56,6 +74,15 @@ function routeToHandler(handler, event, logger) {
56
74
  logger.error(`[Observability] Handler error [handler=${handler.name}]:`, err);
57
75
  }
58
76
  }
77
+ function routeDropToHandler(handler, event, logger) {
78
+ try {
79
+ if (handler.onDroppedEvent) {
80
+ return catchAsyncResult(handler.onDroppedEvent(event), handler.name, "drop", logger);
81
+ }
82
+ } catch (err) {
83
+ logger.error(`[Observability] Handler error [handler=${handler.name}]:`, err);
84
+ }
85
+ }
59
86
  function catchAsyncResult(result, handlerName, signal, logger) {
60
87
  if (result && typeof result.then === "function") {
61
88
  return result.catch((err) => {
@@ -15686,31 +15713,39 @@ var EventBuffer = class {
15686
15713
  break;
15687
15714
  }
15688
15715
  }
15689
- /** Re-add failed create events to the buffer, dropping those that exceed max retries. */
15716
+ /** Re-add failed create events to the buffer, returning events that exceed max retries. */
15690
15717
  reAddCreates(events) {
15691
15718
  const retryable = [];
15719
+ const dropped = [];
15692
15720
  for (const e of events) {
15693
15721
  if (++e.retryCount <= this.#maxRetries) {
15694
15722
  retryable.push(e);
15723
+ } else {
15724
+ dropped.push(e);
15695
15725
  }
15696
15726
  }
15697
15727
  if (retryable.length > 0) {
15698
15728
  this.setFirstEventTime();
15699
15729
  this.#creates.push(...retryable);
15700
15730
  }
15731
+ return dropped;
15701
15732
  }
15702
- /** Re-add failed update events to the buffer, dropping those that exceed max retries. */
15733
+ /** Re-add failed update events to the buffer, returning events that exceed max retries. */
15703
15734
  reAddUpdates(events) {
15704
15735
  const retryable = [];
15736
+ const dropped = [];
15705
15737
  for (const e of events) {
15706
15738
  if (++e.retryCount <= this.#maxRetries) {
15707
15739
  retryable.push(e);
15740
+ } else {
15741
+ dropped.push(e);
15708
15742
  }
15709
15743
  }
15710
15744
  if (retryable.length > 0) {
15711
15745
  this.setFirstEventTime();
15712
15746
  this.#updates.push(...retryable);
15713
15747
  }
15748
+ return dropped;
15714
15749
  }
15715
15750
  /** Snapshot of buffered create events. */
15716
15751
  get creates() {
@@ -15789,6 +15824,7 @@ var DefaultExporter = class extends BaseExporter {
15789
15824
  #observabilityStorage;
15790
15825
  #resolvedStrategy;
15791
15826
  #flushTimer;
15827
+ #emitDropEvent;
15792
15828
  // Signals whose storage methods threw "not implemented" — skip on future flushes
15793
15829
  #unsupportedSignals = /* @__PURE__ */ new Set();
15794
15830
  constructor(config2 = {}) {
@@ -15810,6 +15846,7 @@ var DefaultExporter = class extends BaseExporter {
15810
15846
  async init(options) {
15811
15847
  try {
15812
15848
  this.#isInitializing = true;
15849
+ this.#emitDropEvent = options.emitDropEvent;
15813
15850
  this.#storage = options.mastra?.getStorage();
15814
15851
  if (!this.#storage) {
15815
15852
  this.logger.warn("DefaultExporter disabled: Storage not available. Traces will not be persisted.");
@@ -15895,21 +15932,54 @@ var DefaultExporter = class extends BaseExporter {
15895
15932
  this.scheduleFlush();
15896
15933
  }
15897
15934
  }
15935
+ sanitizeDropError(error48) {
15936
+ if (error48 instanceof error$1.MastraError) {
15937
+ return {
15938
+ id: error48.id,
15939
+ domain: String(error48.domain),
15940
+ message: error48.message
15941
+ };
15942
+ }
15943
+ if (error48 instanceof Error) {
15944
+ return { message: error48.message };
15945
+ }
15946
+ return { message: String(error48) };
15947
+ }
15948
+ emitDrop(signal, reason, count, error48) {
15949
+ if (count === 0) return;
15950
+ const dropEvent = {
15951
+ type: "drop",
15952
+ signal,
15953
+ reason,
15954
+ count,
15955
+ timestamp: /* @__PURE__ */ new Date(),
15956
+ exporterName: this.name,
15957
+ ...this.#observabilityStorage ? { storageName: this.#observabilityStorage.constructor.name } : {},
15958
+ ...error48 === void 0 ? {} : { error: this.sanitizeDropError(error48) }
15959
+ };
15960
+ this.#emitDropEvent?.(dropEvent);
15961
+ }
15898
15962
  /**
15899
15963
  * Flush a batch of create events for a single signal type.
15900
15964
  * On "not implemented" errors, disables the signal for future flushes.
15901
15965
  * On other errors, re-adds events to the buffer for retry.
15902
15966
  */
15903
15967
  async flushCreates(signal, events, storageCall) {
15904
- if (this.#unsupportedSignals.has(signal) || events.length === 0) return;
15968
+ if (events.length === 0) return;
15969
+ if (this.#unsupportedSignals.has(signal)) {
15970
+ this.emitDrop(signal, "unsupported-storage", events.length);
15971
+ return;
15972
+ }
15905
15973
  try {
15906
15974
  await storageCall(events);
15907
15975
  } catch (error48) {
15908
15976
  if (error48 instanceof error$1.MastraError && error48.domain === error$1.ErrorDomain.MASTRA_OBSERVABILITY && error48.id.endsWith("_NOT_IMPLEMENTED")) {
15909
15977
  this.logger.warn(error48.message);
15910
15978
  this.#unsupportedSignals.add(signal);
15979
+ this.emitDrop(signal, "unsupported-storage", events.length, error48);
15911
15980
  } else {
15912
- this.#eventBuffer.reAddCreates(events);
15981
+ const dropped = this.#eventBuffer.reAddCreates(events);
15982
+ this.emitDrop(signal, "retry-exhausted", dropped.length, error48);
15913
15983
  }
15914
15984
  }
15915
15985
  }
@@ -15918,7 +15988,12 @@ var DefaultExporter = class extends BaseExporter {
15918
15988
  * When `isEnd` is true, successfully flushed spans are removed from tracking.
15919
15989
  */
15920
15990
  async flushSpanUpdates(events, deferredUpdates, isEnd) {
15921
- if (this.#unsupportedSignals.has("tracing") || events.length === 0) return;
15991
+ const deferredCountAtEntry = deferredUpdates.length;
15992
+ if (events.length === 0) return;
15993
+ if (this.#unsupportedSignals.has("tracing")) {
15994
+ this.emitDrop("tracing", "unsupported-storage", events.length);
15995
+ return;
15996
+ }
15922
15997
  const partials = [];
15923
15998
  for (const event of events) {
15924
15999
  const span = event.exportedSpan;
@@ -15942,9 +16017,12 @@ var DefaultExporter = class extends BaseExporter {
15942
16017
  if (error48 instanceof error$1.MastraError && error48.domain === error$1.ErrorDomain.MASTRA_OBSERVABILITY && error48.id.endsWith("_NOT_IMPLEMENTED")) {
15943
16018
  this.logger.warn(error48.message);
15944
16019
  this.#unsupportedSignals.add("tracing");
16020
+ deferredUpdates.length = 0;
16021
+ this.emitDrop("tracing", "unsupported-storage", events.length + deferredCountAtEntry, error48);
15945
16022
  } else {
15946
16023
  deferredUpdates.length = 0;
15947
- this.#eventBuffer.reAddUpdates(events);
16024
+ const dropped = this.#eventBuffer.reAddUpdates(events);
16025
+ this.emitDrop("tracing", "retry-exhausted", dropped.length, error48);
15948
16026
  }
15949
16027
  }
15950
16028
  }
@@ -16020,17 +16098,17 @@ var DefaultExporter = class extends BaseExporter {
16020
16098
  (events) => this.#observabilityStorage.batchCreateFeedback({ feedbacks: events.map((f) => storage.buildFeedbackRecord(f)) })
16021
16099
  ),
16022
16100
  this.flushCreates(
16023
- "logs",
16101
+ "log",
16024
16102
  createLogEvents,
16025
16103
  (events) => this.#observabilityStorage.batchCreateLogs({ logs: events.map((l) => storage.buildLogRecord(l)) })
16026
16104
  ),
16027
16105
  this.flushCreates(
16028
- "metrics",
16106
+ "metric",
16029
16107
  createMetricEvents,
16030
16108
  (events) => this.#observabilityStorage.batchCreateMetrics({ metrics: events.map((m) => storage.buildMetricRecord(m)) })
16031
16109
  ),
16032
16110
  this.flushCreates(
16033
- "scores",
16111
+ "score",
16034
16112
  createScoreEvents,
16035
16113
  (events) => this.#observabilityStorage.batchCreateScores({ scores: events.map((s) => storage.buildScoreRecord(s)) })
16036
16114
  ),
@@ -16044,7 +16122,13 @@ var DefaultExporter = class extends BaseExporter {
16044
16122
  await this.flushSpanUpdates(updateSpanEvents, deferredUpdates, false);
16045
16123
  await this.flushSpanUpdates(endSpanEvents, deferredUpdates, true);
16046
16124
  if (deferredUpdates.length > 0) {
16047
- this.#eventBuffer.reAddUpdates(deferredUpdates);
16125
+ if (this.#unsupportedSignals.has("tracing")) {
16126
+ this.emitDrop("tracing", "unsupported-storage", deferredUpdates.length);
16127
+ deferredUpdates.length = 0;
16128
+ } else {
16129
+ const dropped = this.#eventBuffer.reAddUpdates(deferredUpdates);
16130
+ this.emitDrop("tracing", "retry-exhausted", dropped.length);
16131
+ }
16048
16132
  }
16049
16133
  const elapsed = Date.now() - startTime;
16050
16134
  this.logger.debug("Batch flushed", {
@@ -17673,6 +17757,8 @@ var ObservabilityBus = class extends BaseObservabilityEventBus {
17673
17757
  bridge;
17674
17758
  /** In-flight handler promises from routeToHandler. Self-cleaning via .finally(). */
17675
17759
  pendingHandlers = /* @__PURE__ */ new Set();
17760
+ handlerBufferFlushDepth = 0;
17761
+ dropEventsEmittedDuringHandlerFlush = 0;
17676
17762
  /** Resolved deepClean options applied to non-tracing events before fan-out. */
17677
17763
  deepCleanOptions;
17678
17764
  constructor(opts) {
@@ -17759,6 +17845,23 @@ var ObservabilityBus = class extends BaseObservabilityEventBus {
17759
17845
  }
17760
17846
  super.emit(cleaned);
17761
17847
  }
17848
+ /**
17849
+ * Emit exporter pipeline drop events to exporters and the bridge.
17850
+ *
17851
+ * Drop events describe exporter health, not user observability data, so they
17852
+ * are intentionally not delivered to generic event-bus subscribers.
17853
+ */
17854
+ emitDropEvent(event) {
17855
+ if (this.handlerBufferFlushDepth > 0) {
17856
+ this.dropEventsEmittedDuringHandlerFlush++;
17857
+ }
17858
+ for (const exporter of this.exporters) {
17859
+ this.trackPromise(routeDropToHandler(exporter, event, this.logger));
17860
+ }
17861
+ if (this.bridge) {
17862
+ this.trackPromise(routeDropToHandler(this.bridge, event, this.logger));
17863
+ }
17864
+ }
17762
17865
  /**
17763
17866
  * Track an async handler promise so flush() can await it.
17764
17867
  * No-ops for sync (void) results.
@@ -17770,19 +17873,8 @@ var ObservabilityBus = class extends BaseObservabilityEventBus {
17770
17873
  void promise2.finally(() => this.pendingHandlers.delete(promise2));
17771
17874
  }
17772
17875
  }
17773
- /**
17774
- * Two-phase flush to ensure all observability data is fully exported.
17775
- *
17776
- * **Phase 1 — Delivery:** Await all in-flight handler promises (exporters,
17777
- * bridge, and base-class subscribers). After this resolves, all event data
17778
- * has been delivered to handler methods.
17779
- *
17780
- * **Phase 2 — Buffer drain:** Call flush() on each exporter and bridge to
17781
- * drain their SDK-internal buffers (e.g., OTEL BatchSpanProcessor, Langfuse
17782
- * client queue). Phases are sequential — Phase 2 must not start until
17783
- * Phase 1 completes, otherwise exporters would flush empty buffers.
17784
- */
17785
- async flush() {
17876
+ /** Await in-flight routed handler promises, draining until empty. */
17877
+ async drainPendingHandlers() {
17786
17878
  let iterations = 0;
17787
17879
  while (this.pendingHandlers.size > 0) {
17788
17880
  await Promise.allSettled([...this.pendingHandlers]);
@@ -17797,14 +17889,54 @@ var ObservabilityBus = class extends BaseObservabilityEventBus {
17797
17889
  break;
17798
17890
  }
17799
17891
  }
17800
- await super.flush();
17801
- const bufferFlushPromises = this.exporters.map((e) => e.flush());
17802
- if (this.bridge) {
17803
- bufferFlushPromises.push(this.bridge.flush());
17892
+ }
17893
+ /** Drain exporter and bridge SDK-internal buffers. */
17894
+ async flushHandlerBuffers() {
17895
+ const initialDropCount = this.dropEventsEmittedDuringHandlerFlush;
17896
+ this.handlerBufferFlushDepth++;
17897
+ try {
17898
+ const bufferFlushPromises = this.exporters.map((e) => e.flush());
17899
+ if (this.bridge) {
17900
+ bufferFlushPromises.push(this.bridge.flush());
17901
+ }
17902
+ if (bufferFlushPromises.length > 0) {
17903
+ await Promise.allSettled(bufferFlushPromises);
17904
+ }
17905
+ return this.dropEventsEmittedDuringHandlerFlush > initialDropCount;
17906
+ } finally {
17907
+ this.handlerBufferFlushDepth--;
17804
17908
  }
17805
- if (bufferFlushPromises.length > 0) {
17806
- await Promise.allSettled(bufferFlushPromises);
17909
+ }
17910
+ /**
17911
+ * Multi-phase flush to ensure all observability data is fully exported.
17912
+ *
17913
+ * **Phase 1 — Delivery:** Await all in-flight handler promises (exporters,
17914
+ * bridge, and base-class subscribers). After this resolves, all event data
17915
+ * has been delivered to handler methods.
17916
+ *
17917
+ * **Phase 2 — Buffer drain:** Call flush() on each exporter and bridge to
17918
+ * drain their SDK-internal buffers (e.g., OTEL BatchSpanProcessor, Langfuse
17919
+ * client queue). Phases are sequential — buffer drains must not start until
17920
+ * delivery completes, otherwise exporters would flush empty buffers.
17921
+ *
17922
+ * Exporter flushes can emit drop events. When that happens, flush loops
17923
+ * through delivery and buffer drain again so alerting integrations that buffer
17924
+ * drop notifications are drained before returning.
17925
+ */
17926
+ async flush() {
17927
+ await this.drainPendingHandlers();
17928
+ await super.flush();
17929
+ for (let iterations = 0; iterations < MAX_FLUSH_ITERATIONS; iterations++) {
17930
+ const emittedDropEvents = await this.flushHandlerBuffers();
17931
+ if (!emittedDropEvents && this.pendingHandlers.size === 0) {
17932
+ return;
17933
+ }
17934
+ await this.drainPendingHandlers();
17935
+ await super.flush();
17807
17936
  }
17937
+ this.logger.error(
17938
+ `[ObservabilityBus] flush() exceeded ${MAX_FLUSH_ITERATIONS} buffer drain iterations. Handlers may be emitting drop events during every flush.`
17939
+ );
17808
17940
  }
17809
17941
  /** Flush all pending events and exporter buffers, then clear subscribers. */
17810
17942
  async shutdown() {
@@ -18163,8 +18295,19 @@ function normalizeProvider(provider) {
18163
18295
  return dotIndex !== -1 ? normalized.substring(0, dotIndex) : normalized;
18164
18296
  }
18165
18297
  function getModelVariants(model) {
18166
- const dashed = model.replace(/\./g, "-");
18167
- return [model, dashed, stripDateSuffix(model), stripDateSuffix(dashed)];
18298
+ const variants = /* @__PURE__ */ new Set();
18299
+ const add = (v) => {
18300
+ variants.add(v);
18301
+ variants.add(stripDateSuffix(v));
18302
+ };
18303
+ add(model);
18304
+ add(model.replace(/\./g, "-"));
18305
+ add(model.replace(/[./]/g, "-"));
18306
+ const slashIndex = model.indexOf("/");
18307
+ if (slashIndex !== -1) {
18308
+ add(model.substring(slashIndex + 1));
18309
+ }
18310
+ return [...variants];
18168
18311
  }
18169
18312
  function stripDateSuffix(model) {
18170
18313
  let stripped = model.replace(/-20\d{2}-\d{2}-\d{2}$/, "");
@@ -18607,6 +18750,9 @@ function sumDefinedValues(obj, keys) {
18607
18750
  }
18608
18751
 
18609
18752
  // src/model-tracing.ts
18753
+ function supportsModelInference() {
18754
+ return features.coreFeatures.has("model-inference-span");
18755
+ }
18610
18756
  function formatPreviewLabel(label, fallback) {
18611
18757
  return typeof label === "string" && label.length > 0 ? label : fallback;
18612
18758
  }
@@ -18765,6 +18911,7 @@ function extractStepInput(payload) {
18765
18911
  var ModelSpanTracker = class {
18766
18912
  #modelSpan;
18767
18913
  #currentStepSpan;
18914
+ #currentInferenceSpan;
18768
18915
  #currentChunkSpan;
18769
18916
  #currentChunkType;
18770
18917
  #accumulator = {};
@@ -18776,9 +18923,18 @@ var ModelSpanTracker = class {
18776
18923
  #deferStepClose = false;
18777
18924
  /** Stored step-finish payload when defer mode is enabled */
18778
18925
  #pendingStepFinishPayload;
18926
+ /** Static request-side context applied to every MODEL_INFERENCE span */
18927
+ #inferenceContext;
18779
18928
  constructor(modelSpan) {
18780
18929
  this.#modelSpan = modelSpan;
18781
18930
  }
18931
+ /**
18932
+ * Set request-side context applied to subsequent MODEL_INFERENCE spans.
18933
+ * No-op when paired with an older @mastra/core that lacks the feature flag.
18934
+ */
18935
+ setInferenceContext(context) {
18936
+ this.#inferenceContext = context;
18937
+ }
18782
18938
  /**
18783
18939
  * Capture the completion start time (time to first token) when the first content chunk arrives.
18784
18940
  */
@@ -18860,6 +19016,11 @@ var ModelSpanTracker = class {
18860
19016
  * Start a new Model execution step.
18861
19017
  * This should be called at the beginning of LLM execution to capture accurate startTime.
18862
19018
  * The step-start chunk payload can be passed later via updateStep() if needed.
19019
+ *
19020
+ * Note: this only opens MODEL_STEP. The MODEL_INFERENCE child span is opened
19021
+ * separately via startInference() so its duration excludes input processor work.
19022
+ * Callers that don't call startInference() explicitly will get one auto-created
19023
+ * when the first model chunk arrives.
18863
19024
  */
18864
19025
  startStep(payload) {
18865
19026
  if (this.#currentStepSpan) {
@@ -18879,6 +19040,71 @@ var ModelSpanTracker = class {
18879
19040
  this.#currentStepInputIsFinal = Array.isArray(payload?.inputMessages);
18880
19041
  this.#chunkSequence = 0;
18881
19042
  }
19043
+ /**
19044
+ * End the current MODEL_INFERENCE span when the provider stream finishes.
19045
+ * Fields are duplicated onto MODEL_STEP (in #endStepSpan) so existing
19046
+ * integrations that read usage/finishReason from the step span continue
19047
+ * to work unchanged.
19048
+ *
19049
+ * Safe to call multiple times - no-ops if the span is already closed.
19050
+ */
19051
+ #endInferenceSpan(payload) {
19052
+ if (!this.#currentInferenceSpan) return;
19053
+ const { usage: rawUsage, ...otherOutput } = payload.output;
19054
+ const usage = extractUsageMetrics(rawUsage, payload.metadata?.providerMetadata);
19055
+ this.#currentInferenceSpan.end({
19056
+ output: otherOutput,
19057
+ attributes: {
19058
+ usage,
19059
+ finishReason: payload.stepResult.reason,
19060
+ warnings: payload.stepResult.warnings,
19061
+ completionStartTime: this.#completionStartTime
19062
+ }
19063
+ });
19064
+ this.#currentInferenceSpan = void 0;
19065
+ }
19066
+ /**
19067
+ * Open the MODEL_INFERENCE span for the current step. Chunks (including tool-call
19068
+ * chunks emitted by the model) parent under this span so its duration reflects
19069
+ * pure model latency.
19070
+ *
19071
+ * Should be called immediately before invoking the model — after any input
19072
+ * processors / `prepareStep` work has completed — so the span's startTime
19073
+ * does not include processor time. The latest `#inferenceContext` (set via
19074
+ * setInferenceContext) is snapshotted onto the span at creation.
19075
+ *
19076
+ * No-ops when the installed @mastra/core lacks the `model-inference-span`
19077
+ * feature flag, or when called without an active step span. Auto-invoked from
19078
+ * chunk handlers as a safety net; explicit callers get the most accurate
19079
+ * start time.
19080
+ */
19081
+ startInference(payload) {
19082
+ if (!supportsModelInference()) {
19083
+ return;
19084
+ }
19085
+ if (!this.#currentStepSpan || this.#currentInferenceSpan) {
19086
+ return;
19087
+ }
19088
+ const input = extractStepInput(payload);
19089
+ const generationAttrs = this.#modelSpan?.attributes;
19090
+ const ctx = this.#inferenceContext;
19091
+ this.#currentInferenceSpan = this.#currentStepSpan.createChildSpan({
19092
+ name: `inference: ${this.#stepIndex}`,
19093
+ type: observability.SpanType.MODEL_INFERENCE,
19094
+ attributes: {
19095
+ stepIndex: this.#stepIndex,
19096
+ model: generationAttrs?.model,
19097
+ provider: generationAttrs?.provider,
19098
+ streaming: generationAttrs?.streaming,
19099
+ ...ctx?.parameters !== void 0 ? { parameters: ctx.parameters } : {},
19100
+ ...ctx?.providerOptions !== void 0 ? { providerOptions: ctx.providerOptions } : {},
19101
+ ...ctx?.availableTools !== void 0 ? { availableTools: ctx.availableTools } : {},
19102
+ ...ctx?.toolChoice !== void 0 ? { toolChoice: ctx.toolChoice } : {},
19103
+ ...ctx?.responseFormat !== void 0 ? { responseFormat: ctx.responseFormat } : {}
19104
+ },
19105
+ input
19106
+ });
19107
+ }
18882
19108
  /**
18883
19109
  * Update the current step span with additional payload data.
18884
19110
  * Called when step-start chunk arrives with request/warnings info.
@@ -18917,6 +19143,7 @@ var ModelSpanTracker = class {
18917
19143
  delete cleanMetadata[key];
18918
19144
  }
18919
19145
  }
19146
+ this.#endInferenceSpan(payload);
18920
19147
  this.#currentStepSpan.end({
18921
19148
  output: otherOutput,
18922
19149
  attributes: {
@@ -18934,14 +19161,35 @@ var ModelSpanTracker = class {
18934
19161
  this.#stepIndex++;
18935
19162
  }
18936
19163
  /**
18937
- * Create a new chunk span (for multi-part chunks like text-start/delta/end)
19164
+ * Returns the parent span for chunks. Chunks parent under MODEL_INFERENCE
19165
+ * (the provider call) when available, falling back to MODEL_STEP only if
19166
+ * startStep() was bypassed.
18938
19167
  */
18939
- #startChunkSpan(chunkType, initialData) {
18940
- this.#endChunkSpan();
19168
+ #chunkParent() {
19169
+ return this.#currentInferenceSpan ?? this.#currentStepSpan;
19170
+ }
19171
+ /**
19172
+ * Safety-net invoked from chunk handlers: auto-create MODEL_STEP and
19173
+ * MODEL_INFERENCE if a chunk arrives before the loop has explicitly opened
19174
+ * them, so chunks parent under MODEL_INFERENCE rather than falling through
19175
+ * to MODEL_STEP. Idempotent — each public start* method is itself a no-op
19176
+ * when its span is already live.
19177
+ */
19178
+ #ensureStepAndInference() {
18941
19179
  if (!this.#currentStepSpan) {
18942
19180
  this.startStep();
18943
19181
  }
18944
- this.#currentChunkSpan = this.#currentStepSpan?.createChildSpan({
19182
+ if (!this.#currentInferenceSpan) {
19183
+ this.startInference();
19184
+ }
19185
+ }
19186
+ /**
19187
+ * Create a new chunk span (for multi-part chunks like text-start/delta/end)
19188
+ */
19189
+ #startChunkSpan(chunkType, initialData) {
19190
+ this.#endChunkSpan();
19191
+ this.#ensureStepAndInference();
19192
+ this.#currentChunkSpan = this.#chunkParent()?.createChildSpan({
18945
19193
  name: `chunk: '${chunkType}'`,
18946
19194
  type: observability.SpanType.MODEL_CHUNK,
18947
19195
  attributes: {
@@ -18980,10 +19228,8 @@ var ModelSpanTracker = class {
18980
19228
  * Create an event span (for single chunks like tool-call)
18981
19229
  */
18982
19230
  #createEventSpan(chunkType, output, options) {
18983
- if (!this.#currentStepSpan) {
18984
- this.startStep();
18985
- }
18986
- const span = this.#currentStepSpan?.createEventSpan({
19231
+ this.#ensureStepAndInference();
19232
+ const span = this.#chunkParent()?.createEventSpan({
18987
19233
  name: `chunk: '${chunkType}'`,
18988
19234
  type: observability.SpanType.MODEL_CHUNK,
18989
19235
  attributes: {
@@ -19104,10 +19350,8 @@ var ModelSpanTracker = class {
19104
19350
  #handleToolApprovalChunk(chunk) {
19105
19351
  if (chunk.type !== "tool-call-approval") return;
19106
19352
  const payload = chunk.payload;
19107
- if (!this.#currentStepSpan) {
19108
- this.startStep();
19109
- }
19110
- const span = this.#currentStepSpan?.createEventSpan({
19353
+ this.#ensureStepAndInference();
19354
+ const span = this.#chunkParent()?.createEventSpan({
19111
19355
  name: `chunk: 'tool-call-approval'`,
19112
19356
  type: observability.SpanType.MODEL_CHUNK,
19113
19357
  attributes: {
@@ -19165,10 +19409,15 @@ var ModelSpanTracker = class {
19165
19409
  } else {
19166
19410
  this.startStep(chunk.payload);
19167
19411
  }
19412
+ if (!this.#currentInferenceSpan) {
19413
+ this.startInference(chunk.payload);
19414
+ }
19168
19415
  break;
19169
19416
  case "step-finish":
19170
19417
  if (this.#deferStepClose) {
19171
19418
  this.#pendingStepFinishPayload = chunk.payload;
19419
+ this.#endChunkSpan();
19420
+ this.#endInferenceSpan(chunk.payload);
19172
19421
  } else {
19173
19422
  this.#endStepSpan(chunk.payload);
19174
19423
  }
@@ -19264,6 +19513,7 @@ function isSpanInternal(spanType, flags) {
19264
19513
  // Model-related spans
19265
19514
  case observability.SpanType.MODEL_GENERATION:
19266
19515
  case observability.SpanType.MODEL_STEP:
19516
+ case observability.SpanType.MODEL_INFERENCE:
19267
19517
  case observability.SpanType.MODEL_CHUNK:
19268
19518
  return (flags & observability.InternalSpans.MODEL) !== 0;
19269
19519
  // Default: never internal
@@ -20356,6 +20606,7 @@ function buildScoreEvent(args) {
20356
20606
  traceId,
20357
20607
  spanId,
20358
20608
  scorerId: score.scorerId,
20609
+ scorerName: score.scorerName,
20359
20610
  scorerVersion: score.scorerVersion,
20360
20611
  source: score.source,
20361
20612
  scoreSource: score.scoreSource,
@@ -20363,6 +20614,7 @@ function buildScoreEvent(args) {
20363
20614
  reason: score.reason,
20364
20615
  experimentId: score.experimentId,
20365
20616
  scoreTraceId: score.scoreTraceId,
20617
+ targetEntityType: score.targetEntityType,
20366
20618
  correlationContext,
20367
20619
  metadata: mergeMetadata(inheritedMetadata, score.metadata)
20368
20620
  }
@@ -20961,10 +21213,11 @@ var Observability = class extends base.MastraBase {
20961
21213
  instance.__setMastraEnvironment?.(mastraEnvironment);
20962
21214
  const config2 = instance.getConfig();
20963
21215
  const exporters = instance.getExporters();
21216
+ const emitDropEvent = instance instanceof BaseObservabilityInstance ? (event) => instance.getObservabilityBus().emitDropEvent(event) : void 0;
20964
21217
  exporters.forEach((exporter) => {
20965
21218
  if ("init" in exporter && typeof exporter.init === "function") {
20966
21219
  try {
20967
- exporter.init({ mastra, config: config2 });
21220
+ exporter.init({ mastra, config: config2, emitDropEvent });
20968
21221
  } catch (error48) {
20969
21222
  this.logger?.warn("Failed to initialize observability exporter", {
20970
21223
  exporterName: exporter.name,
@@ -21152,6 +21405,9 @@ var Observability = class extends base.MastraBase {
21152
21405
  }
21153
21406
  };
21154
21407
 
21408
+ // src/features.ts
21409
+ var observabilityFeatures = /* @__PURE__ */ new Set(["model-inference-span"]);
21410
+
21155
21411
  // src/tracing-options.ts
21156
21412
  function buildTracingOptions(...updaters) {
21157
21413
  return updaters.reduce((opts, updater) => updater(opts), {});
@@ -21188,6 +21444,7 @@ exports.getExternalParentId = getExternalParentId;
21188
21444
  exports.isSerializedMap = isSerializedMap;
21189
21445
  exports.mergeSerializationOptions = mergeSerializationOptions;
21190
21446
  exports.observabilityConfigValueSchema = observabilityConfigValueSchema;
21447
+ exports.observabilityFeatures = observabilityFeatures;
21191
21448
  exports.observabilityInstanceConfigSchema = observabilityInstanceConfigSchema;
21192
21449
  exports.observabilityRegistryConfigSchema = observabilityRegistryConfigSchema;
21193
21450
  exports.reconstructSerializedMap = reconstructSerializedMap;