@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.js CHANGED
@@ -1943,12 +1943,20 @@ function emitDurationMetrics(span, metrics) {
1943
1943
  const durationMs = span.endTime.getTime() - span.startTime.getTime();
1944
1944
  metrics.emit(durationMetricName, durationMs, { status: span.errorInfo ? "error" : "ok" });
1945
1945
  }
1946
- /** Emit token usage metrics for a model-generation span. */
1946
+ /** Emit token usage metrics for a model-generation or classifier-evaluation span. */
1947
1947
  function emitTokenMetrics(span, metrics) {
1948
- if (span.type !== SpanType.MODEL_GENERATION) return;
1949
- const attrs = span.attributes;
1950
- if (!attrs?.usage) return;
1951
- emitUsageMetrics(attrs, attrs.usage, metrics);
1948
+ if (span.type === SpanType.MODEL_GENERATION) {
1949
+ const attrs = span.attributes;
1950
+ if (attrs?.usage) emitUsageMetrics(attrs, attrs.usage, metrics);
1951
+ return;
1952
+ }
1953
+ if (span.type === SpanType.CLASSIFIER_EVALUATION) {
1954
+ const attrs = span.attributes;
1955
+ if (attrs?.usage) emitUsageMetrics({
1956
+ provider: attrs.provider,
1957
+ model: attrs.modelId
1958
+ }, attrs.usage, metrics);
1959
+ }
1952
1960
  }
1953
1961
  /**
1954
1962
  * Emit token usage metrics from an explicit usage payload, using the supplied
@@ -2480,6 +2488,26 @@ function extractStepInput(payload) {
2480
2488
  }
2481
2489
  }
2482
2490
  /**
2491
+ * Chunks that come from the model provider. The first one marks the response's
2492
+ * first chunk, matching OTel's `gen_ai.response.time_to_first_chunk`. Everything
2493
+ * else, such as lifecycle markers or `data-*` parts a processor writes before the
2494
+ * model call, is Mastra's own and does not count.
2495
+ */
2496
+ const MODEL_RESPONSE_CHUNK_TYPES = /* @__PURE__ */ new Set([
2497
+ "response-metadata",
2498
+ "raw",
2499
+ "text-start",
2500
+ "text-delta",
2501
+ "reasoning-start",
2502
+ "reasoning-delta",
2503
+ "reasoning-file",
2504
+ "tool-call-input-streaming-start",
2505
+ "tool-call-delta",
2506
+ "tool-call",
2507
+ "source",
2508
+ "file"
2509
+ ]);
2510
+ /**
2483
2511
  * Manages MODEL_STEP and MODEL_CHUNK span tracking for streaming Model responses.
2484
2512
  *
2485
2513
  * Should be instantiated once per MODEL_GENERATION span and shared across
@@ -2496,6 +2524,15 @@ var ModelSpanTracker = class {
2496
2524
  #chunkSequence = 0;
2497
2525
  #completionStartTime;
2498
2526
  #inferenceCompletionStartTime;
2527
+ /**
2528
+ * True between a step's `step-start` and `step-finish` chunks, the window in which
2529
+ * the provider responds. Chunks outside it, such as a `tool-call` replayed for a
2530
+ * finished background task on resume, are Mastra's own and never start the clock.
2531
+ * Durable runs wrap one step's provider stream at a time, so every chunk there counts.
2532
+ */
2533
+ #inModelResponse = false;
2534
+ /** Set only by the agent's main loop; receives the first chunk's completionStartTime. */
2535
+ #agentRunSpan;
2499
2536
  #currentStepInputIsFinal = false;
2500
2537
  /** When true, step-finish chunks don't auto-close the step span (for durable execution) */
2501
2538
  #deferStepClose = false;
