@mastra/observability 1.20.1 → 1.21.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1969,12 +1969,20 @@ function emitDurationMetrics(span, metrics) {
1969
1969
  const durationMs = span.endTime.getTime() - span.startTime.getTime();
1970
1970
  metrics.emit(durationMetricName, durationMs, { status: span.errorInfo ? "error" : "ok" });
1971
1971
  }
1972
- /** Emit token usage metrics for a model-generation span. */
1972
+ /** Emit token usage metrics for a model-generation or classifier-evaluation span. */
1973
1973
  function emitTokenMetrics(span, metrics) {
1974
- if (span.type !== _mastra_core_observability.SpanType.MODEL_GENERATION) return;
1975
- const attrs = span.attributes;
1976
- if (!attrs?.usage) return;
1977
- emitUsageMetrics(attrs, attrs.usage, metrics);
1974
+ if (span.type === _mastra_core_observability.SpanType.MODEL_GENERATION) {
1975
+ const attrs = span.attributes;
1976
+ if (attrs?.usage) emitUsageMetrics(attrs, attrs.usage, metrics);
1977
+ return;
1978
+ }
1979
+ if (span.type === _mastra_core_observability.SpanType.CLASSIFIER_EVALUATION) {
1980
+ const attrs = span.attributes;
1981
+ if (attrs?.usage) emitUsageMetrics({
1982
+ provider: attrs.provider,
1983
+ model: attrs.modelId
1984
+ }, attrs.usage, metrics);
1985
+ }
1978
1986
  }
1979
1987
  /**
1980
1988
  * Emit token usage metrics from an explicit usage payload, using the supplied
@@ -2506,6 +2514,26 @@ function extractStepInput(payload) {
2506
2514
  }
2507
2515
  }
2508
2516
  /**
2517
+ * Chunks that come from the model provider. The first one marks the response's
2518
+ * first chunk, matching OTel's `gen_ai.response.time_to_first_chunk`. Everything
2519
+ * else, such as lifecycle markers or `data-*` parts a processor writes before the
2520
+ * model call, is Mastra's own and does not count.
2521
+ */
2522
+ const MODEL_RESPONSE_CHUNK_TYPES = /* @__PURE__ */ new Set([
2523
+ "response-metadata",
2524
+ "raw",
2525
+ "text-start",
2526
+ "text-delta",
2527
+ "reasoning-start",
2528
+ "reasoning-delta",
2529
+ "reasoning-file",
2530
+ "tool-call-input-streaming-start",
2531
+ "tool-call-delta",
2532
+ "tool-call",
2533
+ "source",
2534
+ "file"
2535
+ ]);
2536
+ /**
2509
2537
  * Manages MODEL_STEP and MODEL_CHUNK span tracking for streaming Model responses.
2510
2538
  *
2511
2539
  * Should be instantiated once per MODEL_GENERATION span and shared across
@@ -2522,6 +2550,15 @@ var ModelSpanTracker = class {
2522
2550
  #chunkSequence = 0;
2523
2551
  #completionStartTime;
2524
2552
  #inferenceCompletionStartTime;
2553
+ /**
2554
+ * True between a step's `step-start` and `step-finish` chunks, the window in which
2555
+ * the provider responds. Chunks outside it, such as a `tool-call` replayed for a
2556
+ * finished background task on resume, are Mastra's own and never start the clock.
2557
+ * Durable runs wrap one step's provider stream at a time, so every chunk there counts.
2558
+ */
2559
+ #inModelResponse = false;
2560
+ /** Set only by the agent's main loop; receives the first chunk's completionStartTime. */
2561
+ #agentRunSpan;
2525
2562
  #currentStepInputIsFinal = false;
2526
2563
  /** When true, step-finish chunks don't auto-close the step span (for durable execution) */
2527
2564
  #deferStepClose = false;
