@mastra/otel-exporter 0.0.0-extract-tool-ui-inp-playground-ui-20251024041825 → 0.0.0-feat-add-query-option-to-playground-20251209160219

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,11 +1,14 @@
1
- import { AITracingEventType, AISpanType } from '@mastra/core/ai-tracing';
2
- import { ConsoleLogger } from '@mastra/core/logger';
3
- import { diag, DiagConsoleLogger, DiagLogLevel, SpanKind, SpanStatusCode, TraceFlags } from '@opentelemetry/api';
4
- import { resourceFromAttributes } from '@opentelemetry/resources';
1
+ import { SpanType, TracingEventType } from '@mastra/core/observability';
2
+ import { BaseExporter } from '@mastra/observability';
3
+ import { TraceFlags, SpanKind, SpanStatusCode, diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
5
4
  import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
5
+ import { readFileSync } from 'fs';
6
+ import { fileURLToPath } from 'url';
7
+ import { resourceFromAttributes } from '@opentelemetry/resources';
6
8
  import { ATTR_TELEMETRY_SDK_LANGUAGE, ATTR_TELEMETRY_SDK_VERSION, ATTR_TELEMETRY_SDK_NAME, ATTR_SERVICE_VERSION, ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
9
+ import { ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_INPUT_MESSAGES, ATTR_GEN_AI_OUTPUT_MESSAGES, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_PROVIDER_NAME, ATTR_GEN_AI_AGENT_ID, ATTR_GEN_AI_AGENT_NAME, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, ATTR_GEN_AI_REQUEST_TEMPERATURE, ATTR_GEN_AI_REQUEST_MAX_TOKENS, ATTR_GEN_AI_REQUEST_TOP_P, ATTR_GEN_AI_REQUEST_TOP_K, ATTR_GEN_AI_REQUEST_PRESENCE_PENALTY, ATTR_GEN_AI_REQUEST_FREQUENCY_PENALTY, ATTR_GEN_AI_REQUEST_STOP_SEQUENCES, ATTR_GEN_AI_REQUEST_SEED, ATTR_GEN_AI_RESPONSE_FINISH_REASONS, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_RESPONSE_ID, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_GEN_AI_TOOL_NAME, ATTR_GEN_AI_TOOL_DESCRIPTION, ATTR_GEN_AI_CONVERSATION_ID, ATTR_GEN_AI_SYSTEM_INSTRUCTIONS, ATTR_ERROR_TYPE, ATTR_ERROR_MESSAGE } from '@opentelemetry/semantic-conventions/incubating';
7
10
 
8
- // src/ai-tracing.ts
11
+ // src/tracing.ts
9
12
 
10
13
  // src/loadExporter.ts
11
14
  var OTLPHttpExporter;
@@ -203,410 +206,487 @@ function resolveCustomConfig(config) {
203
206
  protocol: config.protocol || "http/json"
204
207
  };
205
208
  }
206
- var MastraReadableSpan = class {
207
- name;
208
- kind;
209
- spanContext;
210
- parentSpanContext;
211
- parentSpanId;
212
- startTime;
213
- endTime;
214
- status;
215
- attributes;
216
- links;
217
- events;
218
- duration;
219
- ended;
220
- resource;
221
- instrumentationLibrary;
222
- instrumentationScope;
223
- droppedAttributesCount = 0;
224
- droppedEventsCount = 0;
225
- droppedLinksCount = 0;
226
- constructor(aiSpan, attributes, kind, parentSpanId, resource, instrumentationLibrary) {
227
- this.name = aiSpan.name;
228
- this.kind = kind;
229
- this.attributes = attributes;
230
- this.parentSpanId = parentSpanId;
231
- this.links = [];
232
- this.events = [];
233
- this.startTime = this.dateToHrTime(aiSpan.startTime);
234
- this.endTime = aiSpan.endTime ? this.dateToHrTime(aiSpan.endTime) : this.startTime;
235
- this.ended = !!aiSpan.endTime;
236
- if (aiSpan.endTime) {
237
- const durationMs = aiSpan.endTime.getTime() - aiSpan.startTime.getTime();
238
- this.duration = [Math.floor(durationMs / 1e3), durationMs % 1e3 * 1e6];
239
- } else {
240
- this.duration = [0, 0];
209
+
210
+ // src/gen-ai-messages.ts
211
+ var isMastraMessagePart = (p) => {
212
+ return typeof p === "object" && p != null && "type" in p && (p.type === "text" || p.type === "tool-call" || p.type === "tool-result") && (p.type === "text" && "text" in p || p.type === "tool-call" && "toolCallId" in p && "toolName" in p && "input" in p || p.type === "tool-result" && "toolCallId" in p && "toolName" in p && "output" in p);
213
+ };
214
+ var isMastraMessage = (m) => {
215
+ return typeof m === "object" && m != null && "role" in m && "content" in m && (typeof m.content === "string" || Array.isArray(m.content) && m.content.every(isMastraMessagePart));
216
+ };
217
+ var convertMastraMessagesToGenAIMessages = (inputOutputString) => {
218
+ try {
219
+ const parsedIO = JSON.parse(inputOutputString);
220
+ if (typeof parsedIO !== "object" || parsedIO == null || !("messages" in parsedIO) && !("text" in parsedIO)) {
221
+ return inputOutputString;
222
+ }
223
+ if ("text" in parsedIO) {
224
+ return JSON.stringify([
225
+ {
226
+ role: "assistant",
227
+ parts: [{ type: "text", content: parsedIO.text }]
228
+ }
229
+ ]);
241
230
  }
242
- if (aiSpan.errorInfo) {
243
- this.status = {
244
- code: SpanStatusCode.ERROR,
245
- message: aiSpan.errorInfo.message
246
- };
247
- this.events.push({
248
- name: "exception",
249
- attributes: {
250
- "exception.message": aiSpan.errorInfo.message,
251
- "exception.type": "Error",
252
- ...aiSpan.errorInfo.details?.stack && {
253
- "exception.stacktrace": aiSpan.errorInfo.details.stack
231
+ if (Array.isArray(parsedIO.messages)) {
232
+ return JSON.stringify(
233
+ parsedIO.messages.map((m) => {
234
+ if (!isMastraMessage(m)) {
235
+ return m;
254
236
  }
255
- },
256
- time: this.startTime,
257
- droppedAttributesCount: 0
258
- });
259
- } else if (aiSpan.endTime) {
260
- this.status = { code: SpanStatusCode.OK };
261
- } else {
262
- this.status = { code: SpanStatusCode.UNSET };
263
- }
264
- if (aiSpan.isEvent) {
265
- this.events.push({
266
- name: "instant_event",
267
- attributes: {},
268
- time: this.startTime,
269
- droppedAttributesCount: 0
270
- });
271
- }
272
- this.spanContext = () => ({
273
- traceId: aiSpan.traceId,
274
- spanId: aiSpan.id,
275
- traceFlags: TraceFlags.SAMPLED,
276
- isRemote: false
277
- });
278
- if (parentSpanId) {
279
- this.parentSpanContext = {
280
- traceId: aiSpan.traceId,
281
- spanId: parentSpanId,
282
- traceFlags: TraceFlags.SAMPLED,
283
- isRemote: false
284
- };
237
+ const role = m.role;
238
+ let parts = [];
239
+ if (Array.isArray(m.content)) {
240
+ parts = m.content.map((c) => {
241
+ switch (c.type) {
242
+ case "text":
243
+ return {
244
+ type: "text",
245
+ content: c.text
246
+ };
247
+ case "tool-call":
248
+ return {
249
+ type: "tool_call",
250
+ id: c.toolCallId,
251
+ name: c.toolName,
252
+ arguments: JSON.stringify(c.input)
253
+ };
254
+ case "tool-result":
255
+ return {
256
+ type: "tool_call_response",
257
+ id: c.toolCallId,
258
+ name: c.toolName,
259
+ response: JSON.stringify(c.output.value)
260
+ };
261
+ default:
262
+ return c;
263
+ }
264
+ });
265
+ } else {
266
+ parts = [
267
+ {
268
+ type: "text",
269
+ content: m.content
270
+ }
271
+ ];
272
+ }
273
+ return {
274
+ role,
275
+ parts
276
+ };
277
+ })
278
+ );
285
279
  }
286
- this.resource = resource || {};
287
- this.instrumentationLibrary = instrumentationLibrary || {
288
- name: "@mastra/otel",
289
- version: "1.0.0"
290
- };
291
- this.instrumentationScope = this.instrumentationLibrary;
292
- }
293
- /**
294
- * Convert JavaScript Date to hrtime format
295
- */
296
- dateToHrTime(date) {
297
- const ms = date.getTime();
298
- const seconds = Math.floor(ms / 1e3);
299
- const nanoseconds = ms % 1e3 * 1e6;
300
- return [seconds, nanoseconds];
280
+ return inputOutputString;
281
+ } catch {
282
+ return inputOutputString;
301
283
  }
302
284
  };
303
285
 
304
- // src/span-converter.ts
305
- var SPAN_KIND_MAPPING = {
306
- // Model operations are CLIENT spans (calling external AI services)
307
- [AISpanType.MODEL_GENERATION]: SpanKind.CLIENT,
308
- [AISpanType.MODEL_CHUNK]: SpanKind.CLIENT,
309
- // MCP tool calls are CLIENT (external service calls)
310
- [AISpanType.MCP_TOOL_CALL]: SpanKind.CLIENT,
311
- // Root spans for agent/workflow are SERVER (entry points)
312
- [AISpanType.AGENT_RUN]: SpanKind.SERVER,
313
- [AISpanType.WORKFLOW_RUN]: SpanKind.SERVER
314
- };
315
- var SpanConverter = class {
316
- resource;
317
- instrumentationLibrary;
318
- constructor(resource) {
319
- this.resource = resource;
320
- this.instrumentationLibrary = {
321
- name: "@mastra/otel",
322
- version: "1.0.0"
323
- };
286
+ // src/gen-ai-semantics.ts
287
+ function getOperationName(span) {
288
+ switch (span.type) {
289
+ case SpanType.MODEL_GENERATION:
290
+ return "chat";
291
+ case SpanType.TOOL_CALL:
292
+ case SpanType.MCP_TOOL_CALL:
293
+ return "execute_tool";
294
+ case SpanType.AGENT_RUN:
295
+ return "invoke_agent";
296
+ case SpanType.WORKFLOW_RUN:
297
+ return "invoke_workflow";
298
+ default:
299
+ return span.type.toLowerCase();
324
300
  }
325
- /**
326
- * Convert a Mastra AI span to an OpenTelemetry ReadableSpan
327
- * This preserves Mastra's trace and span IDs
328
- */
329
- convertSpan(aiSpan) {
330
- const spanKind = this.getSpanKind(aiSpan);
331
- const attributes = this.buildAttributes(aiSpan);
332
- const spanName = this.buildSpanName(aiSpan);
333
- const otelSpan = { ...aiSpan, name: spanName };
334
- return new MastraReadableSpan(
335
- otelSpan,
336
- attributes,
337
- spanKind,
338
- aiSpan.parentSpanId,
339
- // Use the parentSpanId from the Mastra span directly
340
- this.resource,
341
- this.instrumentationLibrary
342
- );
301
+ }
302
+ function sanitizeSpanName(name) {
303
+ return name.replace(/[^\p{L}\p{N}._ -]/gu, "");
304
+ }
305
+ function getSpanIdentifier(span) {
306
+ switch (span.type) {
307
+ case SpanType.MODEL_GENERATION: {
308
+ const attrs = span.attributes;
309
+ return attrs?.model ?? "unknown";
310
+ }
311
+ case SpanType.TOOL_CALL:
312
+ case SpanType.MCP_TOOL_CALL: {
313
+ const attrs = span.attributes;
314
+ return attrs?.toolId ?? "unknown";
315
+ }
316
+ case SpanType.AGENT_RUN: {
317
+ const attrs = span.attributes;
318
+ return attrs?.agentName ?? attrs?.agentId ?? "unknown";
319
+ }
320
+ case SpanType.WORKFLOW_RUN: {
321
+ const attrs = span.attributes;
322
+ return attrs?.workflowId ?? "unknown";
323
+ }
324
+ default:
325
+ return null;
343
326
  }
344
- /**
345
- * Get the appropriate SpanKind based on span type and context
346
- */
347
- getSpanKind(aiSpan) {
348
- if (aiSpan.isRootSpan) {
349
- if (aiSpan.type === AISpanType.AGENT_RUN || aiSpan.type === AISpanType.WORKFLOW_RUN) {
350
- return SpanKind.SERVER;
351
- }
327
+ }
328
+ function getSpanName(span) {
329
+ const identifier = getSpanIdentifier(span);
330
+ if (identifier) {
331
+ const operation = getOperationName(span);
332
+ return `${operation} ${identifier}`;
333
+ }
334
+ return sanitizeSpanName(span.name);
335
+ }
336
+ function getAttributes(span) {
337
+ const attributes = {};
338
+ const spanType = span.type.toLowerCase();
339
+ attributes[ATTR_GEN_AI_OPERATION_NAME] = getOperationName(span);
340
+ attributes["mastra.span.type"] = span.type;
341
+ if (span.input !== void 0) {
342
+ const inputStr = typeof span.input === "string" ? span.input : JSON.stringify(span.input);
343
+ if (span.type === SpanType.MODEL_GENERATION) {
344
+ attributes[ATTR_GEN_AI_INPUT_MESSAGES] = convertMastraMessagesToGenAIMessages(inputStr);
345
+ } else if (span.type === SpanType.TOOL_CALL || span.type === SpanType.MCP_TOOL_CALL) {
346
+ attributes["gen_ai.tool.call.arguments"] = inputStr;
347
+ } else {
348
+ attributes[`mastra.${spanType}.input`] = inputStr;
352
349
  }
353
- return SPAN_KIND_MAPPING[aiSpan.type] || SpanKind.INTERNAL;
354
350
  }
355
- /**
356
- * Build OTEL-compliant span name based on span type and attributes
357
- */
358
- buildSpanName(aiSpan) {
359
- switch (aiSpan.type) {
360
- case AISpanType.MODEL_GENERATION: {
361
- const attrs = aiSpan.attributes;
362
- const operation = attrs?.resultType === "tool_selection" ? "tool_selection" : "chat";
363
- const model = attrs?.model || "unknown";
364
- return `${operation} ${model}`;
365
- }
366
- case AISpanType.TOOL_CALL:
367
- case AISpanType.MCP_TOOL_CALL: {
368
- const toolAttrs = aiSpan.attributes;
369
- const toolName = toolAttrs?.toolId || "unknown";
370
- return `tool.execute ${toolName}`;
371
- }
372
- case AISpanType.AGENT_RUN: {
373
- const agentAttrs = aiSpan.attributes;
374
- const agentId = agentAttrs?.agentId || "unknown";
375
- return `agent.${agentId}`;
376
- }
377
- case AISpanType.WORKFLOW_RUN: {
378
- const workflowAttrs = aiSpan.attributes;
379
- const workflowId = workflowAttrs?.workflowId || "unknown";
380
- return `workflow.${workflowId}`;
381
- }
382
- case AISpanType.WORKFLOW_STEP:
383
- return aiSpan.name;
384
- default:
385
- return aiSpan.name;
351
+ if (span.output !== void 0) {
352
+ const outputStr = typeof span.output === "string" ? span.output : JSON.stringify(span.output);
353
+ if (span.type === SpanType.MODEL_GENERATION) {
354
+ attributes[ATTR_GEN_AI_OUTPUT_MESSAGES] = convertMastraMessagesToGenAIMessages(outputStr);
355
+ } else if (span.type === SpanType.TOOL_CALL || span.type === SpanType.MCP_TOOL_CALL) {
356
+ attributes["gen_ai.tool.call.result"] = outputStr;
357
+ } else {
358
+ attributes[`mastra.${spanType}.output`] = outputStr;
386
359
  }
387
360
  }
388
- /**
389
- * Build OpenTelemetry attributes from Mastra AI span
390
- * Following OTEL Semantic Conventions for GenAI
391
- */
392
- buildAttributes(aiSpan) {
393
- const attributes = {};
394
- attributes["gen_ai.operation.name"] = this.getOperationName(aiSpan);
395
- attributes["span.kind"] = this.getSpanKindString(aiSpan);
396
- attributes["mastra.span.type"] = aiSpan.type;
397
- attributes["mastra.trace_id"] = aiSpan.traceId;
398
- attributes["mastra.span_id"] = aiSpan.id;
399
- if (aiSpan.parentSpanId) {
400
- attributes["mastra.parent_span_id"] = aiSpan.parentSpanId;
401
- }
402
- if (aiSpan.input !== void 0) {
403
- const inputStr = typeof aiSpan.input === "string" ? aiSpan.input : JSON.stringify(aiSpan.input);
404
- attributes["input"] = inputStr;
405
- if (aiSpan.type === AISpanType.MODEL_GENERATION) {
406
- attributes["gen_ai.prompt"] = inputStr;
407
- } else if (aiSpan.type === AISpanType.TOOL_CALL || aiSpan.type === AISpanType.MCP_TOOL_CALL) {
408
- attributes["gen_ai.tool.input"] = inputStr;
409
- }
361
+ if (span.type === SpanType.MODEL_GENERATION && span.attributes) {
362
+ const modelAttrs = span.attributes;
363
+ if (modelAttrs.model) {
364
+ attributes[ATTR_GEN_AI_REQUEST_MODEL] = modelAttrs.model;
410
365
  }
411
- if (aiSpan.output !== void 0) {
412
- const outputStr = typeof aiSpan.output === "string" ? aiSpan.output : JSON.stringify(aiSpan.output);
413
- attributes["output"] = outputStr;
414
- if (aiSpan.type === AISpanType.MODEL_GENERATION) {
415
- attributes["gen_ai.completion"] = outputStr;
416
- } else if (aiSpan.type === AISpanType.TOOL_CALL || aiSpan.type === AISpanType.MCP_TOOL_CALL) {
417
- attributes["gen_ai.tool.output"] = outputStr;
418
- }
366
+ if (modelAttrs.provider) {
367
+ attributes[ATTR_GEN_AI_PROVIDER_NAME] = normalizeProvider(modelAttrs.provider);
419
368
  }
420
- if (aiSpan.type === AISpanType.MODEL_GENERATION && aiSpan.attributes) {
421
- const modelAttrs = aiSpan.attributes;
422
- if (modelAttrs.model) {
423
- attributes["gen_ai.request.model"] = modelAttrs.model;
369
+ if (modelAttrs.agentId) {
370
+ attributes[ATTR_GEN_AI_AGENT_ID] = modelAttrs.agentId;
371
+ }
372
+ if (modelAttrs.agentName) {
373
+ attributes[ATTR_GEN_AI_AGENT_NAME] = modelAttrs.agentName;
374
+ }
375
+ if (modelAttrs.usage) {
376
+ const inputTokens = modelAttrs.usage.inputTokens ?? modelAttrs.usage.promptTokens;
377
+ const outputTokens = modelAttrs.usage.outputTokens ?? modelAttrs.usage.completionTokens;
378
+ if (inputTokens !== void 0) {
379
+ attributes[ATTR_GEN_AI_USAGE_INPUT_TOKENS] = inputTokens;
424
380
  }
425
- if (modelAttrs.provider) {
426
- attributes["gen_ai.system"] = modelAttrs.provider;
381
+ if (outputTokens !== void 0) {
382
+ attributes[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS] = outputTokens;
427
383
  }
428
- if (modelAttrs.usage) {
429
- const inputTokens = modelAttrs.usage.inputTokens ?? modelAttrs.usage.promptTokens;
430
- const outputTokens = modelAttrs.usage.outputTokens ?? modelAttrs.usage.completionTokens;
431
- if (inputTokens !== void 0) {
432
- attributes["gen_ai.usage.input_tokens"] = inputTokens;
433
- }
434
- if (outputTokens !== void 0) {
435
- attributes["gen_ai.usage.output_tokens"] = outputTokens;
436
- }
437
- if (modelAttrs.usage.totalTokens !== void 0) {
438
- attributes["gen_ai.usage.total_tokens"] = modelAttrs.usage.totalTokens;
439
- }
440
- if (modelAttrs.usage.reasoningTokens !== void 0) {
441
- attributes["gen_ai.usage.reasoning_tokens"] = modelAttrs.usage.reasoningTokens;
442
- }
443
- if (modelAttrs.usage.cachedInputTokens !== void 0) {
444
- attributes["gen_ai.usage.cached_input_tokens"] = modelAttrs.usage.cachedInputTokens;
445
- }
384
+ if (modelAttrs.usage.reasoningTokens !== void 0) {
385
+ attributes["gen_ai.usage.reasoning_tokens"] = modelAttrs.usage.reasoningTokens;
446
386
  }
447
- if (modelAttrs.parameters) {
448
- if (modelAttrs.parameters.temperature !== void 0) {
449
- attributes["gen_ai.request.temperature"] = modelAttrs.parameters.temperature;
450
- }
451
- if (modelAttrs.parameters.maxOutputTokens !== void 0) {
452
- attributes["gen_ai.request.max_tokens"] = modelAttrs.parameters.maxOutputTokens;
453
- }
454
- if (modelAttrs.parameters.topP !== void 0) {
455
- attributes["gen_ai.request.top_p"] = modelAttrs.parameters.topP;
456
- }
457
- if (modelAttrs.parameters.topK !== void 0) {
458
- attributes["gen_ai.request.top_k"] = modelAttrs.parameters.topK;
459
- }
460
- if (modelAttrs.parameters.presencePenalty !== void 0) {
461
- attributes["gen_ai.request.presence_penalty"] = modelAttrs.parameters.presencePenalty;
462
- }
463
- if (modelAttrs.parameters.frequencyPenalty !== void 0) {
464
- attributes["gen_ai.request.frequency_penalty"] = modelAttrs.parameters.frequencyPenalty;
465
- }
466
- if (modelAttrs.parameters.stopSequences) {
467
- attributes["gen_ai.request.stop_sequences"] = JSON.stringify(modelAttrs.parameters.stopSequences);
468
- }
469
- }
470
- if (modelAttrs.finishReason) {
471
- attributes["gen_ai.response.finish_reasons"] = modelAttrs.finishReason;
387
+ if (modelAttrs.usage.cachedInputTokens !== void 0) {
388
+ attributes["gen_ai.usage.cached_input_tokens"] = modelAttrs.usage.cachedInputTokens;
472
389
  }
473
390
  }
474
- if ((aiSpan.type === AISpanType.TOOL_CALL || aiSpan.type === AISpanType.MCP_TOOL_CALL) && aiSpan.attributes) {
475
- const toolAttrs = aiSpan.attributes;
476
- if (toolAttrs.toolId) {
477
- attributes["gen_ai.tool.name"] = toolAttrs.toolId;
391
+ if (modelAttrs.parameters) {
392
+ if (modelAttrs.parameters.temperature !== void 0) {
393
+ attributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = modelAttrs.parameters.temperature;
478
394
  }
479
- if (aiSpan.type === AISpanType.MCP_TOOL_CALL) {
480
- const mcpAttrs = toolAttrs;
481
- if (mcpAttrs.mcpServer) {
482
- attributes["mcp.server"] = mcpAttrs.mcpServer;
483
- }
484
- if (mcpAttrs.serverVersion) {
485
- attributes["mcp.server.version"] = mcpAttrs.serverVersion;
486
- }
487
- } else {
488
- if (toolAttrs.toolDescription) {
489
- attributes["gen_ai.tool.description"] = toolAttrs.toolDescription;
490
- }
395
+ if (modelAttrs.parameters.maxOutputTokens !== void 0) {
396
+ attributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = modelAttrs.parameters.maxOutputTokens;
491
397
  }
492
- if (toolAttrs.success !== void 0) {
493
- attributes["gen_ai.tool.success"] = toolAttrs.success;
398
+ if (modelAttrs.parameters.topP !== void 0) {
399
+ attributes[ATTR_GEN_AI_REQUEST_TOP_P] = modelAttrs.parameters.topP;
494
400
  }
495
- }
496
- if (aiSpan.type === AISpanType.AGENT_RUN && aiSpan.attributes) {
497
- const agentAttrs = aiSpan.attributes;
498
- if (agentAttrs.agentId) {
499
- attributes["agent.id"] = agentAttrs.agentId;
500
- attributes["gen_ai.agent.id"] = agentAttrs.agentId;
401
+ if (modelAttrs.parameters.topK !== void 0) {
402
+ attributes[ATTR_GEN_AI_REQUEST_TOP_K] = modelAttrs.parameters.topK;
501
403
  }
502
- if (agentAttrs.maxSteps) {
503
- attributes["agent.max_steps"] = agentAttrs.maxSteps;
404
+ if (modelAttrs.parameters.presencePenalty !== void 0) {
405
+ attributes[ATTR_GEN_AI_REQUEST_PRESENCE_PENALTY] = modelAttrs.parameters.presencePenalty;
504
406
  }
505
- if (agentAttrs.availableTools) {
506
- attributes["agent.available_tools"] = JSON.stringify(agentAttrs.availableTools);
407
+ if (modelAttrs.parameters.frequencyPenalty !== void 0) {
408
+ attributes[ATTR_GEN_AI_REQUEST_FREQUENCY_PENALTY] = modelAttrs.parameters.frequencyPenalty;
507
409
  }
508
- }
509
- if (aiSpan.type === AISpanType.WORKFLOW_RUN && aiSpan.attributes) {
510
- const workflowAttrs = aiSpan.attributes;
511
- if (workflowAttrs.workflowId) {
512
- attributes["workflow.id"] = workflowAttrs.workflowId;
410
+ if (modelAttrs.parameters.stopSequences) {
411
+ attributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = JSON.stringify(modelAttrs.parameters.stopSequences);
513
412
  }
514
- if (workflowAttrs.status) {
515
- attributes["workflow.status"] = workflowAttrs.status;
413
+ if (modelAttrs.parameters.seed) {
414
+ attributes[ATTR_GEN_AI_REQUEST_SEED] = modelAttrs.parameters.seed;
516
415
  }
517
416
  }
518
- if (aiSpan.errorInfo) {
519
- attributes["error"] = true;
520
- attributes["error.type"] = aiSpan.errorInfo.id || "unknown";
521
- attributes["error.message"] = aiSpan.errorInfo.message;
522
- if (aiSpan.errorInfo.domain) {
523
- attributes["error.domain"] = aiSpan.errorInfo.domain;
417
+ if (modelAttrs.finishReason) {
418
+ attributes[ATTR_GEN_AI_RESPONSE_FINISH_REASONS] = JSON.stringify([modelAttrs.finishReason]);
419
+ }
420
+ if (modelAttrs.responseModel) {
421
+ attributes[ATTR_GEN_AI_RESPONSE_MODEL] = modelAttrs.responseModel;
422
+ }
423
+ if (modelAttrs.responseId) {
424
+ attributes[ATTR_GEN_AI_RESPONSE_ID] = modelAttrs.responseId;
425
+ }
426
+ if (modelAttrs.serverAddress) {
427
+ attributes[ATTR_SERVER_ADDRESS] = modelAttrs.serverAddress;
428
+ }
429
+ if (modelAttrs.serverPort !== void 0) {
430
+ attributes[ATTR_SERVER_PORT] = modelAttrs.serverPort;
431
+ }
432
+ }
433
+ if ((span.type === SpanType.TOOL_CALL || span.type === SpanType.MCP_TOOL_CALL) && span.attributes) {
434
+ const toolAttrs = span.attributes;
435
+ if (toolAttrs.toolId) {
436
+ attributes[ATTR_GEN_AI_TOOL_NAME] = toolAttrs.toolId;
437
+ }
438
+ if (span.type === SpanType.MCP_TOOL_CALL) {
439
+ const mcpAttrs = toolAttrs;
440
+ if (mcpAttrs.mcpServer) {
441
+ attributes[ATTR_SERVER_ADDRESS] = mcpAttrs.mcpServer;
524
442
  }
525
- if (aiSpan.errorInfo.category) {
526
- attributes["error.category"] = aiSpan.errorInfo.category;
443
+ } else {
444
+ if (toolAttrs.toolDescription) {
445
+ attributes[ATTR_GEN_AI_TOOL_DESCRIPTION] = toolAttrs.toolDescription;
527
446
  }
528
447
  }
529
- if (aiSpan.metadata) {
530
- Object.entries(aiSpan.metadata).forEach(([key, value]) => {
531
- if (!attributes[key]) {
532
- if (value === null || value === void 0) {
533
- return;
534
- }
535
- if (typeof value === "object") {
536
- attributes[key] = JSON.stringify(value);
537
- } else {
538
- attributes[key] = value;
539
- }
540
- }
541
- });
448
+ }
449
+ if (span.type === SpanType.AGENT_RUN && span.attributes) {
450
+ const agentAttrs = span.attributes;
451
+ if (agentAttrs.agentId) {
452
+ attributes[ATTR_GEN_AI_AGENT_ID] = agentAttrs.agentId;
453
+ }
454
+ if (agentAttrs.agentName) {
455
+ attributes[ATTR_GEN_AI_AGENT_NAME] = agentAttrs.agentName;
456
+ }
457
+ if (agentAttrs.conversationId) {
458
+ attributes[ATTR_GEN_AI_CONVERSATION_ID] = agentAttrs.conversationId;
459
+ }
460
+ if (agentAttrs.maxSteps) {
461
+ attributes[`mastra.${spanType}.max_steps`] = agentAttrs.maxSteps;
462
+ }
463
+ if (agentAttrs.availableTools) {
464
+ attributes[`gen_ai.tool.definitions`] = JSON.stringify(agentAttrs.availableTools);
465
+ }
466
+ attributes[ATTR_GEN_AI_SYSTEM_INSTRUCTIONS] = agentAttrs.instructions;
467
+ }
468
+ if (span.errorInfo) {
469
+ attributes[ATTR_ERROR_TYPE] = span.errorInfo.id || "unknown";
470
+ attributes[ATTR_ERROR_MESSAGE] = span.errorInfo.message;
471
+ if (span.errorInfo.domain) {
472
+ attributes["error.domain"] = span.errorInfo.domain;
542
473
  }
543
- if (aiSpan.startTime) {
544
- attributes["mastra.start_time"] = aiSpan.startTime.toISOString();
474
+ if (span.errorInfo.category) {
475
+ attributes["error.category"] = span.errorInfo.category;
545
476
  }
546
- if (aiSpan.endTime) {
547
- attributes["mastra.end_time"] = aiSpan.endTime.toISOString();
548
- const duration = aiSpan.endTime.getTime() - aiSpan.startTime.getTime();
549
- attributes["mastra.duration_ms"] = duration;
477
+ }
478
+ return attributes;
479
+ }
480
+ var PROVIDER_ALIASES = {
481
+ anthropic: ["anthropic", "claude"],
482
+ "aws.bedrock": ["awsbedrock", "bedrock", "amazonbedrock"],
483
+ "azure.ai.inference": ["azureaiinference", "azureinference"],
484
+ "azure.ai.openai": ["azureaiopenai", "azureopenai", "msopenai", "microsoftopenai"],
485
+ cohere: ["cohere"],
486
+ deepseek: ["deepseek"],
487
+ "gcp.gemini": ["gcpgemini", "gemini"],
488
+ "gcp.gen_ai": ["gcpgenai", "googlegenai", "googleai"],
489
+ "gcp.vertex_ai": ["gcpvertexai", "vertexai"],
490
+ groq: ["groq"],
491
+ "ibm.watsonx.ai": ["ibmwatsonxai", "watsonx", "watsonxai"],
492
+ mistral_ai: ["mistral", "mistralai"],
493
+ openai: ["openai", "oai"],
494
+ perplexity: ["perplexity", "pplx"],
495
+ x_ai: ["xai", "x-ai", "x_ai", "x.com ai"]
496
+ };
497
+ function normalizeProviderString(input) {
498
+ return input.toLowerCase().replace(/[^a-z0-9]/g, "");
499
+ }
500
+ function normalizeProvider(providerName) {
501
+ const normalized = normalizeProviderString(providerName);
502
+ for (const [canonical, aliases] of Object.entries(PROVIDER_ALIASES)) {
503
+ for (const alias of aliases) {
504
+ if (normalized === alias) {
505
+ return canonical;
506
+ }
550
507
  }
551
- return attributes;
552
508
  }
509
+ return providerName.toLowerCase();
510
+ }
511
+
512
+ // src/span-converter.ts
513
+ var SpanConverter = class {
514
+ constructor(params) {
515
+ this.params = params;
516
+ this.format = params.format;
517
+ }
518
+ resource;
519
+ scope;
520
+ initPromise;
521
+ format;
553
522
  /**
554
- * Get the operation name based on span type for gen_ai.operation.name
523
+ * Lazily initialize resource & scope on first use.
524
+ * Subsequent calls reuse the same promise (no races).
555
525
  */
556
- getOperationName(aiSpan) {
557
- switch (aiSpan.type) {
558
- case AISpanType.MODEL_GENERATION: {
559
- const attrs = aiSpan.attributes;
560
- return attrs?.resultType === "tool_selection" ? "tool_selection" : "chat";
526
+ async initIfNeeded() {
527
+ if (this.initPromise) {
528
+ return this.initPromise;
529
+ }
530
+ this.initPromise = (async () => {
531
+ const packageVersion = await getPackageVersion(this.params.packageName) ?? "unknown";
532
+ const serviceVersion = await getPackageVersion("@mastra/core") ?? "unknown";
533
+ let resource = resourceFromAttributes({
534
+ [ATTR_SERVICE_NAME]: this.params.serviceName || "mastra-service",
535
+ [ATTR_SERVICE_VERSION]: serviceVersion,
536
+ [ATTR_TELEMETRY_SDK_NAME]: this.params.packageName,
537
+ [ATTR_TELEMETRY_SDK_VERSION]: packageVersion,
538
+ [ATTR_TELEMETRY_SDK_LANGUAGE]: "nodejs"
539
+ });
540
+ if (this.params.config?.resourceAttributes) {
541
+ resource = resource.merge(
542
+ // Duplicate attributes from config will override defaults above
543
+ resourceFromAttributes(this.params.config.resourceAttributes)
544
+ );
561
545
  }
562
- case AISpanType.TOOL_CALL:
563
- case AISpanType.MCP_TOOL_CALL:
564
- return "tool.execute";
565
- case AISpanType.AGENT_RUN:
566
- return "agent.run";
567
- case AISpanType.WORKFLOW_RUN:
568
- return "workflow.run";
569
- default:
570
- return aiSpan.type.replace(/_/g, ".");
571
- }
546
+ this.resource = resource;
547
+ this.scope = {
548
+ name: this.params.packageName,
549
+ version: packageVersion
550
+ };
551
+ })();
552
+ return this.initPromise;
572
553
  }
573
554
  /**
574
- * Get span kind as string for attribute
555
+ * Convert a Mastra Span to an OpenTelemetry ReadableSpan
575
556
  */
576
- getSpanKindString(aiSpan) {
577
- const kind = this.getSpanKind(aiSpan);
578
- switch (kind) {
579
- case SpanKind.SERVER:
580
- return "server";
581
- case SpanKind.CLIENT:
582
- return "client";
583
- case SpanKind.INTERNAL:
584
- return "internal";
585
- case SpanKind.PRODUCER:
586
- return "producer";
587
- case SpanKind.CONSUMER:
588
- return "consumer";
589
- default:
590
- return "internal";
557
+ async convertSpan(span) {
558
+ await this.initIfNeeded();
559
+ if (!this.resource || !this.scope) {
560
+ throw new Error("SpanConverter not initialized correctly");
561
+ }
562
+ const name = getSpanName(span);
563
+ const kind = getSpanKind(span.type);
564
+ const attributes = getAttributes(span);
565
+ if (span.metadata) {
566
+ for (const [k, v] of Object.entries(span.metadata)) {
567
+ if (v === null || v === void 0) {
568
+ continue;
569
+ }
570
+ attributes[`mastra.metadata.${k}`] = typeof v === "object" ? JSON.stringify(v) : v;
571
+ }
591
572
  }
573
+ if (span.isRootSpan && span.tags?.length) {
574
+ attributes["mastra.tags"] = JSON.stringify(span.tags);
575
+ }
576
+ const startTime = dateToHrTime(span.startTime);
577
+ const endTime = span.endTime ? dateToHrTime(span.endTime) : startTime;
578
+ const duration = computeDuration(span.startTime, span.endTime);
579
+ const { status, events } = buildStatusAndEvents(span, startTime);
580
+ const spanContext = {
581
+ traceId: span.traceId,
582
+ spanId: span.id,
583
+ traceFlags: TraceFlags.SAMPLED,
584
+ isRemote: false
585
+ };
586
+ const parentSpanContext = span.parentSpanId ? {
587
+ traceId: span.traceId,
588
+ spanId: span.parentSpanId,
589
+ traceFlags: TraceFlags.SAMPLED,
590
+ isRemote: false
591
+ } : void 0;
592
+ const links = [];
593
+ const readable = {
594
+ name,
595
+ kind,
596
+ spanContext: () => spanContext,
597
+ parentSpanContext,
598
+ startTime,
599
+ endTime,
600
+ status,
601
+ attributes,
602
+ links,
603
+ events,
604
+ duration,
605
+ ended: !!span.endTime,
606
+ resource: this.resource,
607
+ instrumentationScope: this.scope,
608
+ droppedAttributesCount: 0,
609
+ droppedEventsCount: 0,
610
+ droppedLinksCount: 0
611
+ };
612
+ return readable;
592
613
  }
593
614
  };
615
+ async function getPackageVersion(pkgName) {
616
+ try {
617
+ const manifestUrl = new URL(await import.meta.resolve(`${pkgName}/package.json`));
618
+ const path = fileURLToPath(manifestUrl);
619
+ const pkgJson = JSON.parse(readFileSync(path, "utf8"));
620
+ return pkgJson.version;
621
+ } catch {
622
+ return void 0;
623
+ }
624
+ }
625
+ function getSpanKind(type) {
626
+ switch (type) {
627
+ case SpanType.MODEL_GENERATION:
628
+ case SpanType.MCP_TOOL_CALL:
629
+ return SpanKind.CLIENT;
630
+ default:
631
+ return SpanKind.INTERNAL;
632
+ }
633
+ }
634
+ function dateToHrTime(date) {
635
+ const ms = date.getTime();
636
+ const seconds = Math.floor(ms / 1e3);
637
+ const nanoseconds = ms % 1e3 * 1e6;
638
+ return [seconds, nanoseconds];
639
+ }
640
+ function computeDuration(start, end) {
641
+ if (!end) return [0, 0];
642
+ const diffMs = end.getTime() - start.getTime();
643
+ return [Math.floor(diffMs / 1e3), diffMs % 1e3 * 1e6];
644
+ }
645
+ function buildStatusAndEvents(span, defaultTime) {
646
+ const events = [];
647
+ if (span.errorInfo) {
648
+ const status = {
649
+ code: SpanStatusCode.ERROR,
650
+ message: span.errorInfo.message
651
+ };
652
+ events.push({
653
+ name: "exception",
654
+ attributes: {
655
+ "exception.message": span.errorInfo.message,
656
+ "exception.type": "Error",
657
+ ...span.errorInfo.details?.stack && {
658
+ "exception.stacktrace": span.errorInfo.details.stack
659
+ }
660
+ },
661
+ time: defaultTime,
662
+ droppedAttributesCount: 0
663
+ });
664
+ return { status, events };
665
+ }
666
+ if (span.endTime) {
667
+ return {
668
+ status: { code: SpanStatusCode.OK },
669
+ events
670
+ };
671
+ }
672
+ return {
673
+ status: { code: SpanStatusCode.UNSET },
674
+ events
675
+ };
676
+ }
594
677
 
595
- // src/ai-tracing.ts
596
- var OtelExporter = class {
678
+ // src/tracing.ts
679
+ var OtelExporter = class extends BaseExporter {
597
680
  config;
598
- tracingConfig;
681
+ observabilityConfig;
599
682
  spanConverter;
600
683
  processor;
601
684
  exporter;
602
685
  isSetup = false;
603
- isDisabled = false;
604
- logger;
605
686
  name = "opentelemetry";
606
687
  constructor(config) {
688
+ super(config);
607
689
  this.config = config;
608
- this.spanConverter = new SpanConverter();
609
- this.logger = new ConsoleLogger({ level: config.logLevel ?? "warn" });
610
690
  if (config.logLevel === "debug") {
611
691
  diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
612
692
  }
@@ -614,8 +694,8 @@ var OtelExporter = class {
614
694
  /**
615
695
  * Initialize with tracing configuration
616
696
  */
617
- init(config) {
618
- this.tracingConfig = config;
697
+ init(options) {
698
+ this.observabilityConfig = options.config;
619
699
  }
620
700
  async setupExporter() {
621
701
  if (this.isSetup || this.exporter) return;
@@ -693,21 +773,12 @@ var OtelExporter = class {
693
773
  }
694
774
  async setupProcessor() {
695
775
  if (this.processor || this.isSetup) return;
696
- let resource = resourceFromAttributes({
697
- [ATTR_SERVICE_NAME]: this.tracingConfig?.serviceName || "mastra-service",
698
- [ATTR_SERVICE_VERSION]: "1.0.0",
699
- // Add telemetry SDK information
700
- [ATTR_TELEMETRY_SDK_NAME]: "@mastra/otel-exporter",
701
- [ATTR_TELEMETRY_SDK_VERSION]: "1.0.0",
702
- [ATTR_TELEMETRY_SDK_LANGUAGE]: "nodejs"
776
+ this.spanConverter = new SpanConverter({
777
+ packageName: "@mastra/otel-exporter",
778
+ serviceName: this.observabilityConfig?.serviceName,
779
+ config: this.config,
780
+ format: "GenAI_v1_38_0"
703
781
  });
704
- if (this.config.resourceAttributes) {
705
- resource = resource.merge(
706
- // Duplicate attributes from config will override defaults above
707
- resourceFromAttributes(this.config.resourceAttributes)
708
- );
709
- }
710
- this.spanConverter = new SpanConverter(resource);
711
782
  this.processor = new BatchSpanProcessor(this.exporter, {
712
783
  maxExportBatchSize: this.config.batchSize || 512,
713
784
  // Default batch size
@@ -728,11 +799,8 @@ var OtelExporter = class {
728
799
  await this.setupProcessor();
729
800
  this.isSetup = true;
730
801
  }
731
- async exportEvent(event) {
732
- if (this.isDisabled) {
733
- return;
734
- }
735
- if (event.type !== AITracingEventType.SPAN_ENDED) {
802
+ async _exportTracingEvent(event) {
803
+ if (event.type !== TracingEventType.SPAN_ENDED) {
736
804
  return;
737
805
  }
738
806
  const span = event.exportedSpan;
@@ -746,9 +814,9 @@ var OtelExporter = class {
746
814
  return;
747
815
  }
748
816
  try {
749
- const readableSpan = this.spanConverter.convertSpan(span);
817
+ const otelSpan = await this.spanConverter.convertSpan(span);
750
818
  await new Promise((resolve) => {
751
- this.processor.onEnd(readableSpan);
819
+ this.processor.onEnd(otelSpan);
752
820
  resolve();
753
821
  });
754
822
  this.logger.debug(
@@ -765,6 +833,6 @@ var OtelExporter = class {
765
833
  }
766
834
  };
767
835
 
768
- export { OtelExporter };
836
+ export { OtelExporter, SpanConverter, getSpanKind };
769
837
  //# sourceMappingURL=index.js.map
770
838
  //# sourceMappingURL=index.js.map