@introspection-sdk/introspection-node 0.20.0 → 0.21.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.
Files changed (211) hide show
  1. package/README.md +28 -8
  2. package/dist/auth.d.ts +1 -1
  3. package/dist/client.d.ts +4 -3
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +16 -7
  6. package/dist/client.js.map +1 -1
  7. package/dist/dev-target.js +1 -1
  8. package/dist/index.d.ts +3 -7
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +2 -6
  11. package/dist/index.js.map +1 -1
  12. package/dist/otel/index.d.ts +1 -14
  13. package/dist/otel/index.d.ts.map +1 -1
  14. package/dist/otel/index.js +10 -18
  15. package/dist/otel/index.js.map +1 -1
  16. package/dist/otel/init.d.ts +19 -21
  17. package/dist/otel/init.d.ts.map +1 -1
  18. package/dist/otel/init.js +62 -48
  19. package/dist/otel/init.js.map +1 -1
  20. package/dist/otel/integrations/base.d.ts +17 -37
  21. package/dist/otel/integrations/base.d.ts.map +1 -1
  22. package/dist/otel/integrations/base.js +6 -13
  23. package/dist/otel/integrations/base.js.map +1 -1
  24. package/dist/otel/integrations/index.d.ts +3 -4
  25. package/dist/otel/integrations/index.d.ts.map +1 -1
  26. package/dist/otel/integrations/index.js +6 -25
  27. package/dist/otel/integrations/index.js.map +1 -1
  28. package/dist/otel/integrations/pi.d.ts.map +1 -1
  29. package/dist/otel/integrations/pi.js +9 -1
  30. package/dist/otel/integrations/pi.js.map +1 -1
  31. package/dist/otel/logs.d.ts +41 -8
  32. package/dist/otel/logs.d.ts.map +1 -1
  33. package/dist/otel/logs.js +95 -66
  34. package/dist/otel/logs.js.map +1 -1
  35. package/dist/otel/pi.d.ts +18 -5
  36. package/dist/otel/pi.d.ts.map +1 -1
  37. package/dist/otel/pi.js +38 -9
  38. package/dist/otel/pi.js.map +1 -1
  39. package/dist/otel/register.d.ts +2 -2
  40. package/dist/otel/register.d.ts.map +1 -1
  41. package/dist/otel/register.js +7 -9
  42. package/dist/otel/register.js.map +1 -1
  43. package/dist/otel/setup.d.ts +2 -2
  44. package/dist/otel/setup.d.ts.map +1 -1
  45. package/dist/otel/setup.js +16 -6
  46. package/dist/otel/setup.js.map +1 -1
  47. package/dist/otel/span-processor.d.ts +18 -5
  48. package/dist/otel/span-processor.d.ts.map +1 -1
  49. package/dist/otel/span-processor.js +50 -65
  50. package/dist/otel/span-processor.js.map +1 -1
  51. package/dist/resources/experiments.d.ts +10 -5
  52. package/dist/resources/experiments.d.ts.map +1 -1
  53. package/dist/resources/experiments.js +9 -25
  54. package/dist/resources/experiments.js.map +1 -1
  55. package/dist/resources/recipes.d.ts +6 -6
  56. package/dist/resources/recipes.d.ts.map +1 -1
  57. package/dist/resources/recipes.js +5 -23
  58. package/dist/resources/recipes.js.map +1 -1
  59. package/dist/resources/runtimes.d.ts +2 -2
  60. package/dist/resources/runtimes.d.ts.map +1 -1
  61. package/dist/resources/runtimes.js +2 -2
  62. package/dist/resources/runtimes.js.map +1 -1
  63. package/dist/runner.d.ts +8 -4
  64. package/dist/runner.d.ts.map +1 -1
  65. package/dist/runner.js +26 -18
  66. package/dist/runner.js.map +1 -1
  67. package/dist/types.d.ts +1 -1
  68. package/dist/types.d.ts.map +1 -1
  69. package/dist/types.js +1 -1
  70. package/dist/types.js.map +1 -1
  71. package/dist/utils.d.ts +45 -0
  72. package/dist/utils.d.ts.map +1 -1
  73. package/dist/utils.js +61 -2
  74. package/dist/utils.js.map +1 -1
  75. package/dist/version.d.ts +1 -1
  76. package/dist/version.d.ts.map +1 -1
  77. package/dist/version.js +1 -1
  78. package/dist/version.js.map +1 -1
  79. package/package.json +16 -58
  80. package/.turbo/turbo-build.log +0 -4
  81. package/dist/converters/claude.d.ts +0 -150
  82. package/dist/converters/claude.d.ts.map +0 -1
  83. package/dist/converters/claude.js +0 -211
  84. package/dist/converters/claude.js.map +0 -1
  85. package/dist/converters/gemini.d.ts +0 -90
  86. package/dist/converters/gemini.d.ts.map +0 -1
  87. package/dist/converters/gemini.js +0 -171
  88. package/dist/converters/gemini.js.map +0 -1
  89. package/dist/converters/index.d.ts +0 -9
  90. package/dist/converters/index.d.ts.map +0 -1
  91. package/dist/converters/index.js +0 -9
  92. package/dist/converters/index.js.map +0 -1
  93. package/dist/converters/openai.d.ts +0 -51
  94. package/dist/converters/openai.d.ts.map +0 -1
  95. package/dist/converters/openai.js +0 -269
  96. package/dist/converters/openai.js.map +0 -1
  97. package/dist/converters/openinference.d.ts +0 -107
  98. package/dist/converters/openinference.d.ts.map +0 -1
  99. package/dist/converters/openinference.js +0 -553
  100. package/dist/converters/openinference.js.map +0 -1
  101. package/dist/converters/vercel.d.ts +0 -21
  102. package/dist/converters/vercel.d.ts.map +0 -1
  103. package/dist/converters/vercel.js +0 -236
  104. package/dist/converters/vercel.js.map +0 -1
  105. package/dist/otel/anthropic.d.ts +0 -63
  106. package/dist/otel/anthropic.d.ts.map +0 -1
  107. package/dist/otel/anthropic.js +0 -475
  108. package/dist/otel/anthropic.js.map +0 -1
  109. package/dist/otel/claude-hooks.d.ts +0 -346
  110. package/dist/otel/claude-hooks.d.ts.map +0 -1
  111. package/dist/otel/claude-hooks.js +0 -647
  112. package/dist/otel/claude-hooks.js.map +0 -1
  113. package/dist/otel/claude-wrapper.d.ts +0 -93
  114. package/dist/otel/claude-wrapper.d.ts.map +0 -1
  115. package/dist/otel/claude-wrapper.js +0 -155
  116. package/dist/otel/claude-wrapper.js.map +0 -1
  117. package/dist/otel/gemini.d.ts +0 -56
  118. package/dist/otel/gemini.d.ts.map +0 -1
  119. package/dist/otel/gemini.js +0 -350
  120. package/dist/otel/gemini.js.map +0 -1
  121. package/dist/otel/integrations/anthropic.d.ts +0 -10
  122. package/dist/otel/integrations/anthropic.d.ts.map +0 -1
  123. package/dist/otel/integrations/anthropic.js +0 -26
  124. package/dist/otel/integrations/anthropic.js.map +0 -1
  125. package/dist/otel/integrations/claude-agent.d.ts +0 -12
  126. package/dist/otel/integrations/claude-agent.d.ts.map +0 -1
  127. package/dist/otel/integrations/claude-agent.js +0 -28
  128. package/dist/otel/integrations/claude-agent.js.map +0 -1
  129. package/dist/otel/integrations/gemini.d.ts +0 -11
  130. package/dist/otel/integrations/gemini.d.ts.map +0 -1
  131. package/dist/otel/integrations/gemini.js +0 -23
  132. package/dist/otel/integrations/gemini.js.map +0 -1
  133. package/dist/otel/integrations/langchain.d.ts +0 -15
  134. package/dist/otel/integrations/langchain.d.ts.map +0 -1
  135. package/dist/otel/integrations/langchain.js +0 -43
  136. package/dist/otel/integrations/langchain.js.map +0 -1
  137. package/dist/otel/integrations/mastra.d.ts +0 -14
  138. package/dist/otel/integrations/mastra.d.ts.map +0 -1
  139. package/dist/otel/integrations/mastra.js +0 -31
  140. package/dist/otel/integrations/mastra.js.map +0 -1
  141. package/dist/otel/integrations/openai-agents.d.ts +0 -11
  142. package/dist/otel/integrations/openai-agents.d.ts.map +0 -1
  143. package/dist/otel/integrations/openai-agents.js +0 -30
  144. package/dist/otel/integrations/openai-agents.js.map +0 -1
  145. package/dist/otel/integrations/vercel.d.ts +0 -14
  146. package/dist/otel/integrations/vercel.d.ts.map +0 -1
  147. package/dist/otel/integrations/vercel.js +0 -20
  148. package/dist/otel/integrations/vercel.js.map +0 -1
  149. package/dist/otel/langchain-handler.d.ts +0 -112
  150. package/dist/otel/langchain-handler.d.ts.map +0 -1
  151. package/dist/otel/langchain-handler.js +0 -561
  152. package/dist/otel/langchain-handler.js.map +0 -1
  153. package/dist/otel/mastra-exporter.d.ts +0 -87
  154. package/dist/otel/mastra-exporter.d.ts.map +0 -1
  155. package/dist/otel/mastra-exporter.js +0 -530
  156. package/dist/otel/mastra-exporter.js.map +0 -1
  157. package/dist/otel/openai.d.ts +0 -20
  158. package/dist/otel/openai.d.ts.map +0 -1
  159. package/dist/otel/openai.js +0 -55
  160. package/dist/otel/openai.js.map +0 -1
  161. package/dist/otel/tracing-processor.d.ts +0 -139
  162. package/dist/otel/tracing-processor.d.ts.map +0 -1
  163. package/dist/otel/tracing-processor.js +0 -434
  164. package/dist/otel/tracing-processor.js.map +0 -1
  165. package/eslint.config.mjs +0 -4
  166. package/src/auth.ts +0 -236
  167. package/src/client.ts +0 -163
  168. package/src/converters/claude.ts +0 -343
  169. package/src/converters/gemini.ts +0 -243
  170. package/src/converters/index.ts +0 -50
  171. package/src/converters/openai.ts +0 -300
  172. package/src/converters/openinference.ts +0 -665
  173. package/src/converters/vercel.ts +0 -302
  174. package/src/dev-target.ts +0 -79
  175. package/src/http.ts +0 -43
  176. package/src/index.ts +0 -277
  177. package/src/otel/anthropic.ts +0 -619
  178. package/src/otel/claude-hooks.ts +0 -1116
  179. package/src/otel/claude-wrapper.ts +0 -239
  180. package/src/otel/gemini.ts +0 -483
  181. package/src/otel/index.ts +0 -128
  182. package/src/otel/init.ts +0 -411
  183. package/src/otel/integrations/anthropic.ts +0 -36
  184. package/src/otel/integrations/base.ts +0 -158
  185. package/src/otel/integrations/claude-agent.ts +0 -36
  186. package/src/otel/integrations/gemini.ts +0 -30
  187. package/src/otel/integrations/index.ts +0 -152
  188. package/src/otel/integrations/langchain.ts +0 -60
  189. package/src/otel/integrations/mastra.ts +0 -48
  190. package/src/otel/integrations/openai-agents.ts +0 -47
  191. package/src/otel/integrations/pi.ts +0 -28
  192. package/src/otel/integrations/vercel.ts +0 -26
  193. package/src/otel/langchain-handler.ts +0 -804
  194. package/src/otel/logs.ts +0 -395
  195. package/src/otel/mastra-exporter.ts +0 -714
  196. package/src/otel/openai.ts +0 -93
  197. package/src/otel/pi.ts +0 -94
  198. package/src/otel/register.ts +0 -50
  199. package/src/otel/setup.ts +0 -215
  200. package/src/otel/span-processor.ts +0 -512
  201. package/src/otel/tracing-processor.ts +0 -637
  202. package/src/pagination.ts +0 -10
  203. package/src/proxy-from-env.d.ts +0 -7
  204. package/src/resources/experiments.ts +0 -176
  205. package/src/resources/recipes.ts +0 -74
  206. package/src/resources/runtimes.ts +0 -31
  207. package/src/runner.ts +0 -213
  208. package/src/types.ts +0 -13
  209. package/src/utils.ts +0 -95
  210. package/src/version.ts +0 -2
  211. 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
- }