@@ -2529,8 +2566,9 @@ var ModelSpanTracker = class {
2529
2566
  #pendingStepFinishPayload;
2530
2567
  /** Static request-side context applied to every MODEL_INFERENCE span */
2531
2568
  #inferenceContext;
2532
- constructor(modelSpan) {
2569
+ constructor(modelSpan, options) {
2533
2570
  this.#modelSpan = modelSpan;
2571
+ this.#agentRunSpan = options?.agentRunSpan;
2534
2572
  }
2535
2573
  /**
2536
2574
  * Set request-side context applied to subsequent MODEL_INFERENCE spans.
@@ -2540,12 +2578,22 @@ var ModelSpanTracker = class {
2540
2578
  this.#inferenceContext = context;
2541
2579
  }
2542
2580
  /**
2543
- * Capture the completion start time (time to first token) when the first content chunk arrives.
2581
+ * Record when the provider's first chunk arrived and copy that clock up the
2582
+ * agent's own chain (inference → step → generation → agent run), first write
2583
+ * wins at each level. Time to first chunk is derived later as
2584
+ * `completionStartTime - startTime` on whichever span is being read.
2544
2585
  */
2545
2586
  #captureCompletionStartTime() {
2587
+ this.#ensureStepAndInference();
2588
+ if (this.#inferenceCompletionStartTime) return;
2546
2589
  const now = /* @__PURE__ */ new Date();
2590
+ this.#inferenceCompletionStartTime = now;
2547
2591
  this.#completionStartTime ??= now;
2548
- this.#inferenceCompletionStartTime ??= now;
2592
+ for (const span of [
2593
+ this.#currentStepSpan,
2594
+ this.#modelSpan,
2595
+ this.#agentRunSpan
2596
+ ]) if (span && !span.attributes?.completionStartTime) span.update({ attributes: { completionStartTime: now } });
2549
2597
  }
2550
2598
  /**
2551
2599
  * Get the tracing context for creating child spans.
@@ -2589,13 +2637,16 @@ var ModelSpanTracker = class {
2589
2637
  }, model);
2590
2638
  if (providerCostContext && !spanOptions.attributes) spanOptions.attributes = {};
2591
2639
  if (spanOptions.attributes) {
2592
- spanOptions.attributes.completionStartTime = this.#completionStartTime;
2640
+ spanOptions.attributes.completionStartTime = this.#completionStartTime ?? spanOptions.attributes.completionStartTime;
2593
2641
  spanOptions.attributes.usage = extractUsageMetrics(usage, providerMetadata);
2594
2642
  spanOptions.attributes.costContext = providerCostContext ?? spanOptions.attributes.costContext;
2595
2643
  if (!spanOptions.attributes.costContext) spanOptions.attributes.costContext = getGatewayCostContext({ stepProviderMetadata });
2596
2644
  }
2597
2645
  this.#modelSpan?.end(spanOptions);
2598
2646
  }
2647
+ getCompletionStartTime() {
2648
+ return this.#completionStartTime;
2649
+ }
2599
2650
  /**
2600
2651
  * Update the generation span
2601
2652
  */
@@ -2663,6 +2714,7 @@ var ModelSpanTracker = class {
2663
2714
  });
2664
2715
  this.#currentStepInputIsFinal = Array.isArray(payload?.inputMessages);
2665
2716
  this.#chunkSequence = 0;
2717
+ this.#inferenceCompletionStartTime = void 0;
2666
2718
  }
2667
2719
  /**
2668
2720
  * End the current MODEL_INFERENCE span when the provider stream finishes.
@@ -2984,13 +3036,7 @@ var ModelSpanTracker = class {
2984
3036
  */
2985
3037
  wrapStream(stream) {
2986
3038
  return stream.pipeThrough(new stream_web.TransformStream({ transform: (chunk, controller) => {
2987
- switch (chunk.type) {
2988
- case "text-delta":
2989
- case "tool-call-delta":
2990
- case "reasoning-delta":
2991
- this.#captureCompletionStartTime();
2992
- break;
2993
- }
3039
+ if ((this.#inModelResponse || this.#deferStepClose) && MODEL_RESPONSE_CHUNK_TYPES.has(chunk.type)) this.#captureCompletionStartTime();
2994
3040
  controller.enqueue(chunk);
2995
3041
  switch (chunk.type) {
2996
3042
  case "text-start":
@@ -3014,11 +3060,13 @@ var ModelSpanTracker = class {
3014
3060
  this.#handleObjectChunk(chunk);
3015
3061
  break;
3016
3062
  case "step-start":
3063
+ this.#inModelResponse = true;
3017
3064
  if (this.#currentStepSpan) this.updateStep(chunk.payload);
3018
3065
  else this.startStep(chunk.payload);
3019
3066
  if (!this.#currentInferenceSpan) this.startInference(chunk.payload);
3020
3067
  break;
3021
3068
  case "step-finish":
3069
+ this.#inModelResponse = false;
3022
3070
  if (this.#deferStepClose) {
3023
3071
  this.#pendingStepFinishPayload = chunk.payload;
3024
3072
  this.#endChunkSpan();
@@ -3376,9 +3424,9 @@ var BaseSpan = class BaseSpan {
3376
3424
  * Create a ModelSpanTracker for this span (only works if this is a MODEL_GENERATION span)
3377
3425
  * Returns undefined for non-MODEL_GENERATION spans
3378
3426
  */
3379
- createTracker() {
3427
+ createTracker(options) {
3380
3428
  if (this.type !== _mastra_core_observability.SpanType.MODEL_GENERATION) return;
3381
- return new ModelSpanTracker(this);
3429
+ return new ModelSpanTracker(this, options);
3382
3430
  }
3383
3431
  /** Returns `TRUE` if the span is the root span of a trace */
3384
3432
  get isRootSpan() {