@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.
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 +4 -7
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +3 -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 +18 -60
  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 -266
  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,512 +0,0 @@
1
- import {
2
- Context,
3
- Attributes,
4
- propagation,
5
- context as otelContext,
6
- } from "@opentelemetry/api";
7
- import {
8
- SpanProcessor,
9
- ReadableSpan,
10
- Span,
11
- BatchSpanProcessor,
12
- SpanExporter,
13
- } from "@opentelemetry/sdk-trace-base";
14
- import { Resource, resourceFromAttributes } from "@opentelemetry/resources";
15
- import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
16
- import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
17
- import { ExportResult, ExportResultCode } from "@opentelemetry/core";
18
- import { randomUUID } from "crypto";
19
- import type { AdvancedOptions } from "../types.js";
20
- import { logger, withOtlpHttpsProxy } from "../utils.js";
21
- import {
22
- isOpenInferenceSpan,
23
- replaceOpenInferenceWithGenAI,
24
- isVercelAISpan,
25
- convertVercelAIToGenAI,
26
- } from "../converters/index.js";
27
-
28
- /**
29
- * Extended span type that includes both v1 and v2 instrumentation properties.
30
- * OpenTelemetry SDK v2 uses instrumentationScope, while v1 uses instrumentationLibrary.
31
- */
32
- type CompatibleSpan = ReadableSpan & {
33
- instrumentationLibrary?: unknown;
34
- instrumentationScope?: unknown;
35
- };
36
-
37
- /**
38
- * Instrumentation scopes of Introspection's own infrastructure spans — the
39
- * proxy layer's `introspection-proxy-call` spans
40
- * (`@introspection-sdk/introspection-proxy`) and the runtime's
41
- * `introspection-api-call` spans (`introspection-api-client`). These are
42
- * exempt from the gen_ai-only export gate: they carry transport shape only
43
- * and are correlated to a conversation via baggage or a pre-stamped
44
- * `gen_ai.conversation.id` attribute.
45
- */
46
- const INTROSPECTION_INFRA_SCOPES: ReadonlySet<string> = new Set([
47
- "@introspection-sdk/introspection-proxy",
48
- "introspection-api-client",
49
- ]);
50
-
51
- /**
52
- * End-user identity baggage keys → their semconv span-attribute keys. The
53
- * baggage keys use the `identify()` underscore form; the span attributes use
54
- * the dotted semconv form. Shared by the gen_ai and infra-span paths so the
55
- * translation cannot drift between them.
56
- */
57
- const IDENTITY_BAGGAGE_TO_ATTRIBUTE: Readonly<Record<string, string>> = {
58
- "identity.user_id": "identity.user.id",
59
- "identity.anonymous_id": "identity.anonymous.id",
60
- };
61
-
62
- /**
63
- * A {@link ReadableSpan} wrapper that substitutes the original attributes with
64
- * a converted set while delegating every other property to the original span.
65
- *
66
- * Used internally by {@link IntrospectionSpanProcessor} to convert
67
- * OpenInference attributes to Gen AI semantic conventions on-the-fly.
68
- */
69
- class ConvertedReadableSpan implements ReadableSpan {
70
- private _original: ReadableSpan;
71
- private _convertedAttributes: Attributes;
72
- private _resource?: Resource;
73
-
74
- constructor(
75
- original: ReadableSpan,
76
- convertedAttributes: Attributes,
77
- resource?: Resource,
78
- ) {
79
- this._original = original;
80
- this._convertedAttributes = convertedAttributes;
81
- this._resource = resource;
82
- }
83
-
84
- get name() {
85
- return this._original.name;
86
- }
87
- get kind() {
88
- return this._original.kind;
89
- }
90
- spanContext() {
91
- return this._original.spanContext();
92
- }
93
- get parentSpanContext() {
94
- return this._original.parentSpanContext;
95
- }
96
- get startTime() {
97
- return this._original.startTime;
98
- }
99
- get endTime() {
100
- return this._original.endTime;
101
- }
102
- get status() {
103
- return this._original.status;
104
- }
105
- get links() {
106
- return this._original.links;
107
- }
108
- get events() {
109
- return this._original.events;
110
- }
111
- get duration() {
112
- return this._original.duration;
113
- }
114
- get ended() {
115
- return this._original.ended;
116
- }
117
- get resource() {
118
- return this._resource ?? this._original.resource;
119
- }
120
- get instrumentationScope() {
121
- return this._original.instrumentationScope;
122
- }
123
- get droppedAttributesCount() {
124
- return this._original.droppedAttributesCount;
125
- }
126
- get droppedEventsCount() {
127
- return this._original.droppedEventsCount;
128
- }
129
- get droppedLinksCount() {
130
- return this._original.droppedLinksCount;
131
- }
132
-
133
- get attributes() {
134
- return this._convertedAttributes;
135
- }
136
- }
137
-
138
- export interface IntrospectionSpanProcessorOptions {
139
- /** Authentication token (env: INTROSPECTION_TOKEN) */
140
- token?: string;
141
- /**
142
- * Service name for telemetry (env: INTROSPECTION_SERVICE_NAME).
143
- * Note: For spans, the service name is typically set on the TracerProvider's resource.
144
- * This option is provided for consistency but the TracerProvider's resource takes precedence.
145
- */
146
- serviceName?: string;
147
- /** Advanced options for configuration and testing */
148
- advanced?: AdvancedOptions;
149
- }
150
-
151
- /**
152
- * OTel {@link SpanProcessor} that forwards traces to the Introspection backend
153
- * via OTLP, automatically converting any OpenInference spans to Gen AI
154
- * semantic convention attributes.
155
- *
156
- * @example
157
- * ```ts
158
- * import { IntrospectionSpanProcessor } from "@introspection-sdk/introspection-node";
159
- *
160
- * const processor = new IntrospectionSpanProcessor({ token: "sk-intro-…" });
161
- * tracerProvider.addSpanProcessor(processor);
162
- * ```
163
- */
164
- export class IntrospectionSpanProcessor implements SpanProcessor {
165
- private _spanProcessor: SpanProcessor;
166
- private _serviceName?: string;
167
- private _conversationIds: Map<string, string> = new Map();
168
-
169
- constructor(options: IntrospectionSpanProcessorOptions = {}) {
170
- const token = options.token || process.env.INTROSPECTION_TOKEN;
171
- if (!token) {
172
- throw new Error("INTROSPECTION_TOKEN is required");
173
- }
174
-
175
- const serviceName =
176
- options.serviceName ||
177
- process.env.INTROSPECTION_SERVICE_NAME ||
178
- undefined; // Falls back to TracerProvider's resource
179
-
180
- this._serviceName = serviceName;
181
-
182
- const advanced = options.advanced || {};
183
- const baseUrl =
184
- advanced.baseUrl ||
185
- process.env.INTROSPECTION_BASE_OTEL_URL ||
186
- "https://otel.introspection.dev";
187
-
188
- const headers: Record<string, string> = {
189
- Authorization: `Bearer ${token}`,
190
- ...(advanced.additionalHeaders || {}),
191
- };
192
-
193
- // Construct endpoint URL
194
- let endpoint: string;
195
- if (baseUrl.endsWith("/v1/traces")) {
196
- endpoint = baseUrl;
197
- } else {
198
- endpoint = `${baseUrl.replace(/\/$/, "")}/v1/traces`;
199
- }
200
-
201
- logger.info(
202
- `IntrospectionSpanProcessor initialized: endpoint=${endpoint}${serviceName ? `, serviceName=${serviceName}` : ""}`,
203
- );
204
-
205
- // Use custom spanExporter if provided (for testing), otherwise create OTLP exporter
206
- const baseExporter: SpanExporter =
207
- advanced.spanExporter ||
208
- new OTLPTraceExporter(
209
- withOtlpHttpsProxy({
210
- url: endpoint,
211
- headers,
212
- }),
213
- );
214
-
215
- // Wrap exporter to add v1/v2 compatibility and basic validation
216
- const exporter: SpanExporter = {
217
- export: (
218
- spans: ReadableSpan[],
219
- resultCallback: (result: ExportResult) => void,
220
- ) => {
221
- // Filter and fix spans: ensure they have required properties and add v1 compatibility
222
- const validSpans = spans
223
- .filter((span) => {
224
- // Basic validation: span must have name and resource
225
- if (!span?.name || !span.resource) {
226
- return false;
227
- }
228
- // Must have either instrumentationLibrary or instrumentationScope
229
- const compatSpan = span as CompatibleSpan;
230
- return !!(
231
- compatSpan.instrumentationLibrary ||
232
- compatSpan.instrumentationScope
233
- );
234
- })
235
- .map((span) => span as CompatibleSpan)
236
- .map((span) => {
237
- // Compatibility: add instrumentationLibrary for spans that only have instrumentationScope (v2)
238
- const compatSpan = span as CompatibleSpan;
239
- if (
240
- compatSpan.instrumentationScope &&
241
- !compatSpan.instrumentationLibrary
242
- ) {
243
- compatSpan.instrumentationLibrary =
244
- compatSpan.instrumentationScope;
245
- }
246
- return span;
247
- });
248
-
249
- if (validSpans.length === 0) {
250
- resultCallback({ code: ExportResultCode.SUCCESS });
251
- return;
252
- }
253
-
254
- // Export with error handling
255
- try {
256
- baseExporter.export(validSpans, (result) => {
257
- if (result.code === ExportResultCode.SUCCESS) {
258
- logger.debug(
259
- `Exported ${validSpans.length} span(s) to ${endpoint}`,
260
- );
261
- } else {
262
- logger.error(
263
- `Failed to export ${validSpans.length} span(s): ${result.error?.message || "Unknown error"}`,
264
- );
265
- }
266
- resultCallback(result);
267
- });
268
- } catch (error) {
269
- logger.error(
270
- `Export error: ${error instanceof Error ? error.message : String(error)}`,
271
- );
272
- resultCallback({
273
- code: ExportResultCode.FAILED,
274
- error: error instanceof Error ? error : new Error(String(error)),
275
- });
276
- }
277
- },
278
- shutdown: () => baseExporter.shutdown(),
279
- forceFlush: () => {
280
- if (baseExporter.forceFlush) {
281
- return baseExporter.forceFlush();
282
- }
283
- return Promise.resolve();
284
- },
285
- };
286
-
287
- // Use BatchSpanProcessor like logfire does
288
- this._spanProcessor = new BatchSpanProcessor(exporter);
289
- }
290
-
291
- /**
292
- * Called when a new span is started; delegates to the inner batch processor.
293
- *
294
- * @param span - The span that was just started.
295
- * @param parentContext - The parent {@link Context}.
296
- */
297
- onStart(span: Span, parentContext: Context): void {
298
- this._spanProcessor.onStart(span, parentContext);
299
- }
300
-
301
- /**
302
- * Called when a span ends. If the span originates from an OpenInference
303
- * instrumentor, its attributes are converted to `gen_ai.*` semconv keys
304
- * before being forwarded.
305
- *
306
- * @param span - The completed {@link ReadableSpan}.
307
- */
308
- onEnd(span: ReadableSpan): void {
309
- const compatSpan = span as CompatibleSpan;
310
- const scopeName =
311
- (compatSpan.instrumentationScope as { name?: string })?.name ||
312
- (compatSpan.instrumentationLibrary as { name?: string })?.name;
313
-
314
- // Introspection's own infra spans bypass the gen_ai gate below. Every
315
- // `gen_ai.*` / `introspection.*` baggage entry in scope is projected onto
316
- // the span (baggage wins over a pre-stamped attribute, matching the
317
- // gen_ai path), and end-user identity baggage maps to its semconv
318
- // attributes the same way. Out-of-process emitters (the sandbox CLI
319
- // preload) have no baggage and pre-stamp the same attributes from the
320
- // environment instead. A conversation id is never auto-generated for
321
- // infra spans — an uncorrelated span must not mint a synthetic
322
- // conversation.
323
- if (scopeName && INTROSPECTION_INFRA_SCOPES.has(scopeName)) {
324
- const infraBaggage = propagation.getBaggage(otelContext.active());
325
- const baggageAttrs: Record<string, string> = {};
326
- for (const [key, entry] of infraBaggage?.getAllEntries() ?? []) {
327
- if (key.startsWith("gen_ai.") || key.startsWith("introspection.")) {
328
- baggageAttrs[key] = entry.value;
329
- continue;
330
- }
331
- const identityKey = IDENTITY_BAGGAGE_TO_ATTRIBUTE[key];
332
- if (identityKey) {
333
- baggageAttrs[identityKey] = entry.value;
334
- }
335
- }
336
- let infraResource: Resource | undefined;
337
- if (this._serviceName) {
338
- infraResource = span.resource.merge(
339
- resourceFromAttributes({ [ATTR_SERVICE_NAME]: this._serviceName }),
340
- );
341
- }
342
- this._spanProcessor.onEnd(
343
- Object.keys(baggageAttrs).length > 0 || infraResource
344
- ? new ConvertedReadableSpan(
345
- span,
346
- { ...span.attributes, ...baggageAttrs } as Attributes,
347
- infraResource,
348
- )
349
- : span,
350
- );
351
- return;
352
- }
353
-
354
- const isOI =
355
- isOpenInferenceSpan(scopeName) ||
356
- typeof span.attributes["openinference.span.kind"] === "string";
357
- const isVercel = isVercelAISpan(span.attributes);
358
-
359
- // Skip Vercel AI SDK wrapper spans (ai.streamText, ai.generateText).
360
- // They duplicate the child doStream/doGenerate output without proper
361
- // input message windows, causing empty user messages in conversation view.
362
- // This applies regardless of whether OI attributes are also present.
363
- const aiOperationId = span.attributes["ai.operationId"];
364
- if (
365
- typeof aiOperationId === "string" &&
366
- !String(aiOperationId).includes(".do")
367
- ) {
368
- return;
369
- }
370
-
371
- // Skip spans that have no LLM-relevant data — they are infrastructure spans
372
- // (e.g. Next.js route resolution, HTTP spans) that should not be exported.
373
- const hasGenAi =
374
- isOI ||
375
- isVercel ||
376
- span.attributes["gen_ai.provider.name"] != null ||
377
- span.attributes["gen_ai.operation.name"] != null ||
378
- span.attributes["gen_ai.request.model"] != null ||
379
- span.attributes["gen_ai.input.messages"] != null ||
380
- span.attributes["gen_ai.output.messages"] != null;
381
-
382
- if (!hasGenAi) return;
383
-
384
- let attrs: Record<string, unknown>;
385
- if (isOI) {
386
- attrs = {
387
- ...(replaceOpenInferenceWithGenAI(span.attributes) as Record<
388
- string,
389
- unknown
390
- >),
391
- };
392
- // When both OI and Vercel attrs present, merge Vercel-specific
393
- // enrichments (conversation ID from metadata, etc.)
394
- if (isVercel) {
395
- const vercelAttrs = convertVercelAIToGenAI(span.attributes);
396
- for (const [key, value] of Object.entries(vercelAttrs)) {
397
- // Prefer Vercel output messages when they contain reasoning.
398
- // Either spelling counts: converters emit `"reasoning"`, but a part
399
- // that has been through ingest normalization says `"thinking"`.
400
- if (
401
- key === "gen_ai.output.messages" &&
402
- typeof value === "string" &&
403
- (value.includes('"reasoning"') || value.includes('"thinking"'))
404
- ) {
405
- attrs[key] = value;
406
- } else if (attrs[key] === undefined) {
407
- attrs[key] = value;
408
- }
409
- }
410
- }
411
- logger.debug(`Converting OpenInference span: ${span.name}`);
412
- } else if (isVercel) {
413
- attrs = {
414
- ...span.attributes,
415
- ...convertVercelAIToGenAI(span.attributes),
416
- };
417
- logger.debug(`Converting Vercel AI span: ${span.name}`);
418
- } else {
419
- attrs = { ...span.attributes };
420
- }
421
- delete attrs["gen_ai.system"];
422
-
423
- // Read baggage from active context
424
- const baggage = propagation.getBaggage(otelContext.active());
425
-
426
- // Conversation ID: baggage > existing attr > auto-generate per trace
427
- const baggageConvId = baggage?.getEntry("gen_ai.conversation.id")?.value;
428
- const existingConvId = attrs["gen_ai.conversation.id"] as
429
- string | undefined;
430
- if (baggageConvId) {
431
- attrs["gen_ai.conversation.id"] = baggageConvId;
432
- } else if (!existingConvId) {
433
- const traceId = span.spanContext().traceId;
434
- if (!this._conversationIds.has(traceId)) {
435
- this._conversationIds.set(
436
- traceId,
437
- `intro_conv_${randomUUID().replace(/-/g, "")}`,
438
- );
439
- }
440
- attrs["gen_ai.conversation.id"] = this._conversationIds.get(traceId)!;
441
- }
442
-
443
- // Default gen_ai.operation.name to "chat" for spans with messages but no operation name
444
- if (
445
- !attrs["gen_ai.operation.name"] &&
446
- (attrs["gen_ai.input.messages"] || attrs["gen_ai.output.messages"])
447
- ) {
448
- attrs["gen_ai.operation.name"] = "chat";
449
- }
450
-
451
- // Agent name / id from baggage (baggage wins so per-call agent identity
452
- // overrides any default stamped at instrumentor-construction time).
453
- const baggageAgentName = baggage?.getEntry("gen_ai.agent.name")?.value;
454
- if (baggageAgentName) {
455
- attrs["gen_ai.agent.name"] = baggageAgentName;
456
- }
457
- const baggageAgentId = baggage?.getEntry("gen_ai.agent.id")?.value;
458
- if (baggageAgentId) {
459
- attrs["gen_ai.agent.id"] = baggageAgentId;
460
- }
461
-
462
- // End-user identity from baggage. Set once at the run root (by the
463
- // caller's `identify()` or, for sandbox/agent runs, by the runtime from
464
- // the task's `metadata.identity`); every child span — including provider
465
- // spans this SDK doesn't author — inherits it via OTel context
466
- // propagation. Mirrors the `identify()` attributes so agent-run spans are
467
- // identity-scoped the same way directly-instrumented app spans are. The
468
- // baggage→attribute key translation is shared with the infra-span path
469
- // via IDENTITY_BAGGAGE_TO_ATTRIBUTE.
470
- for (const [baggageKey, attributeKey] of Object.entries(
471
- IDENTITY_BAGGAGE_TO_ATTRIBUTE,
472
- )) {
473
- const value = baggage?.getEntry(baggageKey)?.value;
474
- if (value) {
475
- attrs[attributeKey] = value;
476
- }
477
- }
478
-
479
- // Override resource with service name if provided
480
- let resource: Resource | undefined;
481
- if (this._serviceName) {
482
- resource = span.resource.merge(
483
- resourceFromAttributes({ [ATTR_SERVICE_NAME]: this._serviceName }),
484
- );
485
- }
486
-
487
- const processedSpan = new ConvertedReadableSpan(
488
- span,
489
- attrs as Attributes,
490
- resource,
491
- );
492
- this._spanProcessor.onEnd(processedSpan);
493
- }
494
-
495
- /**
496
- * Shut down the inner batch processor and its exporter.
497
- *
498
- * @returns A promise that resolves once all pending spans are flushed.
499
- */
500
- async shutdown(): Promise<void> {
501
- await this._spanProcessor.shutdown();
502
- }
503
-
504
- /**
505
- * Force-flush any buffered spans to the Introspection backend.
506
- *
507
- * @returns A promise that resolves once the flush completes.
508
- */
509
- async forceFlush(): Promise<void> {
510
- await this._spanProcessor.forceFlush();
511
- }
512
- }