@mastra/observability 1.22.0 → 1.23.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/exporters/test.d.ts.map +1 -1
- package/dist/index.cjs +277 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +277 -69
- package/dist/index.js.map +1 -1
- package/dist/instances/base.d.ts.map +1 -1
- package/dist/metrics/pricing-data.json +418 -542
- package/dist/model-tracing.d.ts +24 -9
- package/dist/model-tracing.d.ts.map +1 -1
- package/dist/span_processors/sensitive-data-filter.d.ts.map +1 -1
- package/dist/spans/base.d.ts +13 -9
- package/dist/spans/base.d.ts.map +1 -1
- package/dist/spans/default.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -2292,9 +2292,10 @@ function addUsageStats(a, b) {
|
|
|
2292
2292
|
* Provides span tracking for Model generations, including:
|
|
2293
2293
|
* - MODEL_STEP spans (one per Model API call - includes processors and tool executions)
|
|
2294
2294
|
* - MODEL_INFERENCE spans (the provider call itself - model latency only)
|
|
2295
|
-
* - MODEL_CHUNK spans (
|
|
2295
|
+
* - MODEL_CHUNK spans (processed streaming content within a logical step)
|
|
2296
2296
|
*
|
|
2297
|
-
* Hierarchy: MODEL_GENERATION -> MODEL_STEP -> MODEL_INFERENCE
|
|
2297
|
+
* Hierarchy: MODEL_GENERATION -> MODEL_STEP -> MODEL_INFERENCE (provider timing)
|
|
2298
|
+
* -> MODEL_CHUNK (processed content)
|
|
2298
2299
|
*
|
|
2299
2300
|
* Processors and tool executions remain children of MODEL_STEP (siblings of
|
|
2300
2301
|
* MODEL_INFERENCE), so MODEL_INFERENCE measures pure model time.
|
|
@@ -2311,6 +2312,12 @@ function addUsageStats(a, b) {
|
|
|
2311
2312
|
function supportsModelInference() {
|
|
2312
2313
|
return coreFeatures.has("model-inference-span");
|
|
2313
2314
|
}
|
|
2315
|
+
function interruptedByRun(termination) {
|
|
2316
|
+
return termination.outcome === "abort" || termination.stopReason === "timeout" && termination.stopReasonContext && "timeoutType" in termination.stopReasonContext && termination.stopReasonContext.timeoutType === "total" ? {
|
|
2317
|
+
...termination,
|
|
2318
|
+
outcome: "interrupted"
|
|
2319
|
+
} : termination;
|
|
2320
|
+
}
|
|
2314
2321
|
function parseGatewayCost(providerMetadata) {
|
|
2315
2322
|
const rawCost = providerMetadata?.gateway?.cost;
|
|
2316
2323
|
const cost = typeof rawCost === "number" ? rawCost : typeof rawCost === "string" && rawCost.trim().length > 0 ? Number(rawCost) : void 0;
|
|
@@ -2501,13 +2508,17 @@ var ModelSpanTracker = class {
|
|
|
2501
2508
|
#modelSpan;
|
|
2502
2509
|
#currentStepSpan;
|
|
2503
2510
|
#currentInferenceSpan;
|
|
2511
|
+
#stepHasInference = false;
|
|
2512
|
+
#tracksProviderStream = false;
|
|
2504
2513
|
#currentChunkSpan;
|
|
2505
2514
|
#currentChunkType;
|
|
2506
2515
|
#accumulator = {};
|
|
2507
2516
|
#stepIndex = 0;
|
|
2508
2517
|
#chunkSequence = 0;
|
|
2509
2518
|
#completionStartTime;
|
|
2519
|
+
#timeToFirstTokenMs;
|
|
2510
2520
|
#inferenceCompletionStartTime;
|
|
2521
|
+
#inferenceTimeToFirstTokenMs;
|
|
2511
2522
|
/**
|
|
2512
2523
|
* True between a step's `step-start` and `step-finish` chunks, the window in which
|
|
2513
2524
|
* the provider responds. Chunks outside it, such as a `tool-call` replayed for a
|
|
@@ -2536,22 +2547,41 @@ var ModelSpanTracker = class {
|
|
|
2536
2547
|
this.#inferenceContext = context;
|
|
2537
2548
|
}
|
|
2538
2549
|
/**
|
|
2539
|
-
* Record when the provider's first chunk arrived
|
|
2540
|
-
*
|
|
2541
|
-
*
|
|
2542
|
-
*
|
|
2550
|
+
* Record when the provider's first chunk arrived. Time to first token is measured
|
|
2551
|
+
* once here, from the start of the inference that streamed the chunk; the inference
|
|
2552
|
+
* reports it when it ends, and the number is copied up the agent's own chain
|
|
2553
|
+
* (step → generation → agent run) with the chunk's clock, first write wins at each
|
|
2554
|
+
* level. Time to first chunk is `completionStartTime - startTime` on whichever span
|
|
2555
|
+
* is being read.
|
|
2543
2556
|
*/
|
|
2544
2557
|
#captureCompletionStartTime() {
|
|
2545
2558
|
this.#ensureStepAndInference();
|
|
2546
2559
|
if (this.#inferenceCompletionStartTime) return;
|
|
2547
2560
|
const now = /* @__PURE__ */ new Date();
|
|
2561
|
+
const inference = this.#currentInferenceSpan;
|
|
2562
|
+
const timeToFirstTokenMs = inference ? now.getTime() - inference.startTime.getTime() : void 0;
|
|
2548
2563
|
this.#inferenceCompletionStartTime = now;
|
|
2564
|
+
this.#inferenceTimeToFirstTokenMs = timeToFirstTokenMs;
|
|
2549
2565
|
this.#completionStartTime ??= now;
|
|
2566
|
+
this.#timeToFirstTokenMs ??= timeToFirstTokenMs;
|
|
2550
2567
|
for (const span of [
|
|
2551
2568
|
this.#currentStepSpan,
|
|
2552
2569
|
this.#modelSpan,
|
|
2553
2570
|
this.#agentRunSpan
|
|
2554
|
-
])
|
|
2571
|
+
]) {
|
|
2572
|
+
if (!span || span.attributes?.completionStartTime) continue;
|
|
2573
|
+
span.update({ attributes: {
|
|
2574
|
+
completionStartTime: now,
|
|
2575
|
+
...timeToFirstTokenMs !== void 0 ? { timeToFirstTokenMs } : {}
|
|
2576
|
+
} });
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
/** What the current inference streamed so far, for whichever way it ends. */
|
|
2580
|
+
#inferenceStreamAttributes() {
|
|
2581
|
+
return {
|
|
2582
|
+
...this.#inferenceCompletionStartTime ? { completionStartTime: this.#inferenceCompletionStartTime } : {},
|
|
2583
|
+
...this.#inferenceTimeToFirstTokenMs !== void 0 ? { timeToFirstTokenMs: this.#inferenceTimeToFirstTokenMs } : {}
|
|
2584
|
+
};
|
|
2555
2585
|
}
|
|
2556
2586
|
/**
|
|
2557
2587
|
* Get the tracing context for creating child spans.
|
|
@@ -2566,21 +2596,33 @@ var ModelSpanTracker = class {
|
|
|
2566
2596
|
* doesn't leave them dangling. No-op if they were already closed.
|
|
2567
2597
|
*/
|
|
2568
2598
|
reportGenerationError(options) {
|
|
2599
|
+
this.#endChunkSpan();
|
|
2600
|
+
const termination = options.outcome != null ? {
|
|
2601
|
+
outcome: options.outcome,
|
|
2602
|
+
stopReason: options.stopReason,
|
|
2603
|
+
stopReasonContext: options.stopReasonContext
|
|
2604
|
+
} : coreObservability.executionFailureTermination?.(options.error);
|
|
2569
2605
|
if (this.#currentInferenceSpan) {
|
|
2570
2606
|
this.#currentInferenceSpan.error({
|
|
2571
2607
|
error: options.error,
|
|
2572
|
-
|
|
2608
|
+
...termination ? interruptedByRun(termination) : {},
|
|
2609
|
+
endSpan: true,
|
|
2610
|
+
attributes: this.#inferenceStreamAttributes()
|
|
2573
2611
|
});
|
|
2574
2612
|
this.#currentInferenceSpan = void 0;
|
|
2575
2613
|
}
|
|
2576
2614
|
if (this.#currentStepSpan) {
|
|
2577
2615
|
this.#currentStepSpan.error({
|
|
2578
2616
|
error: options.error,
|
|
2617
|
+
...termination ? interruptedByRun(termination) : {},
|
|
2579
2618
|
endSpan: true
|
|
2580
2619
|
});
|
|
2581
2620
|
this.#currentStepSpan = void 0;
|
|
2582
2621
|
}
|
|
2583
|
-
this.#modelSpan?.error(
|
|
2622
|
+
this.#modelSpan?.error({
|
|
2623
|
+
...options,
|
|
2624
|
+
...termination
|
|
2625
|
+
});
|
|
2584
2626
|
}
|
|
2585
2627
|
/**
|
|
2586
2628
|
* End the generation span with optional raw usage data.
|
|
@@ -2595,16 +2637,37 @@ var ModelSpanTracker = class {
|
|
|
2595
2637
|
}, model);
|
|
2596
2638
|
if (providerCostContext && !spanOptions.attributes) spanOptions.attributes = {};
|
|
2597
2639
|
if (spanOptions.attributes) {
|
|
2598
|
-
|
|
2640
|
+
if (this.#completionStartTime) {
|
|
2641
|
+
spanOptions.attributes.completionStartTime = this.#completionStartTime;
|
|
2642
|
+
spanOptions.attributes.timeToFirstTokenMs = this.#timeToFirstTokenMs;
|
|
2643
|
+
}
|
|
2599
2644
|
spanOptions.attributes.usage = extractUsageMetrics(usage, providerMetadata);
|
|
2600
2645
|
spanOptions.attributes.costContext = providerCostContext ?? spanOptions.attributes.costContext;
|
|
2601
2646
|
if (!spanOptions.attributes.costContext) spanOptions.attributes.costContext = getGatewayCostContext({ stepProviderMetadata });
|
|
2602
2647
|
}
|
|
2648
|
+
if (spanOptions.outcome === "abort" || spanOptions.stopReason === "timeout") {
|
|
2649
|
+
const termination = {
|
|
2650
|
+
outcome: spanOptions.outcome,
|
|
2651
|
+
stopReason: spanOptions.stopReason,
|
|
2652
|
+
stopReasonContext: spanOptions.stopReasonContext
|
|
2653
|
+
};
|
|
2654
|
+
this.#endChunkSpan();
|
|
2655
|
+
this.#currentStepSpan?.end({
|
|
2656
|
+
...interruptedByRun(termination),
|
|
2657
|
+
endTree: true
|
|
2658
|
+
});
|
|
2659
|
+
this.#currentStepSpan = void 0;
|
|
2660
|
+
this.#currentInferenceSpan = void 0;
|
|
2661
|
+
this.#currentStepInputIsFinal = false;
|
|
2662
|
+
}
|
|
2603
2663
|
this.#modelSpan?.end(spanOptions);
|
|
2604
2664
|
}
|
|
2605
2665
|
getCompletionStartTime() {
|
|
2606
2666
|
return this.#completionStartTime;
|
|
2607
2667
|
}
|
|
2668
|
+
getTimeToFirstTokenMs() {
|
|
2669
|
+
return this.#timeToFirstTokenMs;
|
|
2670
|
+
}
|
|
2608
2671
|
/**
|
|
2609
2672
|
* Update the generation span
|
|
2610
2673
|
*/
|
|
@@ -2658,10 +2721,12 @@ var ModelSpanTracker = class {
|
|
|
2658
2721
|
*/
|
|
2659
2722
|
startStep(payload) {
|
|
2660
2723
|
if (this.#currentStepSpan) return;
|
|
2724
|
+
this.#stepHasInference = false;
|
|
2661
2725
|
const input = extractStepInput(payload);
|
|
2662
2726
|
this.#currentStepSpan = this.#modelSpan?.createChildSpan({
|
|
2663
2727
|
name: `step: ${this.#stepIndex}`,
|
|
2664
2728
|
type: SpanType.MODEL_STEP,
|
|
2729
|
+
...typeof coreObservability.agentLoopTermination === "function" ? { outcome: null } : {},
|
|
2665
2730
|
attributes: {
|
|
2666
2731
|
stepIndex: this.#stepIndex,
|
|
2667
2732
|
...payload?.messageId ? { messageId: payload.messageId } : {},
|
|
@@ -2673,6 +2738,7 @@ var ModelSpanTracker = class {
|
|
|
2673
2738
|
this.#currentStepInputIsFinal = Array.isArray(payload?.inputMessages);
|
|
2674
2739
|
this.#chunkSequence = 0;
|
|
2675
2740
|
this.#inferenceCompletionStartTime = void 0;
|
|
2741
|
+
this.#inferenceTimeToFirstTokenMs = void 0;
|
|
2676
2742
|
}
|
|
2677
2743
|
/**
|
|
2678
2744
|
* End the current MODEL_INFERENCE span when the provider stream finishes.
|
|
@@ -2682,22 +2748,31 @@ var ModelSpanTracker = class {
|
|
|
2682
2748
|
*
|
|
2683
2749
|
* Safe to call multiple times - no-ops if the span is already closed.
|
|
2684
2750
|
*/
|
|
2685
|
-
#endInferenceSpan(payload) {
|
|
2751
|
+
#endInferenceSpan(payload, captureOutput = true) {
|
|
2686
2752
|
if (!this.#currentInferenceSpan) return;
|
|
2687
2753
|
const { usage: rawUsage, ...otherOutput } = payload.output;
|
|
2688
2754
|
const usage = extractUsageMetrics(rawUsage, payload.metadata?.providerMetadata);
|
|
2689
2755
|
const responseModel = typeof payload.metadata?.modelId === "string" ? payload.metadata.modelId : void 0;
|
|
2690
2756
|
this.#currentInferenceSpan.end({
|
|
2691
|
-
|
|
2757
|
+
...coreObservability.agentLoopTermination?.({
|
|
2758
|
+
stopCause: "model_finished",
|
|
2759
|
+
finishReason: payload.stepResult.reason
|
|
2760
|
+
}),
|
|
2761
|
+
...payload.stepResult.reason === "error" ? {
|
|
2762
|
+
outcome: "error",
|
|
2763
|
+
stopReason: "model_error"
|
|
2764
|
+
} : {},
|
|
2765
|
+
...captureOutput ? { output: otherOutput } : {},
|
|
2692
2766
|
attributes: {
|
|
2693
2767
|
usage,
|
|
2694
2768
|
finishReason: payload.stepResult.reason,
|
|
2695
2769
|
warnings: payload.stepResult.warnings,
|
|
2696
|
-
|
|
2770
|
+
...this.#inferenceStreamAttributes(),
|
|
2697
2771
|
...responseModel?.trim() ? { responseModel } : {}
|
|
2698
2772
|
}
|
|
2699
2773
|
});
|
|
2700
2774
|
this.#currentInferenceSpan = void 0;
|
|
2775
|
+
this.#inferenceCompletionStartTime = void 0;
|
|
2701
2776
|
}
|
|
2702
2777
|
/**
|
|
2703
2778
|
* Open the MODEL_INFERENCE span for the current step. Chunks (including tool-call
|
|
@@ -2715,31 +2790,56 @@ var ModelSpanTracker = class {
|
|
|
2715
2790
|
* start time.
|
|
2716
2791
|
*
|
|
2717
2792
|
* `payload.modelId` / `payload.provider` name the model the caller is about
|
|
2718
|
-
* to invoke and are recorded on MODEL_STEP
|
|
2719
|
-
*
|
|
2720
|
-
* a
|
|
2793
|
+
* to invoke and are recorded on MODEL_STEP and the inference. A named call
|
|
2794
|
+
* while an inference is still open is the next attempt of the same step (a
|
|
2795
|
+
* fallback model after a failure): that inference ends as failed and the
|
|
2796
|
+
* attempt gets its own, so its timing and model are its own. The safety net
|
|
2797
|
+
* never records them: a replayed step-start chunk names the model of the
|
|
2798
|
+
* original call.
|
|
2721
2799
|
*/
|
|
2722
2800
|
startInference(payload) {
|
|
2723
|
-
if (payload?.modelId && payload.provider)
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2801
|
+
if (payload?.modelId && payload.provider) {
|
|
2802
|
+
this.#currentStepSpan?.update({ attributes: {
|
|
2803
|
+
model: payload.modelId,
|
|
2804
|
+
provider: payload.provider
|
|
2805
|
+
} });
|
|
2806
|
+
this.failInference(/* @__PURE__ */ new Error("Model call ended without a result before the next attempt started"));
|
|
2807
|
+
}
|
|
2727
2808
|
this.#openInference(payload);
|
|
2728
2809
|
}
|
|
2810
|
+
/** End the current attempt only; generation and agent spans belong to their owners. */
|
|
2811
|
+
endAttempt(termination) {
|
|
2812
|
+
termination = interruptedByRun(termination);
|
|
2813
|
+
const payload = this.#pendingStepFinishPayload;
|
|
2814
|
+
if (payload) this.#endStepSpan(payload, termination);
|
|
2815
|
+
else {
|
|
2816
|
+
this.#endChunkSpan();
|
|
2817
|
+
this.#currentInferenceSpan?.end(termination);
|
|
2818
|
+
this.#currentStepSpan?.end(termination);
|
|
2819
|
+
this.#currentStepSpan = void 0;
|
|
2820
|
+
this.#currentInferenceSpan = void 0;
|
|
2821
|
+
}
|
|
2822
|
+
this.#pendingStepFinishPayload = void 0;
|
|
2823
|
+
this.#inferenceCompletionStartTime = void 0;
|
|
2824
|
+
this.#currentStepInputIsFinal = false;
|
|
2825
|
+
}
|
|
2729
2826
|
#openInference(payload) {
|
|
2730
2827
|
if (!supportsModelInference()) return;
|
|
2731
2828
|
if (!this.#currentStepSpan || this.#currentInferenceSpan) return;
|
|
2732
2829
|
this.#inferenceCompletionStartTime = void 0;
|
|
2830
|
+
this.#inferenceTimeToFirstTokenMs = void 0;
|
|
2733
2831
|
const input = extractStepInput(payload);
|
|
2734
2832
|
const generationAttrs = this.#modelSpan?.attributes;
|
|
2735
2833
|
const ctx = this.#inferenceContext;
|
|
2834
|
+
this.#stepHasInference = true;
|
|
2736
2835
|
this.#currentInferenceSpan = this.#currentStepSpan.createChildSpan({
|
|
2737
2836
|
name: `inference: ${this.#stepIndex}`,
|
|
2738
2837
|
type: SpanType.MODEL_INFERENCE,
|
|
2838
|
+
...typeof coreObservability.agentLoopTermination === "function" ? { outcome: null } : {},
|
|
2739
2839
|
attributes: {
|
|
2740
2840
|
stepIndex: this.#stepIndex,
|
|
2741
|
-
model: generationAttrs?.model,
|
|
2742
|
-
provider: generationAttrs?.provider,
|
|
2841
|
+
model: payload?.modelId ?? generationAttrs?.model,
|
|
2842
|
+
provider: payload?.provider ?? generationAttrs?.provider,
|
|
2743
2843
|
streaming: generationAttrs?.streaming,
|
|
2744
2844
|
...ctx?.parameters !== void 0 ? { parameters: ctx.parameters } : {},
|
|
2745
2845
|
...ctx?.providerOptions !== void 0 ? { providerOptions: ctx.providerOptions } : {},
|
|
@@ -2756,11 +2856,37 @@ var ModelSpanTracker = class {
|
|
|
2756
2856
|
* End the open MODEL_INFERENCE span as interrupted while keeping MODEL_STEP open,
|
|
2757
2857
|
* so a replacement request for the same step gets its own inference span.
|
|
2758
2858
|
*/
|
|
2759
|
-
interruptInference() {
|
|
2859
|
+
interruptInference(termination) {
|
|
2760
2860
|
this.#endChunkSpan();
|
|
2761
2861
|
if (!this.#currentInferenceSpan) return;
|
|
2762
|
-
this.#currentInferenceSpan.end({
|
|
2862
|
+
this.#currentInferenceSpan.end({
|
|
2863
|
+
attributes: {
|
|
2864
|
+
finishReason: "interrupted",
|
|
2865
|
+
...this.#inferenceStreamAttributes()
|
|
2866
|
+
},
|
|
2867
|
+
...termination ?? (typeof coreObservability.agentLoopTermination === "function" ? {
|
|
2868
|
+
outcome: "interrupted",
|
|
2869
|
+
stopReason: null,
|
|
2870
|
+
stopReasonContext: null
|
|
2871
|
+
} : {})
|
|
2872
|
+
});
|
|
2763
2873
|
this.#currentInferenceSpan = void 0;
|
|
2874
|
+
this.#inferenceCompletionStartTime = void 0;
|
|
2875
|
+
this.#inferenceTimeToFirstTokenMs = void 0;
|
|
2876
|
+
}
|
|
2877
|
+
/** End the open inference span, if any, with `error` and what it streamed. */
|
|
2878
|
+
failInference(error) {
|
|
2879
|
+
this.#endChunkSpan();
|
|
2880
|
+
if (!this.#currentInferenceSpan) return;
|
|
2881
|
+
this.#currentInferenceSpan.error({
|
|
2882
|
+
error,
|
|
2883
|
+
...coreObservability.executionFailureTermination?.(error, "model_error"),
|
|
2884
|
+
endSpan: true,
|
|
2885
|
+
attributes: this.#inferenceStreamAttributes()
|
|
2886
|
+
});
|
|
2887
|
+
this.#inferenceTimeToFirstTokenMs = void 0;
|
|
2888
|
+
this.#currentInferenceSpan = void 0;
|
|
2889
|
+
this.#inferenceCompletionStartTime = void 0;
|
|
2764
2890
|
}
|
|
2765
2891
|
/**
|
|
2766
2892
|
* Update the current step span with additional payload data.
|
|
@@ -2782,10 +2908,11 @@ var ModelSpanTracker = class {
|
|
|
2782
2908
|
/**
|
|
2783
2909
|
* End the current Model execution step with token usage, finish reason, output, and metadata
|
|
2784
2910
|
*/
|
|
2785
|
-
#endStepSpan(payload) {
|
|
2911
|
+
#endStepSpan(payload, termination) {
|
|
2786
2912
|
this.#endChunkSpan();
|
|
2787
2913
|
if (!this.#currentStepSpan) return;
|
|
2788
|
-
const
|
|
2914
|
+
const output = payload.output;
|
|
2915
|
+
const { usage: rawUsage, ...otherOutput } = output;
|
|
2789
2916
|
const stepResult = payload.stepResult;
|
|
2790
2917
|
const metadata = payload.metadata;
|
|
2791
2918
|
const usage = extractUsageMetrics(rawUsage, metadata?.providerMetadata);
|
|
@@ -2801,6 +2928,11 @@ var ModelSpanTracker = class {
|
|
|
2801
2928
|
]) delete cleanMetadata[key];
|
|
2802
2929
|
this.#endInferenceSpan(payload);
|
|
2803
2930
|
this.#currentStepSpan.end({
|
|
2931
|
+
...termination ?? coreObservability.agentLoopTermination?.({
|
|
2932
|
+
stopCause: "model_finished",
|
|
2933
|
+
finishReason: stepResult.reason,
|
|
2934
|
+
tripwire: output.steps?.at(-1)?.tripwire
|
|
2935
|
+
}),
|
|
2804
2936
|
output: otherOutput,
|
|
2805
2937
|
attributes: {
|
|
2806
2938
|
usage,
|
|
@@ -2832,26 +2964,23 @@ var ModelSpanTracker = class {
|
|
|
2832
2964
|
warnings: []
|
|
2833
2965
|
},
|
|
2834
2966
|
metadata: {}
|
|
2835
|
-
}
|
|
2967
|
+
}, coreObservability.agentLoopTermination?.({
|
|
2968
|
+
finishReason: "suspended",
|
|
2969
|
+
suspension: {
|
|
2970
|
+
approvalRequired: chunk.type === "tool-call-approval",
|
|
2971
|
+
toolCallId: chunk.payload.toolCallId,
|
|
2972
|
+
toolName: chunk.payload.toolName
|
|
2973
|
+
}
|
|
2974
|
+
}));
|
|
2836
2975
|
}
|
|
2837
|
-
/**
|
|
2838
|
-
* Returns the parent span for chunks. Chunks parent under MODEL_INFERENCE
|
|
2839
|
-
* (the provider call) when available, falling back to MODEL_STEP only if
|
|
2840
|
-
* startStep() was bypassed.
|
|
2841
|
-
*/
|
|
2976
|
+
/** Processed content belongs to the logical step, independently of provider timing. */
|
|
2842
2977
|
#chunkParent() {
|
|
2843
|
-
return this.#
|
|
2978
|
+
return this.#currentStepSpan?.endTime ? void 0 : this.#currentStepSpan;
|
|
2844
2979
|
}
|
|
2845
|
-
/**
|
|
2846
|
-
* Safety-net invoked from chunk handlers: auto-create MODEL_STEP and
|
|
2847
|
-
* MODEL_INFERENCE if a chunk arrives before the loop has explicitly opened
|
|
2848
|
-
* them, so chunks parent under MODEL_INFERENCE rather than falling through
|
|
2849
|
-
* to MODEL_STEP. Idempotent — each public start* method is itself a no-op
|
|
2850
|
-
* when its span is already live.
|
|
2851
|
-
*/
|
|
2980
|
+
/** Ensure a logical step and provider timing span exist for legacy stream callers. */
|
|
2852
2981
|
#ensureStepAndInference() {
|
|
2853
2982
|
if (!this.#currentStepSpan) this.startStep();
|
|
2854
|
-
if (!this.#currentInferenceSpan) this.#openInference();
|
|
2983
|
+
if (!this.#currentInferenceSpan && !this.#stepHasInference) this.#openInference();
|
|
2855
2984
|
}
|
|
2856
2985
|
/**
|
|
2857
2986
|
* Create a new chunk span (for multi-part chunks like text-start/delta/end)
|
|
@@ -3012,20 +3141,39 @@ var ModelSpanTracker = class {
|
|
|
3012
3141
|
tracingPolicy: this.#modelSpan?.tracingPolicy
|
|
3013
3142
|
})) this.#chunkSequence++;
|
|
3014
3143
|
}
|
|
3144
|
+
/** Track provider timing and completion without recording unprocessed content. */
|
|
3145
|
+
wrapInferenceStream(stream) {
|
|
3146
|
+
this.#tracksProviderStream = true;
|
|
3147
|
+
return this.wrapStream(stream, true);
|
|
3148
|
+
}
|
|
3015
3149
|
/**
|
|
3016
3150
|
* Wraps a stream with model tracing transform to track MODEL_STEP and MODEL_CHUNK spans.
|
|
3017
3151
|
*
|
|
3018
3152
|
* This should be added to the stream pipeline to automatically
|
|
3019
3153
|
* create MODEL_STEP and MODEL_CHUNK spans for each semantic unit in the stream.
|
|
3020
3154
|
*/
|
|
3021
|
-
wrapStream(stream) {
|
|
3155
|
+
wrapStream(stream, inferenceOnly = false) {
|
|
3022
3156
|
return stream.pipeThrough(new TransformStream({ transform: (chunk, controller) => {
|
|
3023
|
-
if (
|
|
3157
|
+
if (inferenceOnly) {
|
|
3158
|
+
try {
|
|
3159
|
+
if (MODEL_RESPONSE_CHUNK_TYPES.has(chunk.type)) this.#captureCompletionStartTime();
|
|
3160
|
+
if (chunk.type === "finish") this.#endInferenceSpan(chunk.payload, false);
|
|
3161
|
+
} catch {}
|
|
3162
|
+
controller.enqueue(chunk);
|
|
3163
|
+
return;
|
|
3164
|
+
}
|
|
3165
|
+
this.recordChunk(chunk);
|
|
3166
|
+
controller.enqueue(chunk);
|
|
3167
|
+
} }));
|
|
3168
|
+
}
|
|
3169
|
+
/** @internal Record a chunk only after output processors accept it. */
|
|
3170
|
+
recordChunk(chunk) {
|
|
3171
|
+
try {
|
|
3172
|
+
if (!this.#tracksProviderStream && (this.#inModelResponse || this.#deferStepClose) && MODEL_RESPONSE_CHUNK_TYPES.has(chunk.type)) this.#captureCompletionStartTime();
|
|
3024
3173
|
if (chunk.type === "tool-call-suspended" || chunk.type === "tool-call-approval") {
|
|
3025
3174
|
if (chunk.type === "tool-call-approval") this.#handleToolApprovalChunk(chunk);
|
|
3026
3175
|
this.#endSuspendedStep(chunk);
|
|
3027
3176
|
}
|
|
3028
|
-
controller.enqueue(chunk);
|
|
3029
3177
|
switch (chunk.type) {
|
|
3030
3178
|
case "text-start":
|
|
3031
3179
|
case "text-delta":
|
|
@@ -3051,7 +3199,7 @@ var ModelSpanTracker = class {
|
|
|
3051
3199
|
this.#inModelResponse = true;
|
|
3052
3200
|
if (this.#currentStepSpan) this.updateStep(chunk.payload);
|
|
3053
3201
|
else this.startStep(chunk.payload);
|
|
3054
|
-
if (!this.#currentInferenceSpan) this.#openInference(chunk.payload);
|
|
3202
|
+
if (!this.#currentInferenceSpan && !this.#stepHasInference) this.#openInference(chunk.payload);
|
|
3055
3203
|
break;
|
|
3056
3204
|
case "step-finish":
|
|
3057
3205
|
this.#inModelResponse = false;
|
|
@@ -3061,15 +3209,20 @@ var ModelSpanTracker = class {
|
|
|
3061
3209
|
this.#endInferenceSpan(chunk.payload);
|
|
3062
3210
|
} else this.#endStepSpan(chunk.payload);
|
|
3063
3211
|
break;
|
|
3212
|
+
case "tripwire": {
|
|
3213
|
+
const termination = coreObservability.agentLoopTermination?.({ tripwire: chunk.payload });
|
|
3214
|
+
if (!termination) break;
|
|
3215
|
+
this.endAttempt(termination);
|
|
3216
|
+
break;
|
|
3217
|
+
}
|
|
3218
|
+
case "finish": break;
|
|
3064
3219
|
case "raw":
|
|
3065
3220
|
case "start":
|
|
3066
|
-
case "finish":
|
|
3067
3221
|
case "response-metadata":
|
|
3068
3222
|
case "source":
|
|
3069
3223
|
case "file":
|
|
3070
3224
|
case "error":
|
|
3071
3225
|
case "abort":
|
|
3072
|
-
case "tripwire":
|
|
3073
3226
|
case "watch":
|
|
3074
3227
|
case "tool-error":
|
|
3075
3228
|
case "reasoning-signature":
|
|
@@ -3094,7 +3247,7 @@ var ModelSpanTracker = class {
|
|
|
3094
3247
|
break;
|
|
3095
3248
|
}
|
|
3096
3249
|
}
|
|
3097
|
-
} }
|
|
3250
|
+
} catch {}
|
|
3098
3251
|
}
|
|
3099
3252
|
};
|
|
3100
3253
|
// src/spans/metadata.ts
|
|
@@ -3283,6 +3436,9 @@ var BaseSpan = class BaseSpan {
|
|
|
3283
3436
|
observabilityInstance;
|
|
3284
3437
|
input;
|
|
3285
3438
|
output;
|
|
3439
|
+
outcome;
|
|
3440
|
+
stopReason;
|
|
3441
|
+
stopReasonContext;
|
|
3286
3442
|
errorInfo;
|
|
3287
3443
|
metadata;
|
|
3288
3444
|
requestContext;
|
|
@@ -3299,11 +3455,7 @@ var BaseSpan = class BaseSpan {
|
|
|
3299
3455
|
parentSpanId;
|
|
3300
3456
|
/** Parent from an external tracing system (ambient OTel / dd-trace span) that Mastra did not create; carried for external correlation, not part of Mastra's own parent/child linkage */
|
|
3301
3457
|
externalParentSpanId;
|
|
3302
|
-
/** Native span fields (see `SpanData`); carried as supplied and exported only when set */
|
|
3303
3458
|
resumedFromSpanId;
|
|
3304
|
-
outcome;
|
|
3305
|
-
stopReason;
|
|
3306
|
-
stopReasonContext;
|
|
3307
3459
|
/** Deep clean options for serialization */
|
|
3308
3460
|
deepCleanOptions;
|
|
3309
3461
|
/**
|
|
@@ -3331,6 +3483,7 @@ var BaseSpan = class BaseSpan {
|
|
|
3331
3483
|
* live `parent`, so its original ancestors are only known from the cached export.
|
|
3332
3484
|
*/
|
|
3333
3485
|
restoredLineage;
|
|
3486
|
+
restoredIsRootSpan;
|
|
3334
3487
|
/**
|
|
3335
3488
|
* Version declared for this span's own entity, or inherited from a parent span
|
|
3336
3489
|
* of the same entity. Unlike `metadata.entityVersionId`, never an ancestor's
|
|
@@ -3351,6 +3504,7 @@ var BaseSpan = class BaseSpan {
|
|
|
3351
3504
|
constructor(options, observabilityInstance) {
|
|
3352
3505
|
const observabilityConfig = observabilityInstance.getConfig();
|
|
3353
3506
|
this.deepCleanOptions = mergeSerializationOptions(observabilityConfig.serializationOptions);
|
|
3507
|
+
this.applyOutcome(options);
|
|
3354
3508
|
this.name = options.name;
|
|
3355
3509
|
this.type = options.type;
|
|
3356
3510
|
this.isInternal = isSpanInternal(this.type, options.tracingPolicy?.internal);
|
|
@@ -3371,9 +3525,6 @@ var BaseSpan = class BaseSpan {
|
|
|
3371
3525
|
this.tags = !options.parent && options.tags?.length ? options.tags : void 0;
|
|
3372
3526
|
this.links = validLinks(options.links);
|
|
3373
3527
|
this.resumedFromSpanId = options.resumedFromSpanId;
|
|
3374
|
-
this.outcome = options.outcome;
|
|
3375
|
-
this.stopReason = options.stopReason;
|
|
3376
|
-
this.stopReasonContext = deepClean(options.stopReasonContext, this.deepCleanOptions);
|
|
3377
3528
|
const entityParent = this.getParentSpan(false);
|
|
3378
3529
|
this.entityType = options.entityType ?? entityParent?.entityType;
|
|
3379
3530
|
this.entityId = options.entityId ?? entityParent?.entityId;
|
|
@@ -3415,14 +3566,49 @@ var BaseSpan = class BaseSpan {
|
|
|
3415
3566
|
return value;
|
|
3416
3567
|
}
|
|
3417
3568
|
}
|
|
3418
|
-
/**
|
|
3569
|
+
/** Preserve explicitly supplied native fields; never derive results from payloads/errors. */
|
|
3570
|
+
applyOutcome(options) {
|
|
3571
|
+
if (this.endTime) return;
|
|
3572
|
+
if (options.outcome !== void 0) {
|
|
3573
|
+
this.outcome = options.outcome;
|
|
3574
|
+
this.stopReason = options.stopReason ?? null;
|
|
3575
|
+
this.stopReasonContext = options.stopReasonContext == null ? null : deepClean(options.stopReasonContext, this.deepCleanOptions);
|
|
3576
|
+
return;
|
|
3577
|
+
}
|
|
3578
|
+
if (options.stopReason !== void 0 && options.stopReason !== this.stopReason) this.stopReasonContext = null;
|
|
3579
|
+
if (options.stopReason !== void 0) this.stopReason = options.stopReason;
|
|
3580
|
+
if (options.stopReasonContext !== void 0) this.stopReasonContext = options.stopReasonContext == null ? null : deepClean(options.stopReasonContext, this.deepCleanOptions);
|
|
3581
|
+
}
|
|
3582
|
+
/** Forward options to open descendants, with interruption outcomes for cascading termination. */
|
|
3419
3583
|
endTree(options) {
|
|
3420
|
-
|
|
3584
|
+
this.endOpenDescendants(options, true);
|
|
3421
3585
|
this.end(options);
|
|
3422
3586
|
}
|
|
3423
|
-
/**
|
|
3424
|
-
endOpenDescendants() {
|
|
3425
|
-
|
|
3587
|
+
/** Preserve completed children; only known cascading termination interrupts open work. */
|
|
3588
|
+
endOpenDescendants(options, forwardOptions = false) {
|
|
3589
|
+
const outcome = options?.outcome !== void 0 ? options.outcome : this.outcome;
|
|
3590
|
+
const stopReason = options?.stopReason !== void 0 ? options.stopReason : this.stopReason;
|
|
3591
|
+
const stopReasonContext = options?.stopReasonContext !== void 0 ? options.stopReasonContext : this.stopReasonContext;
|
|
3592
|
+
const interrupts = outcome === "abort" || outcome === "interrupted" || outcome === "limit" && stopReason === "timeout";
|
|
3593
|
+
const forwarded = forwardOptions ? { ...options } : {};
|
|
3594
|
+
delete forwarded.outcome;
|
|
3595
|
+
delete forwarded.stopReason;
|
|
3596
|
+
delete forwarded.stopReasonContext;
|
|
3597
|
+
if (this.#openChildren) for (const child of [...this.#openChildren]) {
|
|
3598
|
+
const childOptions = {
|
|
3599
|
+
...forwarded,
|
|
3600
|
+
...interrupts ? {
|
|
3601
|
+
outcome: "interrupted",
|
|
3602
|
+
stopReason,
|
|
3603
|
+
stopReasonContext
|
|
3604
|
+
} : {}
|
|
3605
|
+
};
|
|
3606
|
+
if (forwardOptions) child.endTree(childOptions);
|
|
3607
|
+
else child.end({
|
|
3608
|
+
...childOptions,
|
|
3609
|
+
endTree: true
|
|
3610
|
+
});
|
|
3611
|
+
}
|
|
3426
3612
|
}
|
|
3427
3613
|
/**
|
|
3428
3614
|
* Release this span from its parent's open-child set once it has ended.
|
|
@@ -3473,7 +3659,7 @@ var BaseSpan = class BaseSpan {
|
|
|
3473
3659
|
}
|
|
3474
3660
|
/** Returns `TRUE` if the span is the root span of a trace */
|
|
3475
3661
|
get isRootSpan() {
|
|
3476
|
-
return !this.parent;
|
|
3662
|
+
return this.restoredIsRootSpan ?? !this.parent;
|
|
3477
3663
|
}
|
|
3478
3664
|
/**
|
|
3479
3665
|
* Get the closest parent span.
|
|
@@ -3537,6 +3723,7 @@ var BaseSpan = class BaseSpan {
|
|
|
3537
3723
|
*/
|
|
3538
3724
|
restoreEntityLineage(cached) {
|
|
3539
3725
|
this.ownEntityVersionId = cached.ownEntityVersionId ?? void 0;
|
|
3726
|
+
this.restoredIsRootSpan = cached.isRootSpan;
|
|
3540
3727
|
const lineage = definedLineage(cached);
|
|
3541
3728
|
if (Object.keys(lineage).length > 0) {
|
|
3542
3729
|
this.restoredLineage = lineage;
|
|
@@ -3619,6 +3806,7 @@ var BaseSpan = class BaseSpan {
|
|
|
3619
3806
|
const nestedUnderParent = this.traceState?.nestedUnderParent ?? false;
|
|
3620
3807
|
const links = validLinks(this.links);
|
|
3621
3808
|
const correlation = this.getCorrelationContext();
|
|
3809
|
+
const timeToFirstTokenMs = this.attributes?.timeToFirstTokenMs;
|
|
3622
3810
|
return {
|
|
3623
3811
|
id: this.id,
|
|
3624
3812
|
traceId: this.traceId,
|
|
@@ -3634,6 +3822,9 @@ var BaseSpan = class BaseSpan {
|
|
|
3634
3822
|
input: hideInput ? void 0 : this.input,
|
|
3635
3823
|
output: hideOutput ? void 0 : this.output,
|
|
3636
3824
|
errorInfo: this.errorInfo,
|
|
3825
|
+
...this.outcome !== void 0 ? { outcome: this.outcome } : {},
|
|
3826
|
+
...this.stopReason !== void 0 ? { stopReason: this.stopReason } : {},
|
|
3827
|
+
...this.stopReasonContext !== void 0 ? { stopReasonContext: this.stopReasonContext } : {},
|
|
3637
3828
|
requestContext: this.requestContext,
|
|
3638
3829
|
isEvent: this.isEvent,
|
|
3639
3830
|
isRootSpan: this.isRootSpan,
|
|
@@ -3643,9 +3834,7 @@ var BaseSpan = class BaseSpan {
|
|
|
3643
3834
|
parentSpanId: this.getParentSpanId(includeInternalSpans),
|
|
3644
3835
|
externalParentSpanId: this.getExportedExternalParentSpanId(includeInternalSpans),
|
|
3645
3836
|
...this.resumedFromSpanId !== void 0 ? { resumedFromSpanId: this.resumedFromSpanId } : {},
|
|
3646
|
-
...
|
|
3647
|
-
...this.stopReason !== void 0 ? { stopReason: this.stopReason } : {},
|
|
3648
|
-
...this.stopReasonContext !== void 0 ? { stopReasonContext: this.stopReasonContext } : {},
|
|
3837
|
+
...typeof timeToFirstTokenMs === "number" ? { timeToFirstTokenMs } : {},
|
|
3649
3838
|
...nestedUnderParent ? { nestedUnderParent: true } : {},
|
|
3650
3839
|
...links ? { links } : {},
|
|
3651
3840
|
...this.isRootSpan && !nestedUnderParent && this.tags?.length ? { tags: this.tags } : {}
|
|
@@ -3755,7 +3944,9 @@ var DefaultSpan = class extends BaseSpan {
|
|
|
3755
3944
|
end(options) {
|
|
3756
3945
|
if (this.isEvent) return;
|
|
3757
3946
|
if (this.endTime) return;
|
|
3758
|
-
if (options?.endTree) this.endOpenDescendants();
|
|
3947
|
+
if (options?.endTree) this.endOpenDescendants(options);
|
|
3948
|
+
if (options) this.applyOutcome(options);
|
|
3949
|
+
if (this.outcome === null) this.outcome = "unknown";
|
|
3759
3950
|
this.endTime = /* @__PURE__ */ new Date();
|
|
3760
3951
|
this.detachFromParent();
|
|
3761
3952
|
if (options?.metadata) this.metadata = {
|
|
@@ -3770,8 +3961,9 @@ var DefaultSpan = class extends BaseSpan {
|
|
|
3770
3961
|
};
|
|
3771
3962
|
}
|
|
3772
3963
|
error(options) {
|
|
3773
|
-
if (this.isEvent) return;
|
|
3964
|
+
if (this.isEvent || this.endTime) return;
|
|
3774
3965
|
const { error, endSpan = true, endTree, attributes, metadata } = options;
|
|
3966
|
+
this.applyOutcome(options);
|
|
3775
3967
|
if (metadata) this.metadata = {
|
|
3776
3968
|
...this.metadata,
|
|
3777
3969
|
...deepClean(this.prepareSpanMetadata(metadata), this.deepCleanOptions)
|
|
@@ -3806,6 +3998,7 @@ var DefaultSpan = class extends BaseSpan {
|
|
|
3806
3998
|
}
|
|
3807
3999
|
update(options) {
|
|
3808
4000
|
if (this.isEvent) return;
|
|
4001
|
+
this.applyOutcome(options);
|
|
3809
4002
|
if (options.name !== void 0) this.name = options.name;
|
|
3810
4003
|
if (options.metadata) this.metadata = {
|
|
3811
4004
|
...this.metadata,
|
|
@@ -5275,13 +5468,13 @@ var BaseObservabilityInstance = class extends MastraBase {
|
|
|
5275
5468
|
...rest,
|
|
5276
5469
|
metadata
|
|
5277
5470
|
}, this);
|
|
5278
|
-
} else if (!this.shouldSample(customSamplerOptions)) return new NoOpSpan({
|
|
5471
|
+
} else if (!(options.samplingDecision ?? this.shouldSample(customSamplerOptions))) return new NoOpSpan({
|
|
5279
5472
|
...rest,
|
|
5280
5473
|
metadata
|
|
5281
5474
|
}, this);
|
|
5282
5475
|
let traceState;
|
|
5283
5476
|
if (options.parent) traceState = options.parent.traceState;
|
|
5284
|
-
else traceState = this.computeTraceState(tracingOptions);
|
|
5477
|
+
else traceState = options.traceState ?? this.computeTraceState(tracingOptions);
|
|
5285
5478
|
const tracingMetadata = !options.parent ? tracingOptions?.metadata : void 0;
|
|
5286
5479
|
const mergedMetadata = mergeMetadata$1(stripUndefined(metadata), stripUndefined(tracingMetadata));
|
|
5287
5480
|
const enrichedMetadata = this.extractMetadataFromRequestContext(requestContext, mergedMetadata, traceState);
|
|
@@ -5336,6 +5529,11 @@ var BaseObservabilityInstance = class extends MastraBase {
|
|
|
5336
5529
|
* @returns A span that can have lifecycle methods called on it
|
|
5337
5530
|
*/
|
|
5338
5531
|
rebuildSpan(cached) {
|
|
5532
|
+
if (cached.id === "no-op" && cached.traceId === "no-op-trace") return new NoOpSpan({
|
|
5533
|
+
name: cached.name,
|
|
5534
|
+
type: cached.type,
|
|
5535
|
+
traceState: cached.traceState
|
|
5536
|
+
}, this);
|
|
5339
5537
|
const span = this.createSpan({
|
|
5340
5538
|
name: cached.name,
|
|
5341
5539
|
type: cached.type,
|
|
@@ -5359,6 +5557,11 @@ var BaseObservabilityInstance = class extends MastraBase {
|
|
|
5359
5557
|
traceState: this.rebuildTraceState(cached)
|
|
5360
5558
|
});
|
|
5361
5559
|
if (span instanceof BaseSpan) span.restoreEntityLineage(cached);
|
|
5560
|
+
if (cached.endTime) {
|
|
5561
|
+
span.endTime = cached.endTime instanceof Date ? cached.endTime : new Date(cached.endTime);
|
|
5562
|
+
span.output = cached.output;
|
|
5563
|
+
span.errorInfo = cached.errorInfo;
|
|
5564
|
+
}
|
|
5362
5565
|
this.wireSpanLifecycle(span);
|
|
5363
5566
|
return span;
|
|
5364
5567
|
}
|
|
@@ -9123,7 +9326,11 @@ var TestExporter = class extends BaseExporter {
|
|
|
9123
9326
|
if (span.parentSpanId && spanIdMap.has(span.parentSpanId)) normalizedSpan.parentId = spanIdMap.get(span.parentSpanId);
|
|
9124
9327
|
if (span.entityType) normalizedSpan.entityType = span.entityType;
|
|
9125
9328
|
if (span.entityId) normalizedSpan.entityId = normalizeValue(span.entityId, "entityId");
|
|
9126
|
-
if (span.attributes && Object.keys(span.attributes).length > 0)
|
|
9329
|
+
if (span.attributes && Object.keys(span.attributes).length > 0) {
|
|
9330
|
+
const attributes = normalizeValue(span.attributes);
|
|
9331
|
+
if (typeof attributes.timeToFirstTokenMs === "number") attributes.timeToFirstTokenMs = "<ms>";
|
|
9332
|
+
normalizedSpan.attributes = attributes;
|
|
9333
|
+
}
|
|
9127
9334
|
if (span.metadata && Object.keys(span.metadata).length > 0) normalizedSpan.metadata = normalizeValue(span.metadata);
|
|
9128
9335
|
if (span.input !== void 0) normalizedSpan.input = normalizeValue(span.input);
|
|
9129
9336
|
if (span.output !== void 0) normalizedSpan.output = normalizeValue(span.output);
|
|
@@ -10015,6 +10222,7 @@ var SensitiveDataFilter = class {
|
|
|
10015
10222
|
span.input = this.tryFilter(span.input, indexedState);
|
|
10016
10223
|
span.output = this.tryFilter(span.output, indexedState);
|
|
10017
10224
|
span.errorInfo = this.tryFilter(span.errorInfo, indexedState);
|
|
10225
|
+
span.stopReasonContext = this.tryFilter(span.stopReasonContext, indexedState);
|
|
10018
10226
|
span.requestContext = this.tryFilter(span.requestContext, indexedState);
|
|
10019
10227
|
return span;
|
|
10020
10228
|
}
|