@@ -2503,8 +2540,9 @@ var ModelSpanTracker = class {
2503
2540
  #pendingStepFinishPayload;
2504
2541
  /** Static request-side context applied to every MODEL_INFERENCE span */
2505
2542
  #inferenceContext;
2506
- constructor(modelSpan) {
2543
+ constructor(modelSpan, options) {
2507
2544
  this.#modelSpan = modelSpan;
2545
+ this.#agentRunSpan = options?.agentRunSpan;
2508
2546
  }
2509
2547
  /**
2510
2548
  * Set request-side context applied to subsequent MODEL_INFERENCE spans.
@@ -2514,12 +2552,22 @@ var ModelSpanTracker = class {
2514
2552
  this.#inferenceContext = context;
2515
2553
  }
2516
2554
  /**
2517
- * Capture the completion start time (time to first token) when the first content chunk arrives.
2555
+ * Record when the provider's first chunk arrived and copy that clock up the
2556
+ * agent's own chain (inference → step → generation → agent run), first write
2557
+ * wins at each level. Time to first chunk is derived later as
2558
+ * `completionStartTime - startTime` on whichever span is being read.
2518
2559
  */
2519
2560
  #captureCompletionStartTime() {
2561
+ this.#ensureStepAndInference();
2562
+ if (this.#inferenceCompletionStartTime) return;
2520
2563
  const now = /* @__PURE__ */ new Date();
2564
+ this.#inferenceCompletionStartTime = now;
2521
2565
  this.#completionStartTime ??= now;
2522
- this.#inferenceCompletionStartTime ??= now;
2566
+ for (const span of [
2567
+ this.#currentStepSpan,
2568
+ this.#modelSpan,
2569
+ this.#agentRunSpan
2570
+ ]) if (span && !span.attributes?.completionStartTime) span.update({ attributes: { completionStartTime: now } });
2523
2571
  }
2524
2572
  /**
2525
2573
  * Get the tracing context for creating child spans.
@@ -2563,13 +2611,16 @@ var ModelSpanTracker = class {
2563
2611
  }, model);
2564
2612
  if (providerCostContext && !spanOptions.attributes) spanOptions.attributes = {};
2565
2613
  if (spanOptions.attributes) {
2566
- spanOptions.attributes.completionStartTime = this.#completionStartTime;
2614
+ spanOptions.attributes.completionStartTime = this.#completionStartTime ?? spanOptions.attributes.completionStartTime;
2567
2615
  spanOptions.attributes.usage = extractUsageMetrics(usage, providerMetadata);
2568
2616
  spanOptions.attributes.costContext = providerCostContext ?? spanOptions.attributes.costContext;
2569
2617
  if (!spanOptions.attributes.costContext) spanOptions.attributes.costContext = getGatewayCostContext({ stepProviderMetadata });
2570
2618
  }
2571
2619
  this.#modelSpan?.end(spanOptions);
2572
2620
  }
2621
+ getCompletionStartTime() {
2622
+ return this.#completionStartTime;
2623
+ }
2573
2624
  /**
2574
2625
  * Update the generation span
2575
2626
  */
@@ -2637,6 +2688,7 @@ var ModelSpanTracker = class {
2637
2688
  });
2638
2689
  this.#currentStepInputIsFinal = Array.isArray(payload?.inputMessages);
2639
2690
  this.#chunkSequence = 0;
2691
+ this.#inferenceCompletionStartTime = void 0;
2640
2692
  }
2641
2693
  /**
2642
2694
  * End the current MODEL_INFERENCE span when the provider stream finishes.
@@ -2958,13 +3010,7 @@ var ModelSpanTracker = class {
2958
3010
  */
2959
3011
  wrapStream(stream) {
2960
3012
  return stream.pipeThrough(new TransformStream({ transform: (chunk, controller) => {
2961
- switch (chunk.type) {
2962
- case "text-delta":
2963
- case "tool-call-delta":
2964
- case "reasoning-delta":
2965
- this.#captureCompletionStartTime();
2966
- break;
2967
- }
3013
+ if ((this.#inModelResponse || this.#deferStepClose) && MODEL_RESPONSE_CHUNK_TYPES.has(chunk.type)) this.#captureCompletionStartTime();
2968
3014
  controller.enqueue(chunk);
2969
3015
  switch (chunk.type) {
2970
3016
  case "text-start":
@@ -2988,11 +3034,13 @@ var ModelSpanTracker = class {
2988
3034
  this.#handleObjectChunk(chunk);
2989
3035
  break;
2990
3036
  case "step-start":
3037
+ this.#inModelResponse = true;
2991
3038
  if (this.#currentStepSpan) this.updateStep(chunk.payload);
2992
3039
  else this.startStep(chunk.payload);
2993
3040
  if (!this.#currentInferenceSpan) this.startInference(chunk.payload);
2994
3041
  break;
2995
3042
  case "step-finish":
3043
+ this.#inModelResponse = false;
2996
3044
  if (this.#deferStepClose) {
2997
3045
  this.#pendingStepFinishPayload = chunk.payload;
2998
3046
  this.#endChunkSpan();
@@ -3350,9 +3398,9 @@ var BaseSpan = class BaseSpan {
3350
3398
  * Create a ModelSpanTracker for this span (only works if this is a MODEL_GENERATION span)
3351
3399
  * Returns undefined for non-MODEL_GENERATION spans
3352
3400
  */
3353
- createTracker() {
3401
+ createTracker(options) {
3354
3402
  if (this.type !== SpanType.MODEL_GENERATION) return;
3355
- return new ModelSpanTracker(this);
3403
+ return new ModelSpanTracker(this, options);
3356
3404
  }
3357
3405
  /** Returns `TRUE` if the span is the root span of a trace */
3358
3406
  get isRootSpan() {