@mastra/otel-exporter 0.0.0-feat-improve-processors-20251205191721 → 0.0.0-feat-mcp-embedded-docs-tools-clean-20260102135536

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