@introspection-sdk/introspection-node 0.19.1 → 0.21.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/README.md +28 -8
- package/dist/auth.d.ts +1 -1
- package/dist/client.d.ts +4 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +16 -7
- package/dist/client.js.map +1 -1
- package/dist/dev-target.js +1 -1
- package/dist/index.d.ts +4 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -6
- package/dist/index.js.map +1 -1
- package/dist/otel/index.d.ts +1 -14
- package/dist/otel/index.d.ts.map +1 -1
- package/dist/otel/index.js +10 -18
- package/dist/otel/index.js.map +1 -1
- package/dist/otel/init.d.ts +19 -21
- package/dist/otel/init.d.ts.map +1 -1
- package/dist/otel/init.js +62 -48
- package/dist/otel/init.js.map +1 -1
- package/dist/otel/integrations/base.d.ts +17 -37
- package/dist/otel/integrations/base.d.ts.map +1 -1
- package/dist/otel/integrations/base.js +6 -13
- package/dist/otel/integrations/base.js.map +1 -1
- package/dist/otel/integrations/index.d.ts +3 -4
- package/dist/otel/integrations/index.d.ts.map +1 -1
- package/dist/otel/integrations/index.js +6 -25
- package/dist/otel/integrations/index.js.map +1 -1
- package/dist/otel/integrations/pi.d.ts.map +1 -1
- package/dist/otel/integrations/pi.js +9 -1
- package/dist/otel/integrations/pi.js.map +1 -1
- package/dist/otel/logs.d.ts +41 -8
- package/dist/otel/logs.d.ts.map +1 -1
- package/dist/otel/logs.js +95 -66
- package/dist/otel/logs.js.map +1 -1
- package/dist/otel/pi.d.ts +18 -5
- package/dist/otel/pi.d.ts.map +1 -1
- package/dist/otel/pi.js +38 -9
- package/dist/otel/pi.js.map +1 -1
- package/dist/otel/register.d.ts +2 -2
- package/dist/otel/register.d.ts.map +1 -1
- package/dist/otel/register.js +7 -9
- package/dist/otel/register.js.map +1 -1
- package/dist/otel/setup.d.ts +2 -2
- package/dist/otel/setup.d.ts.map +1 -1
- package/dist/otel/setup.js +16 -6
- package/dist/otel/setup.js.map +1 -1
- package/dist/otel/span-processor.d.ts +18 -5
- package/dist/otel/span-processor.d.ts.map +1 -1
- package/dist/otel/span-processor.js +50 -65
- package/dist/otel/span-processor.js.map +1 -1
- package/dist/resources/experiments.d.ts +10 -5
- package/dist/resources/experiments.d.ts.map +1 -1
- package/dist/resources/experiments.js +9 -25
- package/dist/resources/experiments.js.map +1 -1
- package/dist/resources/recipes.d.ts +6 -6
- package/dist/resources/recipes.d.ts.map +1 -1
- package/dist/resources/recipes.js +5 -23
- package/dist/resources/recipes.js.map +1 -1
- package/dist/resources/runtimes.d.ts +2 -2
- package/dist/resources/runtimes.d.ts.map +1 -1
- package/dist/resources/runtimes.js +2 -2
- package/dist/resources/runtimes.js.map +1 -1
- package/dist/runner.d.ts +8 -4
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +26 -18
- package/dist/runner.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +45 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +61 -2
- package/dist/utils.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +18 -60
- package/.turbo/turbo-build.log +0 -4
- package/dist/converters/claude.d.ts +0 -150
- package/dist/converters/claude.d.ts.map +0 -1
- package/dist/converters/claude.js +0 -211
- package/dist/converters/claude.js.map +0 -1
- package/dist/converters/gemini.d.ts +0 -90
- package/dist/converters/gemini.d.ts.map +0 -1
- package/dist/converters/gemini.js +0 -171
- package/dist/converters/gemini.js.map +0 -1
- package/dist/converters/index.d.ts +0 -9
- package/dist/converters/index.d.ts.map +0 -1
- package/dist/converters/index.js +0 -9
- package/dist/converters/index.js.map +0 -1
- package/dist/converters/openai.d.ts +0 -51
- package/dist/converters/openai.d.ts.map +0 -1
- package/dist/converters/openai.js +0 -269
- package/dist/converters/openai.js.map +0 -1
- package/dist/converters/openinference.d.ts +0 -107
- package/dist/converters/openinference.d.ts.map +0 -1
- package/dist/converters/openinference.js +0 -553
- package/dist/converters/openinference.js.map +0 -1
- package/dist/converters/vercel.d.ts +0 -21
- package/dist/converters/vercel.d.ts.map +0 -1
- package/dist/converters/vercel.js +0 -236
- package/dist/converters/vercel.js.map +0 -1
- package/dist/otel/anthropic.d.ts +0 -63
- package/dist/otel/anthropic.d.ts.map +0 -1
- package/dist/otel/anthropic.js +0 -475
- package/dist/otel/anthropic.js.map +0 -1
- package/dist/otel/claude-hooks.d.ts +0 -346
- package/dist/otel/claude-hooks.d.ts.map +0 -1
- package/dist/otel/claude-hooks.js +0 -647
- package/dist/otel/claude-hooks.js.map +0 -1
- package/dist/otel/claude-wrapper.d.ts +0 -93
- package/dist/otel/claude-wrapper.d.ts.map +0 -1
- package/dist/otel/claude-wrapper.js +0 -155
- package/dist/otel/claude-wrapper.js.map +0 -1
- package/dist/otel/gemini.d.ts +0 -56
- package/dist/otel/gemini.d.ts.map +0 -1
- package/dist/otel/gemini.js +0 -350
- package/dist/otel/gemini.js.map +0 -1
- package/dist/otel/integrations/anthropic.d.ts +0 -10
- package/dist/otel/integrations/anthropic.d.ts.map +0 -1
- package/dist/otel/integrations/anthropic.js +0 -26
- package/dist/otel/integrations/anthropic.js.map +0 -1
- package/dist/otel/integrations/claude-agent.d.ts +0 -12
- package/dist/otel/integrations/claude-agent.d.ts.map +0 -1
- package/dist/otel/integrations/claude-agent.js +0 -28
- package/dist/otel/integrations/claude-agent.js.map +0 -1
- package/dist/otel/integrations/gemini.d.ts +0 -11
- package/dist/otel/integrations/gemini.d.ts.map +0 -1
- package/dist/otel/integrations/gemini.js +0 -23
- package/dist/otel/integrations/gemini.js.map +0 -1
- package/dist/otel/integrations/langchain.d.ts +0 -15
- package/dist/otel/integrations/langchain.d.ts.map +0 -1
- package/dist/otel/integrations/langchain.js +0 -43
- package/dist/otel/integrations/langchain.js.map +0 -1
- package/dist/otel/integrations/mastra.d.ts +0 -14
- package/dist/otel/integrations/mastra.d.ts.map +0 -1
- package/dist/otel/integrations/mastra.js +0 -31
- package/dist/otel/integrations/mastra.js.map +0 -1
- package/dist/otel/integrations/openai-agents.d.ts +0 -11
- package/dist/otel/integrations/openai-agents.d.ts.map +0 -1
- package/dist/otel/integrations/openai-agents.js +0 -30
- package/dist/otel/integrations/openai-agents.js.map +0 -1
- package/dist/otel/integrations/vercel.d.ts +0 -14
- package/dist/otel/integrations/vercel.d.ts.map +0 -1
- package/dist/otel/integrations/vercel.js +0 -20
- package/dist/otel/integrations/vercel.js.map +0 -1
- package/dist/otel/langchain-handler.d.ts +0 -112
- package/dist/otel/langchain-handler.d.ts.map +0 -1
- package/dist/otel/langchain-handler.js +0 -561
- package/dist/otel/langchain-handler.js.map +0 -1
- package/dist/otel/mastra-exporter.d.ts +0 -87
- package/dist/otel/mastra-exporter.d.ts.map +0 -1
- package/dist/otel/mastra-exporter.js +0 -530
- package/dist/otel/mastra-exporter.js.map +0 -1
- package/dist/otel/openai.d.ts +0 -20
- package/dist/otel/openai.d.ts.map +0 -1
- package/dist/otel/openai.js +0 -55
- package/dist/otel/openai.js.map +0 -1
- package/dist/otel/tracing-processor.d.ts +0 -139
- package/dist/otel/tracing-processor.d.ts.map +0 -1
- package/dist/otel/tracing-processor.js +0 -434
- package/dist/otel/tracing-processor.js.map +0 -1
- package/eslint.config.mjs +0 -4
- package/src/auth.ts +0 -236
- package/src/client.ts +0 -163
- package/src/converters/claude.ts +0 -343
- package/src/converters/gemini.ts +0 -243
- package/src/converters/index.ts +0 -50
- package/src/converters/openai.ts +0 -300
- package/src/converters/openinference.ts +0 -665
- package/src/converters/vercel.ts +0 -302
- package/src/dev-target.ts +0 -79
- package/src/http.ts +0 -43
- package/src/index.ts +0 -266
- package/src/otel/anthropic.ts +0 -619
- package/src/otel/claude-hooks.ts +0 -1116
- package/src/otel/claude-wrapper.ts +0 -239
- package/src/otel/gemini.ts +0 -483
- package/src/otel/index.ts +0 -128
- package/src/otel/init.ts +0 -411
- package/src/otel/integrations/anthropic.ts +0 -36
- package/src/otel/integrations/base.ts +0 -158
- package/src/otel/integrations/claude-agent.ts +0 -36
- package/src/otel/integrations/gemini.ts +0 -30
- package/src/otel/integrations/index.ts +0 -152
- package/src/otel/integrations/langchain.ts +0 -60
- package/src/otel/integrations/mastra.ts +0 -48
- package/src/otel/integrations/openai-agents.ts +0 -47
- package/src/otel/integrations/pi.ts +0 -28
- package/src/otel/integrations/vercel.ts +0 -26
- package/src/otel/langchain-handler.ts +0 -804
- package/src/otel/logs.ts +0 -395
- package/src/otel/mastra-exporter.ts +0 -714
- package/src/otel/openai.ts +0 -93
- package/src/otel/pi.ts +0 -94
- package/src/otel/register.ts +0 -50
- package/src/otel/setup.ts +0 -215
- package/src/otel/span-processor.ts +0 -512
- package/src/otel/tracing-processor.ts +0 -637
- package/src/pagination.ts +0 -10
- package/src/proxy-from-env.d.ts +0 -7
- package/src/resources/experiments.ts +0 -176
- package/src/resources/recipes.ts +0 -74
- package/src/resources/runtimes.ts +0 -31
- package/src/runner.ts +0 -213
- package/src/types.ts +0 -13
- package/src/utils.ts +0 -95
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -8
|
@@ -1,714 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mastra-compatible Exporter for Introspection.
|
|
3
|
-
*
|
|
4
|
-
* Extends Mastra's BaseExporter to receive TracingEvents (Mastra's native
|
|
5
|
-
* span format) and convert them to gen_ai.* OTel spans for the Introspection
|
|
6
|
-
* backend.
|
|
7
|
-
*
|
|
8
|
-
* **Important:** This module requires `@mastra/observability` and `@mastra/core`
|
|
9
|
-
* at runtime. Import via the subpath: `@introspection-sdk/introspection-node/mastra`
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```ts
|
|
13
|
-
* import { Mastra } from "@mastra/core/mastra";
|
|
14
|
-
* import { IntrospectionMastraExporter } from "@introspection-sdk/introspection-node/mastra";
|
|
15
|
-
*
|
|
16
|
-
* const mastra = new Mastra({
|
|
17
|
-
* agents: { myAgent },
|
|
18
|
-
* observability: {
|
|
19
|
-
* configs: {
|
|
20
|
-
* otel: {
|
|
21
|
-
* serviceName: "my-mastra-app",
|
|
22
|
-
* exporters: [new IntrospectionMastraExporter()],
|
|
23
|
-
* },
|
|
24
|
-
* },
|
|
25
|
-
* },
|
|
26
|
-
* });
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
import { BaseExporter } from "@mastra/observability";
|
|
31
|
-
import type {
|
|
32
|
-
TracingEvent,
|
|
33
|
-
InitExporterOptions,
|
|
34
|
-
AnyExportedSpan,
|
|
35
|
-
} from "@mastra/core/observability";
|
|
36
|
-
|
|
37
|
-
import {
|
|
38
|
-
context as otelContext,
|
|
39
|
-
trace as otelTrace,
|
|
40
|
-
type Span as OtelSpan,
|
|
41
|
-
} from "@opentelemetry/api";
|
|
42
|
-
import {
|
|
43
|
-
BasicTracerProvider,
|
|
44
|
-
BatchSpanProcessor,
|
|
45
|
-
SimpleSpanProcessor,
|
|
46
|
-
type SpanExporter,
|
|
47
|
-
type IdGenerator,
|
|
48
|
-
} from "@opentelemetry/sdk-trace-base";
|
|
49
|
-
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
50
|
-
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
51
|
-
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
52
|
-
import { randomUUID } from "crypto";
|
|
53
|
-
|
|
54
|
-
import { logger, withOtlpHttpsProxy } from "../utils.js";
|
|
55
|
-
import { VERSION } from "../version.js";
|
|
56
|
-
import type {
|
|
57
|
-
InputMessage,
|
|
58
|
-
OutputMessage,
|
|
59
|
-
MessagePart,
|
|
60
|
-
} from "@introspection-sdk/types";
|
|
61
|
-
import { providerCostAttributes } from "@introspection-sdk/types";
|
|
62
|
-
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
// Configuration
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
|
|
67
|
-
/** Advanced options for testing and customization. */
|
|
68
|
-
export interface MastraExporterAdvancedOptions {
|
|
69
|
-
/** Custom span exporter for testing. */
|
|
70
|
-
spanExporter?: SpanExporter;
|
|
71
|
-
/** Custom ID generator for testing. */
|
|
72
|
-
idGenerator?: IdGenerator;
|
|
73
|
-
/** Use SimpleSpanProcessor instead of BatchSpanProcessor. */
|
|
74
|
-
useSimpleSpanProcessor?: boolean;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Configuration for {@link IntrospectionMastraExporter}. */
|
|
78
|
-
export interface IntrospectionMastraExporterOptions {
|
|
79
|
-
/** Authentication token (env: INTROSPECTION_TOKEN). */
|
|
80
|
-
token?: string;
|
|
81
|
-
/** Base URL for the API (env: INTROSPECTION_BASE_OTEL_URL). */
|
|
82
|
-
baseUrl?: string;
|
|
83
|
-
/** Additional headers to include in requests. */
|
|
84
|
-
additionalHeaders?: Record<string, string>;
|
|
85
|
-
/** Enable debug logging. */
|
|
86
|
-
debug?: boolean;
|
|
87
|
-
/** Advanced options for testing. */
|
|
88
|
-
advanced?: MastraExporterAdvancedOptions;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
// Exporter
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
|
|
95
|
-
export class IntrospectionMastraExporter extends BaseExporter {
|
|
96
|
-
name = "introspection";
|
|
97
|
-
|
|
98
|
-
private _options: IntrospectionMastraExporterOptions;
|
|
99
|
-
private _tracerProvider?: BasicTracerProvider;
|
|
100
|
-
private _tracer?: ReturnType<BasicTracerProvider["getTracer"]>;
|
|
101
|
-
private _conversationIds: Map<string, string> = new Map();
|
|
102
|
-
// Track root OTel spans per Mastra traceId so children share the same trace
|
|
103
|
-
private _rootSpans: Map<string, OtelSpan> = new Map();
|
|
104
|
-
|
|
105
|
-
constructor(options: IntrospectionMastraExporterOptions = {}) {
|
|
106
|
-
super({ logLevel: options.debug ? "debug" : "info" });
|
|
107
|
-
this._options = options;
|
|
108
|
-
|
|
109
|
-
const token = options.token || process.env.INTROSPECTION_TOKEN;
|
|
110
|
-
if (!token && !options.advanced?.spanExporter) {
|
|
111
|
-
this.setDisabled("INTROSPECTION_TOKEN is required");
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Called by Mastra after the instance is fully configured.
|
|
117
|
-
* We defer TracerProvider creation to here so we can use the service name.
|
|
118
|
-
*/
|
|
119
|
-
init(options: InitExporterOptions): void {
|
|
120
|
-
if (this.isDisabled) return;
|
|
121
|
-
|
|
122
|
-
const config = options.config as Record<string, unknown> | undefined;
|
|
123
|
-
const serviceName = (config?.["serviceName"] as string) || "mastra-app";
|
|
124
|
-
|
|
125
|
-
this._initProvider(serviceName);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private _initProvider(serviceName: string): void {
|
|
129
|
-
if (this._tracerProvider) return; // Already initialized
|
|
130
|
-
|
|
131
|
-
const opts = this._options;
|
|
132
|
-
const advanced = opts.advanced;
|
|
133
|
-
|
|
134
|
-
const resource = resourceFromAttributes({
|
|
135
|
-
[ATTR_SERVICE_NAME]: serviceName,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
if (advanced?.spanExporter) {
|
|
139
|
-
const sp = advanced.useSimpleSpanProcessor
|
|
140
|
-
? new SimpleSpanProcessor(advanced.spanExporter)
|
|
141
|
-
: new BatchSpanProcessor(advanced.spanExporter, {
|
|
142
|
-
scheduledDelayMillis: 100,
|
|
143
|
-
});
|
|
144
|
-
this._tracerProvider = new BasicTracerProvider({
|
|
145
|
-
resource,
|
|
146
|
-
idGenerator: advanced.idGenerator,
|
|
147
|
-
spanProcessors: [sp],
|
|
148
|
-
});
|
|
149
|
-
logger.info("IntrospectionMastraExporter initialized in test mode");
|
|
150
|
-
} else {
|
|
151
|
-
const token = opts.token || process.env.INTROSPECTION_TOKEN;
|
|
152
|
-
if (!token) return;
|
|
153
|
-
|
|
154
|
-
const baseUrl =
|
|
155
|
-
opts.baseUrl ||
|
|
156
|
-
process.env.INTROSPECTION_BASE_OTEL_URL ||
|
|
157
|
-
"https://otel.introspection.dev";
|
|
158
|
-
const endpoint = baseUrl.endsWith("/v1/traces")
|
|
159
|
-
? baseUrl
|
|
160
|
-
: `${baseUrl.replace(/\/$/, "")}/v1/traces`;
|
|
161
|
-
|
|
162
|
-
const headers: Record<string, string> = {
|
|
163
|
-
"User-Agent": `introspection-sdk/${VERSION}`,
|
|
164
|
-
Authorization: `Bearer ${token}`,
|
|
165
|
-
...opts.additionalHeaders,
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const spanExporter = new OTLPTraceExporter(
|
|
169
|
-
withOtlpHttpsProxy({ url: endpoint, headers }),
|
|
170
|
-
);
|
|
171
|
-
const effectiveBatchSize =
|
|
172
|
-
token.startsWith("intro_dev") || token.startsWith("intro_staging")
|
|
173
|
-
? 1
|
|
174
|
-
: undefined;
|
|
175
|
-
const useSimple =
|
|
176
|
-
advanced?.useSimpleSpanProcessor || effectiveBatchSize === 1;
|
|
177
|
-
|
|
178
|
-
const sp = useSimple
|
|
179
|
-
? new SimpleSpanProcessor(spanExporter)
|
|
180
|
-
: new BatchSpanProcessor(spanExporter, {
|
|
181
|
-
scheduledDelayMillis: 1000,
|
|
182
|
-
...(effectiveBatchSize
|
|
183
|
-
? { maxExportBatchSize: effectiveBatchSize }
|
|
184
|
-
: {}),
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
this._tracerProvider = new BasicTracerProvider({
|
|
188
|
-
resource,
|
|
189
|
-
idGenerator: advanced?.idGenerator,
|
|
190
|
-
spanProcessors: [sp],
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
logger.info(
|
|
194
|
-
`IntrospectionMastraExporter initialized: endpoint=${endpoint}, serviceName=${serviceName}`,
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
this._tracer = this._tracerProvider.getTracer(
|
|
199
|
-
"introspection-mastra",
|
|
200
|
-
VERSION,
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
protected async _exportTracingEvent(event: TracingEvent): Promise<void> {
|
|
205
|
-
if (this.isDisabled) return;
|
|
206
|
-
if (event.type !== "span_ended") return;
|
|
207
|
-
|
|
208
|
-
// Lazy init if init() wasn't called
|
|
209
|
-
if (!this._tracerProvider) {
|
|
210
|
-
this._initProvider("mastra-app");
|
|
211
|
-
}
|
|
212
|
-
if (!this._tracer) return;
|
|
213
|
-
|
|
214
|
-
this._exportSpan(event.exportedSpan);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
async flush(): Promise<void> {
|
|
218
|
-
await this._tracerProvider?.forceFlush();
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
async shutdown(): Promise<void> {
|
|
222
|
-
// End any open root spans
|
|
223
|
-
for (const span of this._rootSpans.values()) span.end();
|
|
224
|
-
this._rootSpans.clear();
|
|
225
|
-
await this._tracerProvider?.shutdown();
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// -------------------------------------------------------------------------
|
|
229
|
-
// Span routing
|
|
230
|
-
// -------------------------------------------------------------------------
|
|
231
|
-
|
|
232
|
-
private _exportSpan(span: AnyExportedSpan): void {
|
|
233
|
-
switch (span.type as string) {
|
|
234
|
-
case "agent_run":
|
|
235
|
-
this._exportAgentRun(span);
|
|
236
|
-
break;
|
|
237
|
-
case "model_step":
|
|
238
|
-
this._exportModelStep(span);
|
|
239
|
-
break;
|
|
240
|
-
case "tool_call":
|
|
241
|
-
case "mcp_tool_call":
|
|
242
|
-
this._exportToolCall(span);
|
|
243
|
-
break;
|
|
244
|
-
// model_generation — skipped; model_step has per-step tool calls
|
|
245
|
-
default:
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// -------------------------------------------------------------------------
|
|
251
|
-
// Span creation with shared trace context
|
|
252
|
-
// -------------------------------------------------------------------------
|
|
253
|
-
|
|
254
|
-
/** Ensure a root OTel span exists for this Mastra trace.
|
|
255
|
-
* Events arrive in end-order (children first, agent_run last),
|
|
256
|
-
* so we create a synthetic root on first encounter. */
|
|
257
|
-
private _ensureRoot(span: AnyExportedSpan): void {
|
|
258
|
-
if (this._rootSpans.has(span.traceId)) return;
|
|
259
|
-
const rootSpan = this._tracer!.startSpan("trace", {
|
|
260
|
-
startTime: span.startTime,
|
|
261
|
-
});
|
|
262
|
-
this._rootSpans.set(span.traceId, rootSpan);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private _createSpan(span: AnyExportedSpan, name?: string): OtelSpan {
|
|
266
|
-
this._ensureRoot(span);
|
|
267
|
-
const parentCtx = otelTrace.setSpan(
|
|
268
|
-
otelContext.active(),
|
|
269
|
-
this._rootSpans.get(span.traceId)!,
|
|
270
|
-
);
|
|
271
|
-
return this._tracer!.startSpan(
|
|
272
|
-
name || span.name,
|
|
273
|
-
{ startTime: span.startTime },
|
|
274
|
-
parentCtx,
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// -------------------------------------------------------------------------
|
|
279
|
-
// Span converters
|
|
280
|
-
// -------------------------------------------------------------------------
|
|
281
|
-
|
|
282
|
-
private _exportAgentRun(span: AnyExportedSpan): void {
|
|
283
|
-
const attrs = span.attributes as Record<string, unknown> | undefined;
|
|
284
|
-
const conversationId = this._getConversationId(span);
|
|
285
|
-
|
|
286
|
-
// agent_run arrives last — set attrs on the synthetic root and end it
|
|
287
|
-
this._ensureRoot(span);
|
|
288
|
-
const rootSpan = this._rootSpans.get(span.traceId)!;
|
|
289
|
-
rootSpan.setAttribute("gen_ai.conversation.id", conversationId);
|
|
290
|
-
rootSpan.setAttribute(
|
|
291
|
-
"gen_ai.agent.name",
|
|
292
|
-
span.entityName || span.name || "agent",
|
|
293
|
-
);
|
|
294
|
-
|
|
295
|
-
// Prefer system messages from span.input (full prompt sent to LLM) over
|
|
296
|
-
// attrs.instructions (may be just the short placeholder string).
|
|
297
|
-
const inputMsgs = Array.isArray(span.input)
|
|
298
|
-
? (span.input as Record<string, unknown>[])
|
|
299
|
-
: undefined;
|
|
300
|
-
const systemMsgsFromInput = inputMsgs?.filter((m) => m.role === "system");
|
|
301
|
-
if (systemMsgsFromInput && systemMsgsFromInput.length > 0) {
|
|
302
|
-
rootSpan.setAttribute(
|
|
303
|
-
"gen_ai.system_instructions",
|
|
304
|
-
JSON.stringify(
|
|
305
|
-
systemMsgsFromInput.map((m) => ({
|
|
306
|
-
type: "text",
|
|
307
|
-
content: this._extractTextContent(m.content),
|
|
308
|
-
})),
|
|
309
|
-
),
|
|
310
|
-
);
|
|
311
|
-
} else if (
|
|
312
|
-
attrs?.["instructions"] &&
|
|
313
|
-
typeof attrs["instructions"] === "string"
|
|
314
|
-
) {
|
|
315
|
-
rootSpan.setAttribute(
|
|
316
|
-
"gen_ai.system_instructions",
|
|
317
|
-
JSON.stringify([{ type: "text", content: attrs["instructions"] }]),
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Metadata passthrough
|
|
322
|
-
if (span.metadata) {
|
|
323
|
-
for (const [key, value] of Object.entries(span.metadata)) {
|
|
324
|
-
if (value != null) {
|
|
325
|
-
rootSpan.setAttribute(
|
|
326
|
-
`ai.telemetry.metadata.${key}`,
|
|
327
|
-
typeof value === "string" ? value : JSON.stringify(value),
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
rootSpan.end(span.endTime || new Date());
|
|
334
|
-
this._rootSpans.delete(span.traceId);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
private _exportModelStep(span: AnyExportedSpan): void {
|
|
338
|
-
const attrs = span.attributes as Record<string, unknown> | undefined;
|
|
339
|
-
const conversationId = this._getConversationId(span);
|
|
340
|
-
|
|
341
|
-
// Model info lives in metadata.modelMetadata (set by Mastra)
|
|
342
|
-
const modelMeta = span.metadata?.["modelMetadata"] as
|
|
343
|
-
Record<string, unknown> | undefined;
|
|
344
|
-
const model = (modelMeta?.["modelId"] as string) || "";
|
|
345
|
-
const provider = (modelMeta?.["modelProvider"] as string) || "";
|
|
346
|
-
|
|
347
|
-
// Response metadata from metadata.body (the API response)
|
|
348
|
-
const respBody = span.metadata?.["body"] as
|
|
349
|
-
Record<string, unknown> | undefined;
|
|
350
|
-
const responseModel = (respBody?.["model"] as string) || "";
|
|
351
|
-
const responseId = (respBody?.["id"] as string) || "";
|
|
352
|
-
|
|
353
|
-
const spanName = model ? `chat ${model}` : span.name;
|
|
354
|
-
const otelSpan = this._createSpan(span, spanName);
|
|
355
|
-
|
|
356
|
-
otelSpan.setAttribute("gen_ai.operation.name", "chat");
|
|
357
|
-
otelSpan.setAttribute("gen_ai.conversation.id", conversationId);
|
|
358
|
-
otelSpan.setAttribute("openinference.span.kind", "LLM");
|
|
359
|
-
|
|
360
|
-
if (model) otelSpan.setAttribute("gen_ai.request.model", model);
|
|
361
|
-
if (provider) otelSpan.setAttribute("gen_ai.provider.name", provider);
|
|
362
|
-
if (responseModel)
|
|
363
|
-
otelSpan.setAttribute("gen_ai.response.model", responseModel);
|
|
364
|
-
if (responseId) otelSpan.setAttribute("gen_ai.response.id", responseId);
|
|
365
|
-
|
|
366
|
-
// Input messages — span.input is either:
|
|
367
|
-
// (a) a flat normalized array { role, content }[] from Mastra's inputMessages path, or
|
|
368
|
-
// (b) an object { body: { input/messages, tools } } from the raw HTTP request path.
|
|
369
|
-
if (span.input != null) {
|
|
370
|
-
let apiMessages: Record<string, unknown>[] | undefined;
|
|
371
|
-
let tools: unknown[] | undefined;
|
|
372
|
-
|
|
373
|
-
if (Array.isArray(span.input)) {
|
|
374
|
-
// Path (a): flat array already normalized by Mastra's normalizeMessages()
|
|
375
|
-
apiMessages = span.input as Record<string, unknown>[];
|
|
376
|
-
} else {
|
|
377
|
-
// Path (b): nested request body
|
|
378
|
-
const rawInput = span.input as Record<string, unknown>;
|
|
379
|
-
const body = rawInput?.body as Record<string, unknown> | undefined;
|
|
380
|
-
const msgs = body?.input ?? body?.messages;
|
|
381
|
-
if (Array.isArray(msgs)) {
|
|
382
|
-
apiMessages = msgs as Record<string, unknown>[];
|
|
383
|
-
}
|
|
384
|
-
if (Array.isArray(body?.tools)) {
|
|
385
|
-
tools = body.tools as unknown[];
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (apiMessages && apiMessages.length > 0) {
|
|
390
|
-
const inputMessages = this._convertInput(apiMessages);
|
|
391
|
-
if (inputMessages.length > 0) {
|
|
392
|
-
otelSpan.setAttribute(
|
|
393
|
-
"gen_ai.input.messages",
|
|
394
|
-
JSON.stringify(inputMessages),
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// System instructions — combine all system messages into one attribute
|
|
399
|
-
const systemMsgs = apiMessages.filter((m) => m.role === "system");
|
|
400
|
-
if (systemMsgs.length > 0) {
|
|
401
|
-
otelSpan.setAttribute(
|
|
402
|
-
"gen_ai.system_instructions",
|
|
403
|
-
JSON.stringify(
|
|
404
|
-
systemMsgs.map((m) => ({
|
|
405
|
-
type: "text",
|
|
406
|
-
content: this._extractTextContent(m.content),
|
|
407
|
-
})),
|
|
408
|
-
),
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Tool definitions (only present on path (b))
|
|
414
|
-
if (Array.isArray(tools) && tools.length > 0) {
|
|
415
|
-
otelSpan.setAttribute(
|
|
416
|
-
"gen_ai.tool.definitions",
|
|
417
|
-
JSON.stringify(
|
|
418
|
-
(tools as Record<string, unknown>[]).map((t) => ({
|
|
419
|
-
type: (t.type as string) || "function",
|
|
420
|
-
name: (t.name as string) || "",
|
|
421
|
-
description: (t.description as string) || "",
|
|
422
|
-
parameters: t.parameters,
|
|
423
|
-
})),
|
|
424
|
-
),
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Output messages — model_step output has {text, toolCalls} per step
|
|
430
|
-
if (span.output != null) {
|
|
431
|
-
const outputMessages = this._convertOutput(
|
|
432
|
-
span.output,
|
|
433
|
-
attrs?.["finishReason"] as string | undefined,
|
|
434
|
-
);
|
|
435
|
-
if (outputMessages.length > 0) {
|
|
436
|
-
otelSpan.setAttribute(
|
|
437
|
-
"gen_ai.output.messages",
|
|
438
|
-
JSON.stringify(outputMessages),
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// Token usage (per step)
|
|
444
|
-
const usage = attrs?.["usage"] as Record<string, unknown> | undefined;
|
|
445
|
-
if (usage) {
|
|
446
|
-
if (typeof usage["inputTokens"] === "number")
|
|
447
|
-
otelSpan.setAttribute(
|
|
448
|
-
"gen_ai.usage.input_tokens",
|
|
449
|
-
usage["inputTokens"],
|
|
450
|
-
);
|
|
451
|
-
if (typeof usage["outputTokens"] === "number")
|
|
452
|
-
otelSpan.setAttribute(
|
|
453
|
-
"gen_ai.usage.output_tokens",
|
|
454
|
-
usage["outputTokens"],
|
|
455
|
-
);
|
|
456
|
-
const inputDetails = usage["inputDetails"] as
|
|
457
|
-
Record<string, unknown> | undefined;
|
|
458
|
-
if (typeof inputDetails?.["cacheRead"] === "number")
|
|
459
|
-
otelSpan.setAttribute(
|
|
460
|
-
"gen_ai.usage.cache_read.input_tokens",
|
|
461
|
-
inputDetails["cacheRead"],
|
|
462
|
-
);
|
|
463
|
-
if (typeof inputDetails?.["cacheWrite"] === "number")
|
|
464
|
-
otelSpan.setAttribute(
|
|
465
|
-
"gen_ai.usage.cache_creation.input_tokens",
|
|
466
|
-
inputDetails["cacheWrite"],
|
|
467
|
-
);
|
|
468
|
-
// Provider-reported cost (e.g. OpenRouter usage.cost) when present.
|
|
469
|
-
otelSpan.setAttributes(providerCostAttributes(usage));
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Finish reason
|
|
473
|
-
const finishReason = attrs?.["finishReason"] as string | undefined;
|
|
474
|
-
if (finishReason)
|
|
475
|
-
otelSpan.setAttribute("gen_ai.response.finish_reasons", [finishReason]);
|
|
476
|
-
|
|
477
|
-
otelSpan.end(span.endTime || new Date());
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
private _exportToolCall(span: AnyExportedSpan): void {
|
|
481
|
-
const conversationId = this._getConversationId(span);
|
|
482
|
-
const toolName = span.entityName || span.name || "tool";
|
|
483
|
-
|
|
484
|
-
const otelSpan = this._createSpan(span, `execute_tool ${toolName}`);
|
|
485
|
-
otelSpan.setAttribute("gen_ai.operation.name", "execute_tool");
|
|
486
|
-
otelSpan.setAttribute("gen_ai.tool.name", toolName);
|
|
487
|
-
otelSpan.setAttribute("gen_ai.tool.type", "function");
|
|
488
|
-
otelSpan.setAttribute("gen_ai.conversation.id", conversationId);
|
|
489
|
-
otelSpan.setAttribute("openinference.span.kind", "TOOL");
|
|
490
|
-
|
|
491
|
-
if (span.input != null) {
|
|
492
|
-
otelSpan.setAttribute(
|
|
493
|
-
"gen_ai.tool.call.arguments",
|
|
494
|
-
typeof span.input === "string"
|
|
495
|
-
? span.input
|
|
496
|
-
: JSON.stringify(span.input),
|
|
497
|
-
);
|
|
498
|
-
}
|
|
499
|
-
if (span.output != null) {
|
|
500
|
-
otelSpan.setAttribute(
|
|
501
|
-
"gen_ai.tool.call.result",
|
|
502
|
-
typeof span.output === "string"
|
|
503
|
-
? span.output
|
|
504
|
-
: JSON.stringify(span.output),
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
otelSpan.end(span.endTime || new Date());
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// -------------------------------------------------------------------------
|
|
512
|
-
// Helpers
|
|
513
|
-
// -------------------------------------------------------------------------
|
|
514
|
-
|
|
515
|
-
private _getConversationId(span: AnyExportedSpan): string {
|
|
516
|
-
const metaConvId = span.metadata?.["gen_ai.conversation.id"] as
|
|
517
|
-
string | undefined;
|
|
518
|
-
if (metaConvId) return metaConvId;
|
|
519
|
-
|
|
520
|
-
const attrs = span.attributes as Record<string, unknown> | undefined;
|
|
521
|
-
const attrConvId = attrs?.["conversationId"] as string | undefined;
|
|
522
|
-
if (attrConvId) return attrConvId;
|
|
523
|
-
|
|
524
|
-
const traceId = span.traceId;
|
|
525
|
-
if (!this._conversationIds.has(traceId)) {
|
|
526
|
-
this._conversationIds.set(
|
|
527
|
-
traceId,
|
|
528
|
-
`intro_conv_${randomUUID().replace(/-/g, "")}`,
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
return this._conversationIds.get(traceId)!;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
private _convertInput(input: unknown): InputMessage[] {
|
|
535
|
-
if (!input) return [];
|
|
536
|
-
if (Array.isArray(input)) {
|
|
537
|
-
const result: InputMessage[] = [];
|
|
538
|
-
for (const m of input) {
|
|
539
|
-
const msg = m as Record<string, unknown>;
|
|
540
|
-
if (!msg) continue;
|
|
541
|
-
|
|
542
|
-
// Skip system messages (handled separately)
|
|
543
|
-
if (msg.role === "system") continue;
|
|
544
|
-
|
|
545
|
-
// Standard role-based messages (AIv4 uses msg.content; AIv5 may use msg.parts)
|
|
546
|
-
if (msg.role) {
|
|
547
|
-
const parts =
|
|
548
|
-
msg.content != null
|
|
549
|
-
? this._extractParts(msg.content)
|
|
550
|
-
: Array.isArray(msg.parts)
|
|
551
|
-
? this._extractParts(msg.parts)
|
|
552
|
-
: [];
|
|
553
|
-
result.push({
|
|
554
|
-
role: msg.role as InputMessage["role"],
|
|
555
|
-
parts,
|
|
556
|
-
});
|
|
557
|
-
continue;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
// OpenAI Responses API: function_call_output (no role, has type)
|
|
561
|
-
if (msg.type === "function_call_output") {
|
|
562
|
-
result.push({
|
|
563
|
-
role: "tool",
|
|
564
|
-
parts: [
|
|
565
|
-
{
|
|
566
|
-
type: "tool_call_response",
|
|
567
|
-
id: (msg.call_id || "") as string,
|
|
568
|
-
response:
|
|
569
|
-
typeof msg.output === "string"
|
|
570
|
-
? msg.output
|
|
571
|
-
: JSON.stringify(msg.output),
|
|
572
|
-
},
|
|
573
|
-
],
|
|
574
|
-
});
|
|
575
|
-
continue;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// item_reference — skip (OpenAI internal reference)
|
|
579
|
-
if (msg.type === "item_reference") continue;
|
|
580
|
-
}
|
|
581
|
-
return result;
|
|
582
|
-
}
|
|
583
|
-
if (typeof input === "string") {
|
|
584
|
-
return [{ role: "user", parts: [{ type: "text", content: input }] }];
|
|
585
|
-
}
|
|
586
|
-
return [];
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
private _convertOutput(
|
|
590
|
-
output: unknown,
|
|
591
|
-
finishReason?: string,
|
|
592
|
-
): OutputMessage[] {
|
|
593
|
-
if (!output) return [];
|
|
594
|
-
const parts: MessagePart[] = [];
|
|
595
|
-
|
|
596
|
-
if (typeof output === "string") {
|
|
597
|
-
parts.push({ type: "text", content: output });
|
|
598
|
-
} else if (typeof output === "object" && output !== null) {
|
|
599
|
-
const out = output as Record<string, unknown>;
|
|
600
|
-
|
|
601
|
-
// Reasoning
|
|
602
|
-
if (Array.isArray(out.reasoning)) {
|
|
603
|
-
for (const r of out.reasoning as Record<string, unknown>[]) {
|
|
604
|
-
const text = (r.text || r.content || "") as string;
|
|
605
|
-
if (text) parts.push({ type: "reasoning", content: text });
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// Text
|
|
610
|
-
if (typeof out.text === "string" && out.text) {
|
|
611
|
-
parts.push({ type: "text", content: out.text });
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// Tool calls
|
|
615
|
-
if (Array.isArray(out.toolCalls)) {
|
|
616
|
-
for (const tc of out.toolCalls as Record<string, unknown>[]) {
|
|
617
|
-
parts.push({
|
|
618
|
-
type: "tool_call",
|
|
619
|
-
name: (tc.toolName || tc.name || "") as string,
|
|
620
|
-
id: (tc.toolCallId || tc.id || "") as string,
|
|
621
|
-
arguments:
|
|
622
|
-
typeof tc.args === "string"
|
|
623
|
-
? tc.args
|
|
624
|
-
: JSON.stringify(tc.args || tc.input),
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
if (parts.length === 0) return [];
|
|
631
|
-
return [{ role: "assistant", parts, finish_reason: finishReason }];
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/** Extract plain text from a message content field (string, array of parts, or AIv5 parts). */
|
|
635
|
-
private _extractTextContent(content: unknown): string {
|
|
636
|
-
if (typeof content === "string") return content;
|
|
637
|
-
if (Array.isArray(content)) {
|
|
638
|
-
return content
|
|
639
|
-
.map((p: unknown) => {
|
|
640
|
-
const part = p as Record<string, unknown>;
|
|
641
|
-
return typeof part.text === "string"
|
|
642
|
-
? part.text
|
|
643
|
-
: typeof part.content === "string"
|
|
644
|
-
? part.content
|
|
645
|
-
: "";
|
|
646
|
-
})
|
|
647
|
-
.filter(Boolean)
|
|
648
|
-
.join("\n");
|
|
649
|
-
}
|
|
650
|
-
return typeof content === "undefined" ? "" : JSON.stringify(content);
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
private _extractParts(content: unknown): MessagePart[] {
|
|
654
|
-
if (typeof content === "string") {
|
|
655
|
-
return [{ type: "text", content }];
|
|
656
|
-
}
|
|
657
|
-
if (Array.isArray(content)) {
|
|
658
|
-
return content
|
|
659
|
-
.map((part: unknown) => {
|
|
660
|
-
const p = part as Record<string, unknown>;
|
|
661
|
-
// Standard text
|
|
662
|
-
if (p.type === "text")
|
|
663
|
-
return {
|
|
664
|
-
type: "text" as const,
|
|
665
|
-
content: (p.text || p.content || "") as string,
|
|
666
|
-
};
|
|
667
|
-
// OpenAI Responses API input_text format
|
|
668
|
-
if (p.type === "input_text")
|
|
669
|
-
return {
|
|
670
|
-
type: "text" as const,
|
|
671
|
-
content: (p.text || "") as string,
|
|
672
|
-
};
|
|
673
|
-
// Tool call
|
|
674
|
-
if (
|
|
675
|
-
p.type === "tool-call" ||
|
|
676
|
-
p.type === "tool_call" ||
|
|
677
|
-
p.type === "function_call"
|
|
678
|
-
)
|
|
679
|
-
return {
|
|
680
|
-
type: "tool_call" as const,
|
|
681
|
-
name: (p.toolName || p.name || "") as string,
|
|
682
|
-
id: (p.toolCallId || p.id || p.call_id || "") as string,
|
|
683
|
-
arguments:
|
|
684
|
-
typeof p.args === "string"
|
|
685
|
-
? p.args
|
|
686
|
-
: typeof p.arguments === "string"
|
|
687
|
-
? p.arguments
|
|
688
|
-
: JSON.stringify(p.args || p.arguments || p.input),
|
|
689
|
-
};
|
|
690
|
-
// Tool result
|
|
691
|
-
if (
|
|
692
|
-
p.type === "tool-result" ||
|
|
693
|
-
p.type === "tool_call_response" ||
|
|
694
|
-
p.type === "function_call_output"
|
|
695
|
-
)
|
|
696
|
-
return {
|
|
697
|
-
type: "tool_call_response" as const,
|
|
698
|
-
id: (p.toolCallId || p.id || p.call_id || "") as string,
|
|
699
|
-
response:
|
|
700
|
-
typeof p.result === "string"
|
|
701
|
-
? p.result
|
|
702
|
-
: typeof p.output === "string"
|
|
703
|
-
? p.output
|
|
704
|
-
: JSON.stringify(p.result || p.output),
|
|
705
|
-
};
|
|
706
|
-
// item_reference (OpenAI Responses API) — skip
|
|
707
|
-
if (p.type === "item_reference") return null;
|
|
708
|
-
return { type: "text" as const, content: JSON.stringify(p) };
|
|
709
|
-
})
|
|
710
|
-
.filter((p) => p !== null) as MessagePart[];
|
|
711
|
-
}
|
|
712
|
-
return [];
|
|
713
|
-
}
|
|
714
|
-
}
|