@introspection-sdk/introspection-node 0.4.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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-check-types.log +5 -0
- package/.turbo/turbo-lint.log +5 -0
- package/dist/ai-sdk-integration.d.ts +123 -0
- package/dist/ai-sdk-integration.d.ts.map +1 -0
- package/dist/ai-sdk-integration.js +475 -0
- package/dist/anthropic.d.ts +54 -0
- package/dist/anthropic.d.ts.map +1 -0
- package/dist/anthropic.js +375 -0
- package/dist/claude-hooks.d.ts +328 -0
- package/dist/claude-hooks.d.ts.map +1 -0
- package/dist/claude-hooks.js +571 -0
- package/dist/claude-wrapper.d.ts +93 -0
- package/dist/claude-wrapper.d.ts.map +1 -0
- package/dist/claude-wrapper.js +154 -0
- package/dist/client.d.ts +157 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +394 -0
- package/dist/converters/ai-sdk.d.ts +65 -0
- package/dist/converters/ai-sdk.d.ts.map +1 -0
- package/dist/converters/ai-sdk.js +238 -0
- package/dist/converters/claude.d.ts +146 -0
- package/dist/converters/claude.d.ts.map +1 -0
- package/dist/converters/claude.js +202 -0
- package/dist/converters/index.d.ts +9 -0
- package/dist/converters/index.d.ts.map +1 -0
- package/dist/converters/index.js +8 -0
- package/dist/converters/openai.d.ts +51 -0
- package/dist/converters/openai.d.ts.map +1 -0
- package/dist/converters/openai.js +268 -0
- package/dist/converters/openclaw.d.ts +16 -0
- package/dist/converters/openclaw.d.ts.map +1 -0
- package/dist/converters/openclaw.js +49 -0
- package/dist/converters/openinference.d.ts +107 -0
- package/dist/converters/openinference.d.ts.map +1 -0
- package/dist/converters/openinference.js +552 -0
- package/dist/converters/vercel.d.ts +21 -0
- package/dist/converters/vercel.d.ts.map +1 -0
- package/dist/converters/vercel.js +208 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/langchain-handler.d.ts +101 -0
- package/dist/langchain-handler.d.ts.map +1 -0
- package/dist/langchain-handler.js +489 -0
- package/dist/mastra-exporter.d.ts +85 -0
- package/dist/mastra-exporter.d.ts.map +1 -0
- package/dist/mastra-exporter.js +471 -0
- package/dist/span-processor.d.ts +62 -0
- package/dist/span-processor.d.ts.map +1 -0
- package/dist/span-processor.js +310 -0
- package/dist/tracing-processor.d.ts +136 -0
- package/dist/tracing-processor.d.ts.map +1 -0
- package/dist/tracing-processor.js +368 -0
- package/dist/types/genai.d.ts +165 -0
- package/dist/types/genai.d.ts.map +1 -0
- package/dist/types/genai.js +82 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +57 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/eslint.config.mjs +4 -0
- package/package.json +78 -0
- package/src/ai-sdk-integration.ts +689 -0
- package/src/anthropic.ts +465 -0
- package/src/claude-hooks.ts +1010 -0
- package/src/claude-wrapper.ts +240 -0
- package/src/client.ts +513 -0
- package/src/converters/ai-sdk.ts +298 -0
- package/src/converters/claude.ts +334 -0
- package/src/converters/index.ts +44 -0
- package/src/converters/openai.ts +302 -0
- package/src/converters/openinference.ts +673 -0
- package/src/converters/vercel.ts +264 -0
- package/src/index.ts +121 -0
- package/src/langchain-handler.ts +722 -0
- package/src/mastra-exporter.ts +650 -0
- package/src/span-processor.ts +418 -0
- package/src/tracing-processor.ts +548 -0
- package/src/types/genai.ts +264 -0
- package/src/types.ts +13 -0
- package/src/utils.ts +69 -0
- package/src/version.ts +2 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,471 @@
|
|
|
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
|
+
import { BaseExporter } from "@mastra/observability";
|
|
30
|
+
import { context as otelContext, trace as otelTrace, } from "@opentelemetry/api";
|
|
31
|
+
import { BasicTracerProvider, BatchSpanProcessor, SimpleSpanProcessor, } from "@opentelemetry/sdk-trace-base";
|
|
32
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
33
|
+
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
34
|
+
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
35
|
+
import { randomUUID } from "crypto";
|
|
36
|
+
import { logger } from "./utils.js";
|
|
37
|
+
import { VERSION } from "./version.js";
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Exporter
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
export class IntrospectionMastraExporter extends BaseExporter {
|
|
42
|
+
name = "introspection";
|
|
43
|
+
_options;
|
|
44
|
+
_tracerProvider;
|
|
45
|
+
_tracer;
|
|
46
|
+
_conversationIds = new Map();
|
|
47
|
+
// Track root OTel spans per Mastra traceId so children share the same trace
|
|
48
|
+
_rootSpans = new Map();
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
super({ logLevel: options.debug ? "debug" : "info" });
|
|
51
|
+
this._options = options;
|
|
52
|
+
const token = options.token || process.env.INTROSPECTION_TOKEN;
|
|
53
|
+
if (!token && !options.advanced?.spanExporter) {
|
|
54
|
+
this.setDisabled("INTROSPECTION_TOKEN is required");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Called by Mastra after the instance is fully configured.
|
|
59
|
+
* We defer TracerProvider creation to here so we can use the service name.
|
|
60
|
+
*/
|
|
61
|
+
init(options) {
|
|
62
|
+
if (this.isDisabled)
|
|
63
|
+
return;
|
|
64
|
+
const config = options.config;
|
|
65
|
+
const serviceName = config?.["serviceName"] || "mastra-app";
|
|
66
|
+
this._initProvider(serviceName);
|
|
67
|
+
}
|
|
68
|
+
_initProvider(serviceName) {
|
|
69
|
+
if (this._tracerProvider)
|
|
70
|
+
return; // Already initialized
|
|
71
|
+
const opts = this._options;
|
|
72
|
+
const advanced = opts.advanced;
|
|
73
|
+
const resource = resourceFromAttributes({
|
|
74
|
+
[ATTR_SERVICE_NAME]: serviceName,
|
|
75
|
+
});
|
|
76
|
+
if (advanced?.spanExporter) {
|
|
77
|
+
const sp = advanced.useSimpleSpanProcessor
|
|
78
|
+
? new SimpleSpanProcessor(advanced.spanExporter)
|
|
79
|
+
: new BatchSpanProcessor(advanced.spanExporter, {
|
|
80
|
+
scheduledDelayMillis: 100,
|
|
81
|
+
});
|
|
82
|
+
this._tracerProvider = new BasicTracerProvider({
|
|
83
|
+
resource,
|
|
84
|
+
idGenerator: advanced.idGenerator,
|
|
85
|
+
spanProcessors: [sp],
|
|
86
|
+
});
|
|
87
|
+
logger.info("IntrospectionMastraExporter initialized in test mode");
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const token = opts.token || process.env.INTROSPECTION_TOKEN;
|
|
91
|
+
if (!token)
|
|
92
|
+
return;
|
|
93
|
+
const baseUrl = opts.baseUrl ||
|
|
94
|
+
process.env.INTROSPECTION_BASE_URL ||
|
|
95
|
+
"https://otel.introspection.dev";
|
|
96
|
+
const endpoint = baseUrl.endsWith("/v1/traces")
|
|
97
|
+
? baseUrl
|
|
98
|
+
: `${baseUrl.replace(/\/$/, "")}/v1/traces`;
|
|
99
|
+
const headers = {
|
|
100
|
+
"User-Agent": `introspection-sdk/${VERSION}`,
|
|
101
|
+
Authorization: `Bearer ${token}`,
|
|
102
|
+
...opts.additionalHeaders,
|
|
103
|
+
};
|
|
104
|
+
const spanExporter = new OTLPTraceExporter({ url: endpoint, headers });
|
|
105
|
+
const effectiveBatchSize = token.startsWith("intro_dev") || token.startsWith("intro_staging")
|
|
106
|
+
? 1
|
|
107
|
+
: undefined;
|
|
108
|
+
const useSimple = advanced?.useSimpleSpanProcessor || effectiveBatchSize === 1;
|
|
109
|
+
const sp = useSimple
|
|
110
|
+
? new SimpleSpanProcessor(spanExporter)
|
|
111
|
+
: new BatchSpanProcessor(spanExporter, {
|
|
112
|
+
scheduledDelayMillis: 1000,
|
|
113
|
+
...(effectiveBatchSize
|
|
114
|
+
? { maxExportBatchSize: effectiveBatchSize }
|
|
115
|
+
: {}),
|
|
116
|
+
});
|
|
117
|
+
this._tracerProvider = new BasicTracerProvider({
|
|
118
|
+
resource,
|
|
119
|
+
idGenerator: advanced?.idGenerator,
|
|
120
|
+
spanProcessors: [sp],
|
|
121
|
+
});
|
|
122
|
+
logger.info(`IntrospectionMastraExporter initialized: endpoint=${endpoint}, serviceName=${serviceName}`);
|
|
123
|
+
}
|
|
124
|
+
this._tracer = this._tracerProvider.getTracer("introspection-mastra", VERSION);
|
|
125
|
+
}
|
|
126
|
+
async _exportTracingEvent(event) {
|
|
127
|
+
if (this.isDisabled)
|
|
128
|
+
return;
|
|
129
|
+
if (event.type !== "span_ended")
|
|
130
|
+
return;
|
|
131
|
+
// Lazy init if init() wasn't called
|
|
132
|
+
if (!this._tracerProvider) {
|
|
133
|
+
this._initProvider("mastra-app");
|
|
134
|
+
}
|
|
135
|
+
if (!this._tracer)
|
|
136
|
+
return;
|
|
137
|
+
this._exportSpan(event.exportedSpan);
|
|
138
|
+
}
|
|
139
|
+
async flush() {
|
|
140
|
+
await this._tracerProvider?.forceFlush();
|
|
141
|
+
}
|
|
142
|
+
async shutdown() {
|
|
143
|
+
// End any open root spans
|
|
144
|
+
for (const span of this._rootSpans.values())
|
|
145
|
+
span.end();
|
|
146
|
+
this._rootSpans.clear();
|
|
147
|
+
await this._tracerProvider?.shutdown();
|
|
148
|
+
}
|
|
149
|
+
// -------------------------------------------------------------------------
|
|
150
|
+
// Span routing
|
|
151
|
+
// -------------------------------------------------------------------------
|
|
152
|
+
_exportSpan(span) {
|
|
153
|
+
switch (span.type) {
|
|
154
|
+
case "agent_run":
|
|
155
|
+
this._exportAgentRun(span);
|
|
156
|
+
break;
|
|
157
|
+
case "model_step":
|
|
158
|
+
this._exportModelStep(span);
|
|
159
|
+
break;
|
|
160
|
+
case "tool_call":
|
|
161
|
+
case "mcp_tool_call":
|
|
162
|
+
this._exportToolCall(span);
|
|
163
|
+
break;
|
|
164
|
+
// model_generation — skipped; model_step has per-step tool calls
|
|
165
|
+
default:
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// -------------------------------------------------------------------------
|
|
170
|
+
// Span creation with shared trace context
|
|
171
|
+
// -------------------------------------------------------------------------
|
|
172
|
+
/** Ensure a root OTel span exists for this Mastra trace.
|
|
173
|
+
* Events arrive in end-order (children first, agent_run last),
|
|
174
|
+
* so we create a synthetic root on first encounter. */
|
|
175
|
+
_ensureRoot(span) {
|
|
176
|
+
if (this._rootSpans.has(span.traceId))
|
|
177
|
+
return;
|
|
178
|
+
const rootSpan = this._tracer.startSpan("trace", {
|
|
179
|
+
startTime: span.startTime,
|
|
180
|
+
});
|
|
181
|
+
this._rootSpans.set(span.traceId, rootSpan);
|
|
182
|
+
}
|
|
183
|
+
_createSpan(span, name) {
|
|
184
|
+
this._ensureRoot(span);
|
|
185
|
+
const parentCtx = otelTrace.setSpan(otelContext.active(), this._rootSpans.get(span.traceId));
|
|
186
|
+
return this._tracer.startSpan(name || span.name, { startTime: span.startTime }, parentCtx);
|
|
187
|
+
}
|
|
188
|
+
// -------------------------------------------------------------------------
|
|
189
|
+
// Span converters
|
|
190
|
+
// -------------------------------------------------------------------------
|
|
191
|
+
_exportAgentRun(span) {
|
|
192
|
+
const attrs = span.attributes;
|
|
193
|
+
const conversationId = this._getConversationId(span);
|
|
194
|
+
// agent_run arrives last — set attrs on the synthetic root and end it
|
|
195
|
+
this._ensureRoot(span);
|
|
196
|
+
const rootSpan = this._rootSpans.get(span.traceId);
|
|
197
|
+
rootSpan.setAttribute("gen_ai.conversation.id", conversationId);
|
|
198
|
+
rootSpan.setAttribute("gen_ai.agent.name", span.entityName || span.name || "agent");
|
|
199
|
+
if (attrs?.["instructions"] && typeof attrs["instructions"] === "string") {
|
|
200
|
+
rootSpan.setAttribute("gen_ai.system_instructions", JSON.stringify([{ type: "text", content: attrs["instructions"] }]));
|
|
201
|
+
}
|
|
202
|
+
// Metadata passthrough
|
|
203
|
+
if (span.metadata) {
|
|
204
|
+
for (const [key, value] of Object.entries(span.metadata)) {
|
|
205
|
+
if (value != null) {
|
|
206
|
+
rootSpan.setAttribute(`ai.telemetry.metadata.${key}`, typeof value === "string" ? value : JSON.stringify(value));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
rootSpan.end(span.endTime || new Date());
|
|
211
|
+
this._rootSpans.delete(span.traceId);
|
|
212
|
+
}
|
|
213
|
+
_exportModelStep(span) {
|
|
214
|
+
const attrs = span.attributes;
|
|
215
|
+
const conversationId = this._getConversationId(span);
|
|
216
|
+
// Model info lives in metadata.modelMetadata (set by Mastra)
|
|
217
|
+
const modelMeta = span.metadata?.["modelMetadata"];
|
|
218
|
+
const model = modelMeta?.["modelId"] || "";
|
|
219
|
+
const provider = modelMeta?.["modelProvider"] || "";
|
|
220
|
+
// Response metadata from metadata.body (the API response)
|
|
221
|
+
const respBody = span.metadata?.["body"];
|
|
222
|
+
const responseModel = respBody?.["model"] || "";
|
|
223
|
+
const responseId = respBody?.["id"] || "";
|
|
224
|
+
const spanName = model ? `chat ${model}` : span.name;
|
|
225
|
+
const otelSpan = this._createSpan(span, spanName);
|
|
226
|
+
otelSpan.setAttribute("gen_ai.operation.name", "chat");
|
|
227
|
+
otelSpan.setAttribute("gen_ai.conversation.id", conversationId);
|
|
228
|
+
otelSpan.setAttribute("openinference.span.kind", "LLM");
|
|
229
|
+
if (model)
|
|
230
|
+
otelSpan.setAttribute("gen_ai.request.model", model);
|
|
231
|
+
if (provider)
|
|
232
|
+
otelSpan.setAttribute("gen_ai.system", provider);
|
|
233
|
+
if (responseModel)
|
|
234
|
+
otelSpan.setAttribute("gen_ai.response.model", responseModel);
|
|
235
|
+
if (responseId)
|
|
236
|
+
otelSpan.setAttribute("gen_ai.response.id", responseId);
|
|
237
|
+
// Input messages — model_step has input.body.input (raw API messages)
|
|
238
|
+
if (span.input != null) {
|
|
239
|
+
const rawInput = span.input;
|
|
240
|
+
const body = rawInput?.body;
|
|
241
|
+
const apiMessages = body?.input ?? body?.messages;
|
|
242
|
+
if (Array.isArray(apiMessages)) {
|
|
243
|
+
const inputMessages = this._convertInput(apiMessages);
|
|
244
|
+
if (inputMessages.length > 0) {
|
|
245
|
+
otelSpan.setAttribute("gen_ai.input.messages", JSON.stringify(inputMessages));
|
|
246
|
+
}
|
|
247
|
+
// System instructions
|
|
248
|
+
const systemMsgs = apiMessages.filter((m) => m.role === "system");
|
|
249
|
+
if (systemMsgs.length > 0) {
|
|
250
|
+
otelSpan.setAttribute("gen_ai.system_instructions", JSON.stringify(systemMsgs.map((m) => ({
|
|
251
|
+
type: "text",
|
|
252
|
+
content: typeof m.content === "string"
|
|
253
|
+
? m.content
|
|
254
|
+
: JSON.stringify(m.content),
|
|
255
|
+
}))));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Tool definitions from input.body.tools
|
|
259
|
+
const tools = body?.tools;
|
|
260
|
+
if (Array.isArray(tools) && tools.length > 0) {
|
|
261
|
+
otelSpan.setAttribute("gen_ai.tool.definitions", JSON.stringify(tools.map((t) => ({
|
|
262
|
+
type: t.type || "function",
|
|
263
|
+
name: t.name || "",
|
|
264
|
+
description: t.description || "",
|
|
265
|
+
parameters: t.parameters,
|
|
266
|
+
}))));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Output messages — model_step output has {text, toolCalls} per step
|
|
270
|
+
if (span.output != null) {
|
|
271
|
+
const outputMessages = this._convertOutput(span.output, attrs?.["finishReason"]);
|
|
272
|
+
if (outputMessages.length > 0) {
|
|
273
|
+
otelSpan.setAttribute("gen_ai.output.messages", JSON.stringify(outputMessages));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Token usage (per step)
|
|
277
|
+
const usage = attrs?.["usage"];
|
|
278
|
+
if (usage) {
|
|
279
|
+
if (typeof usage["inputTokens"] === "number")
|
|
280
|
+
otelSpan.setAttribute("gen_ai.usage.input_tokens", usage["inputTokens"]);
|
|
281
|
+
if (typeof usage["outputTokens"] === "number")
|
|
282
|
+
otelSpan.setAttribute("gen_ai.usage.output_tokens", usage["outputTokens"]);
|
|
283
|
+
const inputDetails = usage["inputDetails"];
|
|
284
|
+
if (typeof inputDetails?.["cacheRead"] === "number")
|
|
285
|
+
otelSpan.setAttribute("gen_ai.usage.cache_read.input_tokens", inputDetails["cacheRead"]);
|
|
286
|
+
}
|
|
287
|
+
// Finish reason
|
|
288
|
+
const finishReason = attrs?.["finishReason"];
|
|
289
|
+
if (finishReason)
|
|
290
|
+
otelSpan.setAttribute("gen_ai.response.finish_reasons", [finishReason]);
|
|
291
|
+
otelSpan.end(span.endTime || new Date());
|
|
292
|
+
}
|
|
293
|
+
_exportToolCall(span) {
|
|
294
|
+
const conversationId = this._getConversationId(span);
|
|
295
|
+
const toolName = span.entityName || span.name || "tool";
|
|
296
|
+
const otelSpan = this._createSpan(span, toolName);
|
|
297
|
+
otelSpan.setAttribute("gen_ai.tool.name", toolName);
|
|
298
|
+
otelSpan.setAttribute("gen_ai.conversation.id", conversationId);
|
|
299
|
+
otelSpan.setAttribute("openinference.span.kind", "TOOL");
|
|
300
|
+
if (span.input != null) {
|
|
301
|
+
otelSpan.setAttribute("gen_ai.tool.input", typeof span.input === "string"
|
|
302
|
+
? span.input
|
|
303
|
+
: JSON.stringify(span.input));
|
|
304
|
+
}
|
|
305
|
+
if (span.output != null) {
|
|
306
|
+
otelSpan.setAttribute("gen_ai.tool.output", typeof span.output === "string"
|
|
307
|
+
? span.output
|
|
308
|
+
: JSON.stringify(span.output));
|
|
309
|
+
}
|
|
310
|
+
otelSpan.end(span.endTime || new Date());
|
|
311
|
+
}
|
|
312
|
+
// -------------------------------------------------------------------------
|
|
313
|
+
// Helpers
|
|
314
|
+
// -------------------------------------------------------------------------
|
|
315
|
+
_getConversationId(span) {
|
|
316
|
+
const metaConvId = span.metadata?.["gen_ai.conversation.id"];
|
|
317
|
+
if (metaConvId)
|
|
318
|
+
return metaConvId;
|
|
319
|
+
const attrs = span.attributes;
|
|
320
|
+
const attrConvId = attrs?.["conversationId"];
|
|
321
|
+
if (attrConvId)
|
|
322
|
+
return attrConvId;
|
|
323
|
+
const traceId = span.traceId;
|
|
324
|
+
if (!this._conversationIds.has(traceId)) {
|
|
325
|
+
this._conversationIds.set(traceId, `intro_conv_${randomUUID().replace(/-/g, "")}`);
|
|
326
|
+
}
|
|
327
|
+
return this._conversationIds.get(traceId);
|
|
328
|
+
}
|
|
329
|
+
_convertInput(input) {
|
|
330
|
+
if (!input)
|
|
331
|
+
return [];
|
|
332
|
+
if (Array.isArray(input)) {
|
|
333
|
+
const result = [];
|
|
334
|
+
for (const m of input) {
|
|
335
|
+
const msg = m;
|
|
336
|
+
if (!msg)
|
|
337
|
+
continue;
|
|
338
|
+
// Skip system messages (handled separately)
|
|
339
|
+
if (msg.role === "system")
|
|
340
|
+
continue;
|
|
341
|
+
// Standard role-based messages
|
|
342
|
+
if (msg.role) {
|
|
343
|
+
result.push({
|
|
344
|
+
role: msg.role,
|
|
345
|
+
parts: this._extractParts(msg.content),
|
|
346
|
+
});
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
// OpenAI Responses API: function_call_output (no role, has type)
|
|
350
|
+
if (msg.type === "function_call_output") {
|
|
351
|
+
result.push({
|
|
352
|
+
role: "tool",
|
|
353
|
+
parts: [
|
|
354
|
+
{
|
|
355
|
+
type: "tool_call_response",
|
|
356
|
+
id: (msg.call_id || ""),
|
|
357
|
+
response: typeof msg.output === "string"
|
|
358
|
+
? msg.output
|
|
359
|
+
: JSON.stringify(msg.output),
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
});
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
// item_reference — skip (OpenAI internal reference)
|
|
366
|
+
if (msg.type === "item_reference")
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
return result;
|
|
370
|
+
}
|
|
371
|
+
if (typeof input === "string") {
|
|
372
|
+
return [{ role: "user", parts: [{ type: "text", content: input }] }];
|
|
373
|
+
}
|
|
374
|
+
return [];
|
|
375
|
+
}
|
|
376
|
+
_convertOutput(output, finishReason) {
|
|
377
|
+
if (!output)
|
|
378
|
+
return [];
|
|
379
|
+
const parts = [];
|
|
380
|
+
if (typeof output === "string") {
|
|
381
|
+
parts.push({ type: "text", content: output });
|
|
382
|
+
}
|
|
383
|
+
else if (typeof output === "object" && output !== null) {
|
|
384
|
+
const out = output;
|
|
385
|
+
// Reasoning
|
|
386
|
+
if (Array.isArray(out.reasoning)) {
|
|
387
|
+
for (const r of out.reasoning) {
|
|
388
|
+
const text = (r.text || r.content || "");
|
|
389
|
+
if (text)
|
|
390
|
+
parts.push({ type: "thinking", content: text });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// Text
|
|
394
|
+
if (typeof out.text === "string" && out.text) {
|
|
395
|
+
parts.push({ type: "text", content: out.text });
|
|
396
|
+
}
|
|
397
|
+
// Tool calls
|
|
398
|
+
if (Array.isArray(out.toolCalls)) {
|
|
399
|
+
for (const tc of out.toolCalls) {
|
|
400
|
+
parts.push({
|
|
401
|
+
type: "tool_call",
|
|
402
|
+
name: (tc.toolName || tc.name || ""),
|
|
403
|
+
id: (tc.toolCallId || tc.id || ""),
|
|
404
|
+
arguments: typeof tc.args === "string"
|
|
405
|
+
? tc.args
|
|
406
|
+
: JSON.stringify(tc.args || tc.input),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (parts.length === 0)
|
|
412
|
+
return [];
|
|
413
|
+
return [{ role: "assistant", parts, finish_reason: finishReason }];
|
|
414
|
+
}
|
|
415
|
+
_extractParts(content) {
|
|
416
|
+
if (typeof content === "string") {
|
|
417
|
+
return [{ type: "text", content }];
|
|
418
|
+
}
|
|
419
|
+
if (Array.isArray(content)) {
|
|
420
|
+
return content
|
|
421
|
+
.map((part) => {
|
|
422
|
+
const p = part;
|
|
423
|
+
// Standard text
|
|
424
|
+
if (p.type === "text")
|
|
425
|
+
return {
|
|
426
|
+
type: "text",
|
|
427
|
+
content: (p.text || p.content || ""),
|
|
428
|
+
};
|
|
429
|
+
// OpenAI Responses API input_text format
|
|
430
|
+
if (p.type === "input_text")
|
|
431
|
+
return {
|
|
432
|
+
type: "text",
|
|
433
|
+
content: (p.text || ""),
|
|
434
|
+
};
|
|
435
|
+
// Tool call
|
|
436
|
+
if (p.type === "tool-call" ||
|
|
437
|
+
p.type === "tool_call" ||
|
|
438
|
+
p.type === "function_call")
|
|
439
|
+
return {
|
|
440
|
+
type: "tool_call",
|
|
441
|
+
name: (p.toolName || p.name || ""),
|
|
442
|
+
id: (p.toolCallId || p.id || p.call_id || ""),
|
|
443
|
+
arguments: typeof p.args === "string"
|
|
444
|
+
? p.args
|
|
445
|
+
: typeof p.arguments === "string"
|
|
446
|
+
? p.arguments
|
|
447
|
+
: JSON.stringify(p.args || p.arguments || p.input),
|
|
448
|
+
};
|
|
449
|
+
// Tool result
|
|
450
|
+
if (p.type === "tool-result" ||
|
|
451
|
+
p.type === "tool_call_response" ||
|
|
452
|
+
p.type === "function_call_output")
|
|
453
|
+
return {
|
|
454
|
+
type: "tool_call_response",
|
|
455
|
+
id: (p.toolCallId || p.id || p.call_id || ""),
|
|
456
|
+
response: typeof p.result === "string"
|
|
457
|
+
? p.result
|
|
458
|
+
: typeof p.output === "string"
|
|
459
|
+
? p.output
|
|
460
|
+
: JSON.stringify(p.result || p.output),
|
|
461
|
+
};
|
|
462
|
+
// item_reference (OpenAI Responses API) — skip
|
|
463
|
+
if (p.type === "item_reference")
|
|
464
|
+
return null;
|
|
465
|
+
return { type: "text", content: JSON.stringify(p) };
|
|
466
|
+
})
|
|
467
|
+
.filter((p) => p !== null);
|
|
468
|
+
}
|
|
469
|
+
return [];
|
|
470
|
+
}
|
|
471
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Context } from "@opentelemetry/api";
|
|
2
|
+
import { SpanProcessor, ReadableSpan, Span } from "@opentelemetry/sdk-trace-base";
|
|
3
|
+
import type { AdvancedOptions } from "./types.js";
|
|
4
|
+
export interface IntrospectionSpanProcessorOptions {
|
|
5
|
+
/** Authentication token (env: INTROSPECTION_TOKEN) */
|
|
6
|
+
token?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Service name for telemetry (env: INTROSPECTION_SERVICE_NAME).
|
|
9
|
+
* Note: For spans, the service name is typically set on the TracerProvider's resource.
|
|
10
|
+
* This option is provided for consistency but the TracerProvider's resource takes precedence.
|
|
11
|
+
*/
|
|
12
|
+
serviceName?: string;
|
|
13
|
+
/** Advanced options for configuration and testing */
|
|
14
|
+
advanced?: AdvancedOptions;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* OTel {@link SpanProcessor} that forwards traces to the Introspection backend
|
|
18
|
+
* via OTLP, automatically converting any OpenInference spans to Gen AI
|
|
19
|
+
* semantic convention attributes.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { IntrospectionSpanProcessor } from "@introspection-sdk/introspection-node";
|
|
24
|
+
*
|
|
25
|
+
* const processor = new IntrospectionSpanProcessor({ token: "sk-intro-…" });
|
|
26
|
+
* tracerProvider.addSpanProcessor(processor);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare class IntrospectionSpanProcessor implements SpanProcessor {
|
|
30
|
+
private _spanProcessor;
|
|
31
|
+
private _serviceName?;
|
|
32
|
+
private _conversationIds;
|
|
33
|
+
constructor(options?: IntrospectionSpanProcessorOptions);
|
|
34
|
+
/**
|
|
35
|
+
* Called when a new span is started; delegates to the inner batch processor.
|
|
36
|
+
*
|
|
37
|
+
* @param span - The span that was just started.
|
|
38
|
+
* @param parentContext - The parent {@link Context}.
|
|
39
|
+
*/
|
|
40
|
+
onStart(span: Span, parentContext: Context): void;
|
|
41
|
+
/**
|
|
42
|
+
* Called when a span ends. If the span originates from an OpenInference
|
|
43
|
+
* instrumentor, its attributes are converted to `gen_ai.*` semconv keys
|
|
44
|
+
* before being forwarded.
|
|
45
|
+
*
|
|
46
|
+
* @param span - The completed {@link ReadableSpan}.
|
|
47
|
+
*/
|
|
48
|
+
onEnd(span: ReadableSpan): void;
|
|
49
|
+
/**
|
|
50
|
+
* Shut down the inner batch processor and its exporter.
|
|
51
|
+
*
|
|
52
|
+
* @returns A promise that resolves once all pending spans are flushed.
|
|
53
|
+
*/
|
|
54
|
+
shutdown(): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Force-flush any buffered spans to the Introspection backend.
|
|
57
|
+
*
|
|
58
|
+
* @returns A promise that resolves once the flush completes.
|
|
59
|
+
*/
|
|
60
|
+
forceFlush(): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=span-processor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span-processor.d.ts","sourceRoot":"","sources":["../src/span-processor.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EAIR,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,aAAa,EACb,YAAY,EACZ,IAAI,EAGL,MAAM,+BAA+B,CAAC;AAMvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA2FlD,MAAM,WAAW,iCAAiC;IAChD,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,0BAA2B,YAAW,aAAa;IAC9D,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,gBAAgB,CAAkC;gBAE9C,OAAO,GAAE,iCAAsC;IAwH3D;;;;;OAKG;IACH,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,GAAG,IAAI;IAIjD;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IA2H/B;;;;OAIG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B;;;;OAIG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAGlC"}
|