@mastra/arthur 0.4.5 → 0.4.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @mastra/arthur
2
2
 
3
+ ## 0.4.6
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`ce93a3c`](https://github.com/mastra-ai/mastra/commit/ce93a3c114ea1cbfbd576f3db41d7c26c9844f5b), [`5718a22`](https://github.com/mastra-ai/mastra/commit/5718a229281dcfd36bcd1f42a242e3717e510a33), [`a211d09`](https://github.com/mastra-ai/mastra/commit/a211d09185dc65a746534914cf38b67f21ee9bac), [`0dca9d0`](https://github.com/mastra-ai/mastra/commit/0dca9d0b1356024a53b72ea6f040db528b126caa), [`6218217`](https://github.com/mastra-ai/mastra/commit/62182171b6cfca0b099f1c6a77a2e65e7639ab86), [`5807d3a`](https://github.com/mastra-ai/mastra/commit/5807d3ae1d259b8b7d6df7e5bf2b485c694af9c8), [`57661af`](https://github.com/mastra-ai/mastra/commit/57661afeca52ff9af4e72675ede2134fa503d5a5), [`05db566`](https://github.com/mastra-ai/mastra/commit/05db566fcbdcbf33d0bffca0c72ec30129e2e3ca), [`57661af`](https://github.com/mastra-ai/mastra/commit/57661afeca52ff9af4e72675ede2134fa503d5a5), [`57661af`](https://github.com/mastra-ai/mastra/commit/57661afeca52ff9af4e72675ede2134fa503d5a5), [`5718a22`](https://github.com/mastra-ai/mastra/commit/5718a229281dcfd36bcd1f42a242e3717e510a33), [`57661af`](https://github.com/mastra-ai/mastra/commit/57661afeca52ff9af4e72675ede2134fa503d5a5), [`d1b7e3a`](https://github.com/mastra-ai/mastra/commit/d1b7e3a978a309a5653eeaa490d2d6c7c53bd093), [`29c584a`](https://github.com/mastra-ai/mastra/commit/29c584a13a88831e5ed1fdeb0ff8e82eae180433), [`c093146`](https://github.com/mastra-ai/mastra/commit/c0931466404d3c521308ea119cb165bb7e695155), [`8124754`](https://github.com/mastra-ai/mastra/commit/8124754ae89fbc69f8136d1df4a91904d0f84c4e), [`d12b2e4`](https://github.com/mastra-ai/mastra/commit/d12b2e4023fd9e3d3e93a9169f5088bcee2a849c)]:
8
+ - @mastra/core@1.54.0
9
+ - @mastra/otel-exporter@1.3.6
10
+
11
+ ## 0.4.6-alpha.0
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies [[`ce93a3c`](https://github.com/mastra-ai/mastra/commit/ce93a3c114ea1cbfbd576f3db41d7c26c9844f5b), [`5718a22`](https://github.com/mastra-ai/mastra/commit/5718a229281dcfd36bcd1f42a242e3717e510a33), [`5807d3a`](https://github.com/mastra-ai/mastra/commit/5807d3ae1d259b8b7d6df7e5bf2b485c694af9c8), [`57661af`](https://github.com/mastra-ai/mastra/commit/57661afeca52ff9af4e72675ede2134fa503d5a5), [`57661af`](https://github.com/mastra-ai/mastra/commit/57661afeca52ff9af4e72675ede2134fa503d5a5), [`57661af`](https://github.com/mastra-ai/mastra/commit/57661afeca52ff9af4e72675ede2134fa503d5a5), [`5718a22`](https://github.com/mastra-ai/mastra/commit/5718a229281dcfd36bcd1f42a242e3717e510a33), [`57661af`](https://github.com/mastra-ai/mastra/commit/57661afeca52ff9af4e72675ede2134fa503d5a5), [`d1b7e3a`](https://github.com/mastra-ai/mastra/commit/d1b7e3a978a309a5653eeaa490d2d6c7c53bd093), [`c093146`](https://github.com/mastra-ai/mastra/commit/c0931466404d3c521308ea119cb165bb7e695155)]:
16
+ - @mastra/core@1.54.0-alpha.1
17
+ - @mastra/otel-exporter@1.3.6-alpha.0
18
+
3
19
  ## 0.4.5
4
20
 
5
21
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -1,244 +1,235 @@
1
- 'use strict';
2
-
3
- var otelExporter = require('@mastra/otel-exporter');
4
- var openinferenceGenai = require('@arizeai/openinference-genai');
5
- var openinferenceSemanticConventions = require('@arizeai/openinference-semantic-conventions');
6
- var exporterTraceOtlpProto = require('@opentelemetry/exporter-trace-otlp-proto');
7
- var incubating = require('@opentelemetry/semantic-conventions/incubating');
8
-
9
- // src/tracing.ts
10
- var GEN_AI_USAGE_REASONING_TOKENS = "gen_ai.usage.reasoning_tokens";
11
- var GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens";
12
- var GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS = "gen_ai.usage.cache_creation.input_tokens";
13
- var GEN_AI_USAGE_AUDIO_INPUT_TOKENS = "gen_ai.usage.audio_input_tokens";
14
- var GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS = "gen_ai.usage.audio_output_tokens";
15
- var MASTRA_GENERAL_PREFIX = "mastra.";
16
- var MASTRA_METADATA_PREFIX = "mastra.metadata.";
17
- var MASTRA_MODEL_STEP_INPUT = "mastra.model_step.input";
18
- var MASTRA_MODEL_STEP_OUTPUT = "mastra.model_step.output";
19
- var MASTRA_MODEL_CHUNK_OUTPUT = "mastra.model_chunk.output";
20
- var MASTRA_SPAN_TYPE = "mastra.span.type";
21
- var SPAN_TYPE_TO_KIND = {
22
- // Model spans -> LLM
23
- model_generation: openinferenceSemanticConventions.OpenInferenceSpanKind.LLM,
24
- model_step: openinferenceSemanticConventions.OpenInferenceSpanKind.LLM,
25
- model_chunk: openinferenceSemanticConventions.OpenInferenceSpanKind.LLM,
26
- // Tool spans -> TOOL
27
- tool_call: openinferenceSemanticConventions.OpenInferenceSpanKind.TOOL,
28
- mcp_tool_call: openinferenceSemanticConventions.OpenInferenceSpanKind.TOOL,
29
- // Agent spans -> AGENT
30
- agent_run: openinferenceSemanticConventions.OpenInferenceSpanKind.AGENT
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _mastra_otel_exporter = require("@mastra/otel-exporter");
3
+ let _arizeai_openinference_genai = require("@arizeai/openinference-genai");
4
+ let _arizeai_openinference_semantic_conventions = require("@arizeai/openinference-semantic-conventions");
5
+ let _opentelemetry_exporter_trace_otlp_proto = require("@opentelemetry/exporter-trace-otlp-proto");
6
+ let _opentelemetry_semantic_conventions_incubating = require("@opentelemetry/semantic-conventions/incubating");
7
+ //#region src/openInferenceOTLPExporter.ts
8
+ const GEN_AI_USAGE_REASONING_TOKENS = "gen_ai.usage.reasoning_tokens";
9
+ const GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens";
10
+ const GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS = "gen_ai.usage.cache_creation.input_tokens";
11
+ const GEN_AI_USAGE_AUDIO_INPUT_TOKENS = "gen_ai.usage.audio_input_tokens";
12
+ const GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS = "gen_ai.usage.audio_output_tokens";
13
+ const MASTRA_GENERAL_PREFIX = "mastra.";
14
+ const MASTRA_METADATA_PREFIX = "mastra.metadata.";
15
+ const MASTRA_MODEL_STEP_INPUT = "mastra.model_step.input";
16
+ const MASTRA_MODEL_STEP_OUTPUT = "mastra.model_step.output";
17
+ const MASTRA_MODEL_CHUNK_OUTPUT = "mastra.model_chunk.output";
18
+ const MASTRA_SPAN_TYPE = "mastra.span.type";
19
+ /**
20
+ * Maps Mastra span types to OpenInference span kinds for proper trace categorization.
21
+ *
22
+ * Only non-CHAIN types are mapped here - all other span types default to CHAIN.
23
+ */
24
+ const SPAN_TYPE_TO_KIND = {
25
+ model_generation: _arizeai_openinference_semantic_conventions.OpenInferenceSpanKind.LLM,
26
+ model_step: _arizeai_openinference_semantic_conventions.OpenInferenceSpanKind.LLM,
27
+ model_chunk: _arizeai_openinference_semantic_conventions.OpenInferenceSpanKind.LLM,
28
+ tool_call: _arizeai_openinference_semantic_conventions.OpenInferenceSpanKind.TOOL,
29
+ mcp_tool_call: _arizeai_openinference_semantic_conventions.OpenInferenceSpanKind.TOOL,
30
+ agent_run: _arizeai_openinference_semantic_conventions.OpenInferenceSpanKind.AGENT
31
31
  };
32
+ /**
33
+ * Converts GenAI usage metrics to OpenInference LLM token count attributes.
34
+ * Maps from OTEL GenAI semantic conventions to OpenInference semantic conventions.
35
+ *
36
+ * @param attributes - The span attributes containing GenAI usage metrics
37
+ * @returns OpenInference token count attributes
38
+ */
32
39
  function convertUsageMetricsToOpenInference(attributes) {
33
- const result = {};
34
- const inputTokens = attributes[incubating.ATTR_GEN_AI_USAGE_INPUT_TOKENS];
35
- const outputTokens = attributes[incubating.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS];
36
- if (inputTokens !== void 0) {
37
- result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_PROMPT] = inputTokens;
38
- }
39
- if (outputTokens !== void 0) {
40
- result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_COMPLETION] = outputTokens;
41
- }
42
- if (inputTokens !== void 0 && outputTokens !== void 0) {
43
- result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_TOTAL] = inputTokens + outputTokens;
44
- }
45
- const cacheReadInputTokens = attributes[GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS];
46
- if (cacheReadInputTokens !== void 0) {
47
- result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ] = cacheReadInputTokens;
48
- }
49
- const cacheCreationInputTokens = attributes[GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS];
50
- if (cacheCreationInputTokens !== void 0) {
51
- result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE] = cacheCreationInputTokens;
52
- }
53
- const reasoningTokens = attributes[GEN_AI_USAGE_REASONING_TOKENS];
54
- if (reasoningTokens !== void 0) {
55
- result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING] = reasoningTokens;
56
- }
57
- const audioInputTokens = attributes[GEN_AI_USAGE_AUDIO_INPUT_TOKENS];
58
- if (audioInputTokens !== void 0) {
59
- result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO] = audioInputTokens;
60
- }
61
- const audioOutputTokens = attributes[GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS];
62
- if (audioOutputTokens !== void 0) {
63
- result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO] = audioOutputTokens;
64
- }
65
- return result;
40
+ const result = {};
41
+ const inputTokens = attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_USAGE_INPUT_TOKENS];
42
+ const outputTokens = attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS];
43
+ if (inputTokens !== void 0) result[_arizeai_openinference_semantic_conventions.LLM_TOKEN_COUNT_PROMPT] = inputTokens;
44
+ if (outputTokens !== void 0) result[_arizeai_openinference_semantic_conventions.LLM_TOKEN_COUNT_COMPLETION] = outputTokens;
45
+ if (inputTokens !== void 0 && outputTokens !== void 0) result[_arizeai_openinference_semantic_conventions.LLM_TOKEN_COUNT_TOTAL] = inputTokens + outputTokens;
46
+ const cacheReadInputTokens = attributes[GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS];
47
+ if (cacheReadInputTokens !== void 0) result[_arizeai_openinference_semantic_conventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ] = cacheReadInputTokens;
48
+ const cacheCreationInputTokens = attributes[GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS];
49
+ if (cacheCreationInputTokens !== void 0) result[_arizeai_openinference_semantic_conventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE] = cacheCreationInputTokens;
50
+ const reasoningTokens = attributes[GEN_AI_USAGE_REASONING_TOKENS];
51
+ if (reasoningTokens !== void 0) result[_arizeai_openinference_semantic_conventions.LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING] = reasoningTokens;
52
+ const audioInputTokens = attributes[GEN_AI_USAGE_AUDIO_INPUT_TOKENS];
53
+ if (audioInputTokens !== void 0) result[_arizeai_openinference_semantic_conventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO] = audioInputTokens;
54
+ const audioOutputTokens = attributes[GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS];
55
+ if (audioOutputTokens !== void 0) result[_arizeai_openinference_semantic_conventions.LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO] = audioOutputTokens;
56
+ return result;
66
57
  }
58
+ /**
59
+ * Splits Mastra span attributes into two groups:
60
+ * - `metadata`: keys starting with "mastra.metadata." (prefix removed)
61
+ * - `other`: all remaining keys starting with "mastra."
62
+ *
63
+ * Any attributes not starting with "mastra." are ignored entirely.
64
+ */
67
65
  function splitMastraAttributes(attributes) {
68
- return Object.entries(attributes).reduce(
69
- (acc, [key, value]) => {
70
- if (key.startsWith(MASTRA_GENERAL_PREFIX)) {
71
- if (key.startsWith(MASTRA_METADATA_PREFIX)) {
72
- const strippedKey = key.slice(MASTRA_METADATA_PREFIX.length);
73
- acc.mastraMetadata[strippedKey] = value;
74
- } else {
75
- acc.mastraOther[key] = value;
76
- }
77
- }
78
- return acc;
79
- },
80
- {
81
- mastraMetadata: {},
82
- mastraOther: {}
83
- }
84
- );
66
+ return Object.entries(attributes).reduce((acc, [key, value]) => {
67
+ if (key.startsWith(MASTRA_GENERAL_PREFIX)) if (key.startsWith(MASTRA_METADATA_PREFIX)) {
68
+ const strippedKey = key.slice(16);
69
+ acc.mastraMetadata[strippedKey] = value;
70
+ } else acc.mastraOther[key] = value;
71
+ return acc;
72
+ }, {
73
+ mastraMetadata: {},
74
+ mastraOther: {}
75
+ });
85
76
  }
86
- var OpenInferenceOTLPTraceExporter = class extends exporterTraceOtlpProto.OTLPTraceExporter {
87
- export(spans, resultCallback) {
88
- const processedSpans = spans.map((span) => {
89
- const attributes = { ...span.attributes ?? {} };
90
- const mutableSpan = span;
91
- const { mastraMetadata, mastraOther } = splitMastraAttributes(attributes);
92
- const processedAttributes = openinferenceGenai.convertGenAISpanAttributesToOpenInferenceSpanAttributes(attributes);
93
- if (processedAttributes) {
94
- const threadId = mastraMetadata["threadId"];
95
- if (threadId) {
96
- delete mastraMetadata["threadId"];
97
- processedAttributes[openinferenceSemanticConventions.SESSION_ID] = threadId;
98
- }
99
- if (mastraOther["mastra.tags"]) {
100
- processedAttributes[openinferenceSemanticConventions.TAG_TAGS] = mastraOther["mastra.tags"];
101
- delete mastraOther["mastra.tags"];
102
- }
103
- const userId = mastraMetadata["userId"];
104
- if (userId) {
105
- delete mastraMetadata["userId"];
106
- processedAttributes[openinferenceSemanticConventions.USER_ID] = userId;
107
- }
108
- if (Object.keys(mastraMetadata).length > 0) {
109
- try {
110
- processedAttributes[openinferenceSemanticConventions.METADATA] = JSON.stringify(mastraMetadata);
111
- } catch {
112
- }
113
- }
114
- const inputMessages = attributes[incubating.ATTR_GEN_AI_INPUT_MESSAGES] ?? attributes[incubating.ATTR_GEN_AI_TOOL_CALL_ARGUMENTS] ?? mastraOther[MASTRA_MODEL_STEP_INPUT];
115
- if (inputMessages) {
116
- processedAttributes[openinferenceSemanticConventions.INPUT_MIME_TYPE] = "application/json";
117
- processedAttributes[openinferenceSemanticConventions.INPUT_VALUE] = inputMessages;
118
- }
119
- const outputMessages = attributes[incubating.ATTR_GEN_AI_OUTPUT_MESSAGES] ?? attributes[incubating.ATTR_GEN_AI_TOOL_CALL_RESULT] ?? mastraOther[MASTRA_MODEL_STEP_OUTPUT] ?? mastraOther[MASTRA_MODEL_CHUNK_OUTPUT];
120
- if (outputMessages) {
121
- processedAttributes[openinferenceSemanticConventions.OUTPUT_MIME_TYPE] = "application/json";
122
- processedAttributes[openinferenceSemanticConventions.OUTPUT_VALUE] = outputMessages;
123
- }
124
- if (!processedAttributes[openinferenceSemanticConventions.INPUT_VALUE]) {
125
- for (const key of Object.keys(mastraOther)) {
126
- if (key.endsWith(".input")) {
127
- processedAttributes[openinferenceSemanticConventions.INPUT_MIME_TYPE] = "application/json";
128
- processedAttributes[openinferenceSemanticConventions.INPUT_VALUE] = mastraOther[key];
129
- break;
130
- }
131
- }
132
- }
133
- if (!processedAttributes[openinferenceSemanticConventions.OUTPUT_VALUE]) {
134
- for (const key of Object.keys(mastraOther)) {
135
- if (key.endsWith(".output")) {
136
- processedAttributes[openinferenceSemanticConventions.OUTPUT_MIME_TYPE] = "application/json";
137
- processedAttributes[openinferenceSemanticConventions.OUTPUT_VALUE] = mastraOther[key];
138
- break;
139
- }
140
- }
141
- }
142
- const usageMetrics = convertUsageMetricsToOpenInference(attributes);
143
- Object.assign(processedAttributes, usageMetrics);
144
- mutableSpan.attributes = { ...processedAttributes, ...mastraOther };
145
- const spanType = mastraOther[MASTRA_SPAN_TYPE];
146
- if (typeof spanType === "string") {
147
- mutableSpan.attributes[openinferenceSemanticConventions.SemanticConventions.OPENINFERENCE_SPAN_KIND] = SPAN_TYPE_TO_KIND[spanType] ?? openinferenceSemanticConventions.OpenInferenceSpanKind.CHAIN;
148
- }
149
- }
150
- return mutableSpan;
151
- });
152
- super.export(processedSpans, resultCallback);
153
- }
77
+ var OpenInferenceOTLPTraceExporter = class extends _opentelemetry_exporter_trace_otlp_proto.OTLPTraceExporter {
78
+ export(spans, resultCallback) {
79
+ const processedSpans = spans.map((span) => {
80
+ const attributes = { ...span.attributes ?? {} };
81
+ const mutableSpan = span;
82
+ const { mastraMetadata, mastraOther } = splitMastraAttributes(attributes);
83
+ const processedAttributes = (0, _arizeai_openinference_genai.convertGenAISpanAttributesToOpenInferenceSpanAttributes)(attributes);
84
+ if (processedAttributes) {
85
+ const threadId = mastraMetadata["threadId"];
86
+ if (threadId) {
87
+ delete mastraMetadata["threadId"];
88
+ processedAttributes[_arizeai_openinference_semantic_conventions.SESSION_ID] = threadId;
89
+ }
90
+ if (mastraOther["mastra.tags"]) {
91
+ processedAttributes[_arizeai_openinference_semantic_conventions.TAG_TAGS] = mastraOther["mastra.tags"];
92
+ delete mastraOther["mastra.tags"];
93
+ }
94
+ const userId = mastraMetadata["userId"];
95
+ if (userId) {
96
+ delete mastraMetadata["userId"];
97
+ processedAttributes[_arizeai_openinference_semantic_conventions.USER_ID] = userId;
98
+ }
99
+ if (Object.keys(mastraMetadata).length > 0) try {
100
+ processedAttributes[_arizeai_openinference_semantic_conventions.METADATA] = JSON.stringify(mastraMetadata);
101
+ } catch {}
102
+ const inputMessages = attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_INPUT_MESSAGES] ?? attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_TOOL_CALL_ARGUMENTS] ?? mastraOther[MASTRA_MODEL_STEP_INPUT];
103
+ if (inputMessages) {
104
+ processedAttributes[_arizeai_openinference_semantic_conventions.INPUT_MIME_TYPE] = "application/json";
105
+ processedAttributes[_arizeai_openinference_semantic_conventions.INPUT_VALUE] = inputMessages;
106
+ }
107
+ const outputMessages = attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_OUTPUT_MESSAGES] ?? attributes[_opentelemetry_semantic_conventions_incubating.ATTR_GEN_AI_TOOL_CALL_RESULT] ?? mastraOther[MASTRA_MODEL_STEP_OUTPUT] ?? mastraOther[MASTRA_MODEL_CHUNK_OUTPUT];
108
+ if (outputMessages) {
109
+ processedAttributes[_arizeai_openinference_semantic_conventions.OUTPUT_MIME_TYPE] = "application/json";
110
+ processedAttributes[_arizeai_openinference_semantic_conventions.OUTPUT_VALUE] = outputMessages;
111
+ }
112
+ if (!processedAttributes[_arizeai_openinference_semantic_conventions.INPUT_VALUE]) {
113
+ for (const key of Object.keys(mastraOther)) if (key.endsWith(".input")) {
114
+ processedAttributes[_arizeai_openinference_semantic_conventions.INPUT_MIME_TYPE] = "application/json";
115
+ processedAttributes[_arizeai_openinference_semantic_conventions.INPUT_VALUE] = mastraOther[key];
116
+ break;
117
+ }
118
+ }
119
+ if (!processedAttributes[_arizeai_openinference_semantic_conventions.OUTPUT_VALUE]) {
120
+ for (const key of Object.keys(mastraOther)) if (key.endsWith(".output")) {
121
+ processedAttributes[_arizeai_openinference_semantic_conventions.OUTPUT_MIME_TYPE] = "application/json";
122
+ processedAttributes[_arizeai_openinference_semantic_conventions.OUTPUT_VALUE] = mastraOther[key];
123
+ break;
124
+ }
125
+ }
126
+ const usageMetrics = convertUsageMetricsToOpenInference(attributes);
127
+ Object.assign(processedAttributes, usageMetrics);
128
+ mutableSpan.attributes = {
129
+ ...processedAttributes,
130
+ ...mastraOther
131
+ };
132
+ const spanType = mastraOther[MASTRA_SPAN_TYPE];
133
+ if (typeof spanType === "string") mutableSpan.attributes[_arizeai_openinference_semantic_conventions.SemanticConventions.OPENINFERENCE_SPAN_KIND] = SPAN_TYPE_TO_KIND[spanType] ?? _arizeai_openinference_semantic_conventions.OpenInferenceSpanKind.CHAIN;
134
+ }
135
+ return mutableSpan;
136
+ });
137
+ super.export(processedSpans, resultCallback);
138
+ }
154
139
  };
155
-
156
- // src/tracing.ts
157
- var LOG_PREFIX = "[ArthurExporter]";
158
- var ArthurExporter = class extends otelExporter.OtelExporter {
159
- name = "arthur";
160
- taskId;
161
- /**
162
- * @param config - Arthur exporter configuration. All fields are optional when
163
- * the corresponding environment variables are set.
164
- */
165
- constructor(config = {}) {
166
- const apiKey = config.apiKey ?? process.env.ARTHUR_API_KEY;
167
- const endpoint = config.endpoint ?? process.env.ARTHUR_BASE_URL;
168
- const taskId = config.taskId ?? process.env.ARTHUR_TASK_ID;
169
- const headers = {
170
- ...config.headers
171
- };
172
- let disabledReason;
173
- if (!apiKey) {
174
- disabledReason = `${LOG_PREFIX} API key is required. Set ARTHUR_API_KEY environment variable or pass apiKey in config.`;
175
- } else {
176
- headers["Authorization"] = `Bearer ${apiKey}`;
177
- }
178
- if (!disabledReason && !endpoint) {
179
- disabledReason = `${LOG_PREFIX} Endpoint is required. Set ARTHUR_BASE_URL environment variable or pass endpoint in config.`;
180
- }
181
- const tracesEndpoint = endpoint ? `${stripTrailingSlashes(endpoint)}/api/v1/traces` : "http://disabled";
182
- if (disabledReason) {
183
- super({
184
- ...config,
185
- provider: {
186
- custom: {
187
- endpoint: "http://disabled",
188
- headers: {},
189
- protocol: "http/protobuf"
190
- }
191
- }
192
- });
193
- this.setDisabled(disabledReason);
194
- return;
195
- }
196
- super({
197
- exporter: new OpenInferenceOTLPTraceExporter({
198
- url: tracesEndpoint,
199
- headers
200
- }),
201
- ...config,
202
- resourceAttributes: {
203
- ...taskId ? { "arthur.task.id": taskId } : {},
204
- ...config.resourceAttributes
205
- },
206
- provider: {
207
- custom: {
208
- endpoint: tracesEndpoint,
209
- headers,
210
- protocol: "http/protobuf"
211
- }
212
- }
213
- });
214
- this.taskId = taskId;
215
- }
216
- /**
217
- * Called after construction with the observability config.
218
- * Validates that traces can be routed to a task via either taskId or serviceName.
219
- */
220
- init(options) {
221
- super.init(options);
222
- const serviceName = options.config?.serviceName;
223
- if (this.taskId && serviceName) {
224
- this.logger.warn(
225
- `${LOG_PREFIX} Both taskId and serviceName are set. Arthur Engine will use serviceName to route traces, ignoring the provided taskId.`
226
- );
227
- } else if (!this.taskId && !serviceName) {
228
- this.logger.warn(
229
- `${LOG_PREFIX} Neither taskId nor serviceName is set. Set ARTHUR_TASK_ID environment variable, pass taskId in config, or set serviceName in the observability config so Arthur Engine can route traces to a task.`
230
- );
231
- }
232
- }
140
+ //#endregion
141
+ //#region src/tracing.ts
142
+ const LOG_PREFIX = "[ArthurExporter]";
143
+ /**
144
+ * Exports Mastra traces to Arthur AI using OpenInference semantic conventions.
145
+ *
146
+ * Supports zero-config setup via environment variables (ARTHUR_API_KEY, ARTHUR_BASE_URL,
147
+ * ARTHUR_TASK_ID) or explicit configuration. Automatically disables itself with a warning
148
+ * when required credentials are missing.
149
+ *
150
+ * @example
151
+ * ```ts
152
+ * const mastra = new Mastra({
153
+ * observability: new Observability({
154
+ * configs: {
155
+ * arthur: {
156
+ * serviceName: 'my-service',
157
+ * exporters: [new ArthurExporter()],
158
+ * },
159
+ * },
160
+ * }),
161
+ * });
162
+ * ```
163
+ */
164
+ var ArthurExporter = class extends _mastra_otel_exporter.OtelExporter {
165
+ name = "arthur";
166
+ taskId;
167
+ /**
168
+ * @param config - Arthur exporter configuration. All fields are optional when
169
+ * the corresponding environment variables are set.
170
+ */
171
+ constructor(config = {}) {
172
+ const apiKey = config.apiKey ?? process.env.ARTHUR_API_KEY;
173
+ const endpoint = config.endpoint ?? process.env.ARTHUR_BASE_URL;
174
+ const taskId = config.taskId ?? process.env.ARTHUR_TASK_ID;
175
+ const headers = { ...config.headers };
176
+ let disabledReason;
177
+ if (!apiKey) disabledReason = `${LOG_PREFIX} API key is required. Set ARTHUR_API_KEY environment variable or pass apiKey in config.`;
178
+ else headers["Authorization"] = `Bearer ${apiKey}`;
179
+ if (!disabledReason && !endpoint) disabledReason = `${LOG_PREFIX} Endpoint is required. Set ARTHUR_BASE_URL environment variable or pass endpoint in config.`;
180
+ const tracesEndpoint = endpoint ? `${stripTrailingSlashes(endpoint)}/api/v1/traces` : "http://disabled";
181
+ if (disabledReason) {
182
+ super({
183
+ ...config,
184
+ provider: { custom: {
185
+ endpoint: "http://disabled",
186
+ headers: {},
187
+ protocol: "http/protobuf"
188
+ } }
189
+ });
190
+ this.setDisabled(disabledReason);
191
+ return;
192
+ }
193
+ super({
194
+ exporter: new OpenInferenceOTLPTraceExporter({
195
+ url: tracesEndpoint,
196
+ headers
197
+ }),
198
+ ...config,
199
+ resourceAttributes: {
200
+ ...taskId ? { "arthur.task.id": taskId } : {},
201
+ ...config.resourceAttributes
202
+ },
203
+ provider: { custom: {
204
+ endpoint: tracesEndpoint,
205
+ headers,
206
+ protocol: "http/protobuf"
207
+ } }
208
+ });
209
+ this.taskId = taskId;
210
+ }
211
+ /**
212
+ * Called after construction with the observability config.
213
+ * Validates that traces can be routed to a task via either taskId or serviceName.
214
+ */
215
+ init(options) {
216
+ super.init(options);
217
+ const serviceName = options.config?.serviceName;
218
+ if (this.taskId && serviceName) this.logger.warn(`${LOG_PREFIX} Both taskId and serviceName are set. Arthur Engine will use serviceName to route traces, ignoring the provided taskId.`);
219
+ else if (!this.taskId && !serviceName) this.logger.warn(`${LOG_PREFIX} Neither taskId nor serviceName is set. Set ARTHUR_TASK_ID environment variable, pass taskId in config, or set serviceName in the observability config so Arthur Engine can route traces to a task.`);
220
+ }
233
221
  };
222
+ /**
223
+ * Remove trailing '/' characters procedurally. Avoids the polynomial
224
+ * backtracking that a greedy regex like `/\/+$/` can exhibit when the
225
+ * input is attacker-controlled.
226
+ */
234
227
  function stripTrailingSlashes(s) {
235
- let end = s.length;
236
- while (end > 0 && s.charCodeAt(end - 1) === 47) {
237
- end--;
238
- }
239
- return end === s.length ? s : s.slice(0, end);
228
+ let end = s.length;
229
+ while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
230
+ return end === s.length ? s : s.slice(0, end);
240
231
  }
241
-
232
+ //#endregion
242
233
  exports.ArthurExporter = ArthurExporter;
243
- //# sourceMappingURL=index.cjs.map
234
+
244
235
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/openInferenceOTLPExporter.ts","../src/tracing.ts"],"names":["OpenInferenceSpanKind","ATTR_GEN_AI_USAGE_INPUT_TOKENS","ATTR_GEN_AI_USAGE_OUTPUT_TOKENS","LLM_TOKEN_COUNT_PROMPT","LLM_TOKEN_COUNT_COMPLETION","LLM_TOKEN_COUNT_TOTAL","LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ","LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE","LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING","LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO","LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO","OTLPTraceExporter","convertGenAISpanAttributesToOpenInferenceSpanAttributes","SESSION_ID","TAG_TAGS","USER_ID","METADATA","ATTR_GEN_AI_INPUT_MESSAGES","ATTR_GEN_AI_TOOL_CALL_ARGUMENTS","INPUT_MIME_TYPE","INPUT_VALUE","ATTR_GEN_AI_OUTPUT_MESSAGES","ATTR_GEN_AI_TOOL_CALL_RESULT","OUTPUT_MIME_TYPE","OUTPUT_VALUE","SemanticConventions","OtelExporter"],"mappings":"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","file":"index.cjs","sourcesContent":["import { convertGenAISpanAttributesToOpenInferenceSpanAttributes } from '@arizeai/openinference-genai';\nimport type { Mutable } from '@arizeai/openinference-genai/types';\nimport {\n INPUT_MIME_TYPE,\n INPUT_VALUE,\n LLM_TOKEN_COUNT_COMPLETION,\n LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO,\n LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING,\n LLM_TOKEN_COUNT_PROMPT,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE,\n LLM_TOKEN_COUNT_TOTAL,\n METADATA,\n OpenInferenceSpanKind,\n OUTPUT_MIME_TYPE,\n OUTPUT_VALUE,\n SemanticConventions,\n SESSION_ID,\n TAG_TAGS,\n USER_ID,\n} from '@arizeai/openinference-semantic-conventions';\nimport type { ExportResult } from '@opentelemetry/core';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport {\n ATTR_GEN_AI_INPUT_MESSAGES,\n ATTR_GEN_AI_OUTPUT_MESSAGES,\n ATTR_GEN_AI_USAGE_INPUT_TOKENS,\n ATTR_GEN_AI_USAGE_OUTPUT_TOKENS,\n ATTR_GEN_AI_TOOL_CALL_ARGUMENTS,\n ATTR_GEN_AI_TOOL_CALL_RESULT,\n} from '@opentelemetry/semantic-conventions/incubating';\n\n// GenAI usage attribute keys (not all are in @opentelemetry/semantic-conventions yet)\nconst GEN_AI_USAGE_REASONING_TOKENS = 'gen_ai.usage.reasoning_tokens';\nconst GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = 'gen_ai.usage.cache_read.input_tokens';\nconst GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS = 'gen_ai.usage.cache_creation.input_tokens';\nconst GEN_AI_USAGE_AUDIO_INPUT_TOKENS = 'gen_ai.usage.audio_input_tokens';\nconst GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS = 'gen_ai.usage.audio_output_tokens';\n\nconst MASTRA_GENERAL_PREFIX = 'mastra.';\nconst MASTRA_METADATA_PREFIX = 'mastra.metadata.';\nconst MASTRA_MODEL_STEP_INPUT = 'mastra.model_step.input';\nconst MASTRA_MODEL_STEP_OUTPUT = 'mastra.model_step.output';\nconst MASTRA_MODEL_CHUNK_OUTPUT = 'mastra.model_chunk.output';\nconst MASTRA_SPAN_TYPE = 'mastra.span.type';\n\n/**\n * Maps Mastra span types to OpenInference span kinds for proper trace categorization.\n *\n * Only non-CHAIN types are mapped here - all other span types default to CHAIN.\n */\nconst SPAN_TYPE_TO_KIND: Record<string, OpenInferenceSpanKind> = {\n // Model spans -> LLM\n model_generation: OpenInferenceSpanKind.LLM,\n model_step: OpenInferenceSpanKind.LLM,\n model_chunk: OpenInferenceSpanKind.LLM,\n // Tool spans -> TOOL\n tool_call: OpenInferenceSpanKind.TOOL,\n mcp_tool_call: OpenInferenceSpanKind.TOOL,\n // Agent spans -> AGENT\n agent_run: OpenInferenceSpanKind.AGENT,\n};\n\n/**\n * Converts GenAI usage metrics to OpenInference LLM token count attributes.\n * Maps from OTEL GenAI semantic conventions to OpenInference semantic conventions.\n *\n * @param attributes - The span attributes containing GenAI usage metrics\n * @returns OpenInference token count attributes\n */\nfunction convertUsageMetricsToOpenInference(attributes: Record<string, any>): Record<string, any> {\n const result: Record<string, any> = {};\n\n const inputTokens = attributes[ATTR_GEN_AI_USAGE_INPUT_TOKENS];\n const outputTokens = attributes[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS];\n\n // Core token counts\n if (inputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT] = inputTokens;\n }\n if (outputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION] = outputTokens;\n }\n\n // Total tokens (compute if we have both input and output)\n if (inputTokens !== undefined && outputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_TOTAL] = inputTokens + outputTokens;\n }\n\n // Cache tokens (prompt details)\n const cacheReadInputTokens = attributes[GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS];\n if (cacheReadInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ] = cacheReadInputTokens;\n }\n\n const cacheCreationInputTokens = attributes[GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS];\n if (cacheCreationInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE] = cacheCreationInputTokens;\n }\n\n // Reasoning tokens (completion details)\n const reasoningTokens = attributes[GEN_AI_USAGE_REASONING_TOKENS];\n if (reasoningTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING] = reasoningTokens;\n }\n\n // Audio tokens\n const audioInputTokens = attributes[GEN_AI_USAGE_AUDIO_INPUT_TOKENS];\n if (audioInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO] = audioInputTokens;\n }\n\n const audioOutputTokens = attributes[GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS];\n if (audioOutputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO] = audioOutputTokens;\n }\n\n return result;\n}\n\n/**\n * Splits Mastra span attributes into two groups:\n * - `metadata`: keys starting with \"mastra.metadata.\" (prefix removed)\n * - `other`: all remaining keys starting with \"mastra.\"\n *\n * Any attributes not starting with \"mastra.\" are ignored entirely.\n */\nfunction splitMastraAttributes(attributes: Record<string, any>): {\n mastraMetadata: Record<string, any>;\n mastraOther: Record<string, any>;\n} {\n return Object.entries(attributes).reduce(\n (acc, [key, value]) => {\n if (key.startsWith(MASTRA_GENERAL_PREFIX)) {\n if (key.startsWith(MASTRA_METADATA_PREFIX)) {\n const strippedKey = key.slice(MASTRA_METADATA_PREFIX.length);\n acc.mastraMetadata[strippedKey] = value;\n } else {\n acc.mastraOther[key] = value;\n }\n }\n return acc;\n },\n {\n mastraMetadata: {} as Record<string, any>,\n mastraOther: {} as Record<string, any>,\n },\n );\n}\n\nexport class OpenInferenceOTLPTraceExporter extends OTLPTraceExporter {\n export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void) {\n const processedSpans = spans.map(span => {\n const attributes = { ...(span.attributes ?? {}) };\n const mutableSpan = span as Mutable<ReadableSpan>;\n\n const { mastraMetadata, mastraOther } = splitMastraAttributes(attributes);\n const processedAttributes = convertGenAISpanAttributesToOpenInferenceSpanAttributes(attributes);\n\n // only add processed attributes if conversion was successful\n if (processedAttributes) {\n const threadId = mastraMetadata['threadId'];\n if (threadId) {\n delete mastraMetadata['threadId'];\n processedAttributes[SESSION_ID] = threadId;\n }\n\n // Map mastra.tags to OpenInference native tag.tags convention (tags are only on root spans)\n if (mastraOther['mastra.tags']) {\n processedAttributes[TAG_TAGS] = mastraOther['mastra.tags'];\n delete mastraOther['mastra.tags'];\n }\n\n const userId = mastraMetadata['userId'];\n if (userId) {\n delete mastraMetadata['userId'];\n processedAttributes[USER_ID] = userId;\n }\n\n // Gather custom metadata into OpenInference metadata (flat best-effort)\n if (Object.keys(mastraMetadata).length > 0) {\n try {\n processedAttributes[METADATA] = JSON.stringify(mastraMetadata);\n } catch {\n // best-effort only\n }\n }\n\n const inputMessages =\n attributes[ATTR_GEN_AI_INPUT_MESSAGES] ??\n attributes[ATTR_GEN_AI_TOOL_CALL_ARGUMENTS] ??\n mastraOther[MASTRA_MODEL_STEP_INPUT];\n if (inputMessages) {\n processedAttributes[INPUT_MIME_TYPE] = 'application/json';\n processedAttributes[INPUT_VALUE] = inputMessages;\n }\n const outputMessages =\n attributes[ATTR_GEN_AI_OUTPUT_MESSAGES] ??\n attributes[ATTR_GEN_AI_TOOL_CALL_RESULT] ??\n mastraOther[MASTRA_MODEL_STEP_OUTPUT] ??\n mastraOther[MASTRA_MODEL_CHUNK_OUTPUT];\n if (outputMessages) {\n processedAttributes[OUTPUT_MIME_TYPE] = 'application/json';\n processedAttributes[OUTPUT_VALUE] = outputMessages;\n }\n\n // Map generic Mastra span input/output to OpenInference input/output\n // These are set by Mastra's gen-ai-semantics.ts for non-LLM/tool spans\n // (e.g., mastra.processor_run.input, mastra.workflow_run.input, etc.)\n if (!processedAttributes[INPUT_VALUE]) {\n for (const key of Object.keys(mastraOther)) {\n if (key.endsWith('.input')) {\n processedAttributes[INPUT_MIME_TYPE] = 'application/json';\n processedAttributes[INPUT_VALUE] = mastraOther[key];\n break;\n }\n }\n }\n if (!processedAttributes[OUTPUT_VALUE]) {\n for (const key of Object.keys(mastraOther)) {\n if (key.endsWith('.output')) {\n processedAttributes[OUTPUT_MIME_TYPE] = 'application/json';\n processedAttributes[OUTPUT_VALUE] = mastraOther[key];\n break;\n }\n }\n }\n\n // Convert GenAI usage metrics to OpenInference token count attributes\n const usageMetrics = convertUsageMetricsToOpenInference(attributes);\n Object.assign(processedAttributes, usageMetrics);\n\n mutableSpan.attributes = { ...processedAttributes, ...mastraOther };\n\n // Set span kind based on mastra.span.type for proper trace categorization\n const spanType = mastraOther[MASTRA_SPAN_TYPE];\n if (typeof spanType === 'string') {\n mutableSpan.attributes[SemanticConventions.OPENINFERENCE_SPAN_KIND] =\n SPAN_TYPE_TO_KIND[spanType] ?? OpenInferenceSpanKind.CHAIN;\n }\n }\n\n return mutableSpan;\n });\n\n super.export(processedSpans, resultCallback);\n }\n}\n","import type { InitExporterOptions } from '@mastra/core/observability';\nimport { OtelExporter } from '@mastra/otel-exporter';\nimport type { OtelExporterConfig } from '@mastra/otel-exporter';\n\nimport { OpenInferenceOTLPTraceExporter } from './openInferenceOTLPExporter.js';\n\nconst LOG_PREFIX = '[ArthurExporter]';\n\nexport type ArthurExporterConfig = Omit<OtelExporterConfig, 'provider' | 'exporter'> & {\n /**\n * Arthur API key for authentication.\n * Falls back to ARTHUR_API_KEY environment variable.\n */\n apiKey?: string;\n /**\n * Arthur platform endpoint (e.g. https://app.arthur.ai).\n * Falls back to ARTHUR_BASE_URL environment variable.\n */\n endpoint?: string;\n /**\n * Arthur task ID to associate traces with.\n * Falls back to ARTHUR_TASK_ID environment variable.\n * At least one of taskId or serviceName (from the observability config) should be provided.\n */\n taskId?: string;\n /**\n * Optional headers to be added to each OTLP request.\n * Note: the Authorization header is managed internally when apiKey is provided.\n */\n headers?: Record<string, string>;\n};\n\n/**\n * Exports Mastra traces to Arthur AI using OpenInference semantic conventions.\n *\n * Supports zero-config setup via environment variables (ARTHUR_API_KEY, ARTHUR_BASE_URL,\n * ARTHUR_TASK_ID) or explicit configuration. Automatically disables itself with a warning\n * when required credentials are missing.\n *\n * @example\n * ```ts\n * const mastra = new Mastra({\n * observability: new Observability({\n * configs: {\n * arthur: {\n * serviceName: 'my-service',\n * exporters: [new ArthurExporter()],\n * },\n * },\n * }),\n * });\n * ```\n */\nexport class ArthurExporter extends OtelExporter {\n name = 'arthur';\n private taskId?: string;\n\n /**\n * @param config - Arthur exporter configuration. All fields are optional when\n * the corresponding environment variables are set.\n */\n constructor(config: ArthurExporterConfig = {}) {\n const apiKey = config.apiKey ?? process.env.ARTHUR_API_KEY;\n const endpoint = config.endpoint ?? process.env.ARTHUR_BASE_URL;\n const taskId = config.taskId ?? process.env.ARTHUR_TASK_ID;\n\n const headers: Record<string, string> = {\n ...config.headers,\n };\n\n let disabledReason: string | undefined;\n\n if (!apiKey) {\n disabledReason =\n `${LOG_PREFIX} API key is required. ` + `Set ARTHUR_API_KEY environment variable or pass apiKey in config.`;\n } else {\n headers['Authorization'] = `Bearer ${apiKey}`;\n }\n\n if (!disabledReason && !endpoint) {\n disabledReason =\n `${LOG_PREFIX} Endpoint is required. ` + `Set ARTHUR_BASE_URL environment variable or pass endpoint in config.`;\n }\n\n // Ensure the endpoint ends with /api/v1/traces\n const tracesEndpoint = endpoint ? `${stripTrailingSlashes(endpoint)}/api/v1/traces` : 'http://disabled';\n\n if (disabledReason) {\n super({\n ...config,\n provider: {\n custom: {\n endpoint: 'http://disabled',\n headers: {},\n protocol: 'http/protobuf',\n },\n },\n });\n this.setDisabled(disabledReason);\n return;\n }\n\n super({\n exporter: new OpenInferenceOTLPTraceExporter({\n url: tracesEndpoint,\n headers,\n }),\n ...config,\n resourceAttributes: {\n ...(taskId ? { 'arthur.task.id': taskId } : {}),\n ...config.resourceAttributes,\n },\n provider: {\n custom: {\n endpoint: tracesEndpoint,\n headers,\n protocol: 'http/protobuf',\n },\n } satisfies OtelExporterConfig['provider'],\n } satisfies OtelExporterConfig);\n\n this.taskId = taskId;\n }\n\n /**\n * Called after construction with the observability config.\n * Validates that traces can be routed to a task via either taskId or serviceName.\n */\n init(options: InitExporterOptions) {\n super.init(options);\n\n const serviceName = options.config?.serviceName;\n\n if (this.taskId && serviceName) {\n this.logger.warn(\n `${LOG_PREFIX} Both taskId and serviceName are set. Arthur Engine will use serviceName to route traces, ` +\n `ignoring the provided taskId.`,\n );\n } else if (!this.taskId && !serviceName) {\n this.logger.warn(\n `${LOG_PREFIX} Neither taskId nor serviceName is set. Set ARTHUR_TASK_ID environment variable, ` +\n `pass taskId in config, or set serviceName in the observability config so Arthur Engine can route traces to a task.`,\n );\n }\n }\n}\n\n/**\n * Remove trailing '/' characters procedurally. Avoids the polynomial\n * backtracking that a greedy regex like `/\\/+$/` can exhibit when the\n * input is attacker-controlled.\n */\nfunction stripTrailingSlashes(s: string): string {\n let end = s.length;\n while (end > 0 && s.charCodeAt(end - 1) === 47 /* '/' */) {\n end--;\n }\n return end === s.length ? s : s.slice(0, end);\n}\n"]}
1
+ {"version":3,"file":"index.cjs","names":["OpenInferenceSpanKind","ATTR_GEN_AI_USAGE_INPUT_TOKENS","ATTR_GEN_AI_USAGE_OUTPUT_TOKENS","LLM_TOKEN_COUNT_PROMPT","LLM_TOKEN_COUNT_COMPLETION","LLM_TOKEN_COUNT_TOTAL","LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ","LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE","LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING","LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO","LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO","OTLPTraceExporter","SESSION_ID","TAG_TAGS","USER_ID","METADATA","ATTR_GEN_AI_INPUT_MESSAGES","ATTR_GEN_AI_TOOL_CALL_ARGUMENTS","INPUT_MIME_TYPE","INPUT_VALUE","ATTR_GEN_AI_OUTPUT_MESSAGES","ATTR_GEN_AI_TOOL_CALL_RESULT","OUTPUT_MIME_TYPE","OUTPUT_VALUE","SemanticConventions","OtelExporter"],"sources":["../src/openInferenceOTLPExporter.ts","../src/tracing.ts"],"sourcesContent":["import { convertGenAISpanAttributesToOpenInferenceSpanAttributes } from '@arizeai/openinference-genai';\nimport type { Mutable } from '@arizeai/openinference-genai/types';\nimport {\n INPUT_MIME_TYPE,\n INPUT_VALUE,\n LLM_TOKEN_COUNT_COMPLETION,\n LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO,\n LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING,\n LLM_TOKEN_COUNT_PROMPT,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE,\n LLM_TOKEN_COUNT_TOTAL,\n METADATA,\n OpenInferenceSpanKind,\n OUTPUT_MIME_TYPE,\n OUTPUT_VALUE,\n SemanticConventions,\n SESSION_ID,\n TAG_TAGS,\n USER_ID,\n} from '@arizeai/openinference-semantic-conventions';\nimport type { ExportResult } from '@opentelemetry/core';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport {\n ATTR_GEN_AI_INPUT_MESSAGES,\n ATTR_GEN_AI_OUTPUT_MESSAGES,\n ATTR_GEN_AI_USAGE_INPUT_TOKENS,\n ATTR_GEN_AI_USAGE_OUTPUT_TOKENS,\n ATTR_GEN_AI_TOOL_CALL_ARGUMENTS,\n ATTR_GEN_AI_TOOL_CALL_RESULT,\n} from '@opentelemetry/semantic-conventions/incubating';\n\n// GenAI usage attribute keys (not all are in @opentelemetry/semantic-conventions yet)\nconst GEN_AI_USAGE_REASONING_TOKENS = 'gen_ai.usage.reasoning_tokens';\nconst GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = 'gen_ai.usage.cache_read.input_tokens';\nconst GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS = 'gen_ai.usage.cache_creation.input_tokens';\nconst GEN_AI_USAGE_AUDIO_INPUT_TOKENS = 'gen_ai.usage.audio_input_tokens';\nconst GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS = 'gen_ai.usage.audio_output_tokens';\n\nconst MASTRA_GENERAL_PREFIX = 'mastra.';\nconst MASTRA_METADATA_PREFIX = 'mastra.metadata.';\nconst MASTRA_MODEL_STEP_INPUT = 'mastra.model_step.input';\nconst MASTRA_MODEL_STEP_OUTPUT = 'mastra.model_step.output';\nconst MASTRA_MODEL_CHUNK_OUTPUT = 'mastra.model_chunk.output';\nconst MASTRA_SPAN_TYPE = 'mastra.span.type';\n\n/**\n * Maps Mastra span types to OpenInference span kinds for proper trace categorization.\n *\n * Only non-CHAIN types are mapped here - all other span types default to CHAIN.\n */\nconst SPAN_TYPE_TO_KIND: Record<string, OpenInferenceSpanKind> = {\n // Model spans -> LLM\n model_generation: OpenInferenceSpanKind.LLM,\n model_step: OpenInferenceSpanKind.LLM,\n model_chunk: OpenInferenceSpanKind.LLM,\n // Tool spans -> TOOL\n tool_call: OpenInferenceSpanKind.TOOL,\n mcp_tool_call: OpenInferenceSpanKind.TOOL,\n // Agent spans -> AGENT\n agent_run: OpenInferenceSpanKind.AGENT,\n};\n\n/**\n * Converts GenAI usage metrics to OpenInference LLM token count attributes.\n * Maps from OTEL GenAI semantic conventions to OpenInference semantic conventions.\n *\n * @param attributes - The span attributes containing GenAI usage metrics\n * @returns OpenInference token count attributes\n */\nfunction convertUsageMetricsToOpenInference(attributes: Record<string, any>): Record<string, any> {\n const result: Record<string, any> = {};\n\n const inputTokens = attributes[ATTR_GEN_AI_USAGE_INPUT_TOKENS];\n const outputTokens = attributes[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS];\n\n // Core token counts\n if (inputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT] = inputTokens;\n }\n if (outputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION] = outputTokens;\n }\n\n // Total tokens (compute if we have both input and output)\n if (inputTokens !== undefined && outputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_TOTAL] = inputTokens + outputTokens;\n }\n\n // Cache tokens (prompt details)\n const cacheReadInputTokens = attributes[GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS];\n if (cacheReadInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ] = cacheReadInputTokens;\n }\n\n const cacheCreationInputTokens = attributes[GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS];\n if (cacheCreationInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE] = cacheCreationInputTokens;\n }\n\n // Reasoning tokens (completion details)\n const reasoningTokens = attributes[GEN_AI_USAGE_REASONING_TOKENS];\n if (reasoningTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING] = reasoningTokens;\n }\n\n // Audio tokens\n const audioInputTokens = attributes[GEN_AI_USAGE_AUDIO_INPUT_TOKENS];\n if (audioInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO] = audioInputTokens;\n }\n\n const audioOutputTokens = attributes[GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS];\n if (audioOutputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO] = audioOutputTokens;\n }\n\n return result;\n}\n\n/**\n * Splits Mastra span attributes into two groups:\n * - `metadata`: keys starting with \"mastra.metadata.\" (prefix removed)\n * - `other`: all remaining keys starting with \"mastra.\"\n *\n * Any attributes not starting with \"mastra.\" are ignored entirely.\n */\nfunction splitMastraAttributes(attributes: Record<string, any>): {\n mastraMetadata: Record<string, any>;\n mastraOther: Record<string, any>;\n} {\n return Object.entries(attributes).reduce(\n (acc, [key, value]) => {\n if (key.startsWith(MASTRA_GENERAL_PREFIX)) {\n if (key.startsWith(MASTRA_METADATA_PREFIX)) {\n const strippedKey = key.slice(MASTRA_METADATA_PREFIX.length);\n acc.mastraMetadata[strippedKey] = value;\n } else {\n acc.mastraOther[key] = value;\n }\n }\n return acc;\n },\n {\n mastraMetadata: {} as Record<string, any>,\n mastraOther: {} as Record<string, any>,\n },\n );\n}\n\nexport class OpenInferenceOTLPTraceExporter extends OTLPTraceExporter {\n export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void) {\n const processedSpans = spans.map(span => {\n const attributes = { ...(span.attributes ?? {}) };\n const mutableSpan = span as Mutable<ReadableSpan>;\n\n const { mastraMetadata, mastraOther } = splitMastraAttributes(attributes);\n const processedAttributes = convertGenAISpanAttributesToOpenInferenceSpanAttributes(attributes);\n\n // only add processed attributes if conversion was successful\n if (processedAttributes) {\n const threadId = mastraMetadata['threadId'];\n if (threadId) {\n delete mastraMetadata['threadId'];\n processedAttributes[SESSION_ID] = threadId;\n }\n\n // Map mastra.tags to OpenInference native tag.tags convention (tags are only on root spans)\n if (mastraOther['mastra.tags']) {\n processedAttributes[TAG_TAGS] = mastraOther['mastra.tags'];\n delete mastraOther['mastra.tags'];\n }\n\n const userId = mastraMetadata['userId'];\n if (userId) {\n delete mastraMetadata['userId'];\n processedAttributes[USER_ID] = userId;\n }\n\n // Gather custom metadata into OpenInference metadata (flat best-effort)\n if (Object.keys(mastraMetadata).length > 0) {\n try {\n processedAttributes[METADATA] = JSON.stringify(mastraMetadata);\n } catch {\n // best-effort only\n }\n }\n\n const inputMessages =\n attributes[ATTR_GEN_AI_INPUT_MESSAGES] ??\n attributes[ATTR_GEN_AI_TOOL_CALL_ARGUMENTS] ??\n mastraOther[MASTRA_MODEL_STEP_INPUT];\n if (inputMessages) {\n processedAttributes[INPUT_MIME_TYPE] = 'application/json';\n processedAttributes[INPUT_VALUE] = inputMessages;\n }\n const outputMessages =\n attributes[ATTR_GEN_AI_OUTPUT_MESSAGES] ??\n attributes[ATTR_GEN_AI_TOOL_CALL_RESULT] ??\n mastraOther[MASTRA_MODEL_STEP_OUTPUT] ??\n mastraOther[MASTRA_MODEL_CHUNK_OUTPUT];\n if (outputMessages) {\n processedAttributes[OUTPUT_MIME_TYPE] = 'application/json';\n processedAttributes[OUTPUT_VALUE] = outputMessages;\n }\n\n // Map generic Mastra span input/output to OpenInference input/output\n // These are set by Mastra's gen-ai-semantics.ts for non-LLM/tool spans\n // (e.g., mastra.processor_run.input, mastra.workflow_run.input, etc.)\n if (!processedAttributes[INPUT_VALUE]) {\n for (const key of Object.keys(mastraOther)) {\n if (key.endsWith('.input')) {\n processedAttributes[INPUT_MIME_TYPE] = 'application/json';\n processedAttributes[INPUT_VALUE] = mastraOther[key];\n break;\n }\n }\n }\n if (!processedAttributes[OUTPUT_VALUE]) {\n for (const key of Object.keys(mastraOther)) {\n if (key.endsWith('.output')) {\n processedAttributes[OUTPUT_MIME_TYPE] = 'application/json';\n processedAttributes[OUTPUT_VALUE] = mastraOther[key];\n break;\n }\n }\n }\n\n // Convert GenAI usage metrics to OpenInference token count attributes\n const usageMetrics = convertUsageMetricsToOpenInference(attributes);\n Object.assign(processedAttributes, usageMetrics);\n\n mutableSpan.attributes = { ...processedAttributes, ...mastraOther };\n\n // Set span kind based on mastra.span.type for proper trace categorization\n const spanType = mastraOther[MASTRA_SPAN_TYPE];\n if (typeof spanType === 'string') {\n mutableSpan.attributes[SemanticConventions.OPENINFERENCE_SPAN_KIND] =\n SPAN_TYPE_TO_KIND[spanType] ?? OpenInferenceSpanKind.CHAIN;\n }\n }\n\n return mutableSpan;\n });\n\n super.export(processedSpans, resultCallback);\n }\n}\n","import type { InitExporterOptions } from '@mastra/core/observability';\nimport { OtelExporter } from '@mastra/otel-exporter';\nimport type { OtelExporterConfig } from '@mastra/otel-exporter';\n\nimport { OpenInferenceOTLPTraceExporter } from './openInferenceOTLPExporter.js';\n\nconst LOG_PREFIX = '[ArthurExporter]';\n\nexport type ArthurExporterConfig = Omit<OtelExporterConfig, 'provider' | 'exporter'> & {\n /**\n * Arthur API key for authentication.\n * Falls back to ARTHUR_API_KEY environment variable.\n */\n apiKey?: string;\n /**\n * Arthur platform endpoint (e.g. https://app.arthur.ai).\n * Falls back to ARTHUR_BASE_URL environment variable.\n */\n endpoint?: string;\n /**\n * Arthur task ID to associate traces with.\n * Falls back to ARTHUR_TASK_ID environment variable.\n * At least one of taskId or serviceName (from the observability config) should be provided.\n */\n taskId?: string;\n /**\n * Optional headers to be added to each OTLP request.\n * Note: the Authorization header is managed internally when apiKey is provided.\n */\n headers?: Record<string, string>;\n};\n\n/**\n * Exports Mastra traces to Arthur AI using OpenInference semantic conventions.\n *\n * Supports zero-config setup via environment variables (ARTHUR_API_KEY, ARTHUR_BASE_URL,\n * ARTHUR_TASK_ID) or explicit configuration. Automatically disables itself with a warning\n * when required credentials are missing.\n *\n * @example\n * ```ts\n * const mastra = new Mastra({\n * observability: new Observability({\n * configs: {\n * arthur: {\n * serviceName: 'my-service',\n * exporters: [new ArthurExporter()],\n * },\n * },\n * }),\n * });\n * ```\n */\nexport class ArthurExporter extends OtelExporter {\n name = 'arthur';\n private taskId?: string;\n\n /**\n * @param config - Arthur exporter configuration. All fields are optional when\n * the corresponding environment variables are set.\n */\n constructor(config: ArthurExporterConfig = {}) {\n const apiKey = config.apiKey ?? process.env.ARTHUR_API_KEY;\n const endpoint = config.endpoint ?? process.env.ARTHUR_BASE_URL;\n const taskId = config.taskId ?? process.env.ARTHUR_TASK_ID;\n\n const headers: Record<string, string> = {\n ...config.headers,\n };\n\n let disabledReason: string | undefined;\n\n if (!apiKey) {\n disabledReason =\n `${LOG_PREFIX} API key is required. ` + `Set ARTHUR_API_KEY environment variable or pass apiKey in config.`;\n } else {\n headers['Authorization'] = `Bearer ${apiKey}`;\n }\n\n if (!disabledReason && !endpoint) {\n disabledReason =\n `${LOG_PREFIX} Endpoint is required. ` + `Set ARTHUR_BASE_URL environment variable or pass endpoint in config.`;\n }\n\n // Ensure the endpoint ends with /api/v1/traces\n const tracesEndpoint = endpoint ? `${stripTrailingSlashes(endpoint)}/api/v1/traces` : 'http://disabled';\n\n if (disabledReason) {\n super({\n ...config,\n provider: {\n custom: {\n endpoint: 'http://disabled',\n headers: {},\n protocol: 'http/protobuf',\n },\n },\n });\n this.setDisabled(disabledReason);\n return;\n }\n\n super({\n exporter: new OpenInferenceOTLPTraceExporter({\n url: tracesEndpoint,\n headers,\n }),\n ...config,\n resourceAttributes: {\n ...(taskId ? { 'arthur.task.id': taskId } : {}),\n ...config.resourceAttributes,\n },\n provider: {\n custom: {\n endpoint: tracesEndpoint,\n headers,\n protocol: 'http/protobuf',\n },\n } satisfies OtelExporterConfig['provider'],\n } satisfies OtelExporterConfig);\n\n this.taskId = taskId;\n }\n\n /**\n * Called after construction with the observability config.\n * Validates that traces can be routed to a task via either taskId or serviceName.\n */\n init(options: InitExporterOptions) {\n super.init(options);\n\n const serviceName = options.config?.serviceName;\n\n if (this.taskId && serviceName) {\n this.logger.warn(\n `${LOG_PREFIX} Both taskId and serviceName are set. Arthur Engine will use serviceName to route traces, ` +\n `ignoring the provided taskId.`,\n );\n } else if (!this.taskId && !serviceName) {\n this.logger.warn(\n `${LOG_PREFIX} Neither taskId nor serviceName is set. Set ARTHUR_TASK_ID environment variable, ` +\n `pass taskId in config, or set serviceName in the observability config so Arthur Engine can route traces to a task.`,\n );\n }\n }\n}\n\n/**\n * Remove trailing '/' characters procedurally. Avoids the polynomial\n * backtracking that a greedy regex like `/\\/+$/` can exhibit when the\n * input is attacker-controlled.\n */\nfunction stripTrailingSlashes(s: string): string {\n let end = s.length;\n while (end > 0 && s.charCodeAt(end - 1) === 47 /* '/' */) {\n end--;\n }\n return end === s.length ? s : s.slice(0, end);\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,242 +1,234 @@
1
- import { OtelExporter } from '@mastra/otel-exporter';
2
- import { convertGenAISpanAttributesToOpenInferenceSpanAttributes } from '@arizeai/openinference-genai';
3
- import { SESSION_ID, TAG_TAGS, USER_ID, METADATA, INPUT_MIME_TYPE, INPUT_VALUE, OUTPUT_MIME_TYPE, OUTPUT_VALUE, OpenInferenceSpanKind, SemanticConventions, LLM_TOKEN_COUNT_PROMPT, LLM_TOKEN_COUNT_COMPLETION, LLM_TOKEN_COUNT_TOTAL, LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ, LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE, LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING, LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO, LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO } from '@arizeai/openinference-semantic-conventions';
4
- import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
5
- import { ATTR_GEN_AI_INPUT_MESSAGES, ATTR_GEN_AI_TOOL_CALL_ARGUMENTS, ATTR_GEN_AI_OUTPUT_MESSAGES, ATTR_GEN_AI_TOOL_CALL_RESULT, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS } from '@opentelemetry/semantic-conventions/incubating';
6
-
7
- // src/tracing.ts
8
- var GEN_AI_USAGE_REASONING_TOKENS = "gen_ai.usage.reasoning_tokens";
9
- var GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens";
10
- var GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS = "gen_ai.usage.cache_creation.input_tokens";
11
- var GEN_AI_USAGE_AUDIO_INPUT_TOKENS = "gen_ai.usage.audio_input_tokens";
12
- var GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS = "gen_ai.usage.audio_output_tokens";
13
- var MASTRA_GENERAL_PREFIX = "mastra.";
14
- var MASTRA_METADATA_PREFIX = "mastra.metadata.";
15
- var MASTRA_MODEL_STEP_INPUT = "mastra.model_step.input";
16
- var MASTRA_MODEL_STEP_OUTPUT = "mastra.model_step.output";
17
- var MASTRA_MODEL_CHUNK_OUTPUT = "mastra.model_chunk.output";
18
- var MASTRA_SPAN_TYPE = "mastra.span.type";
19
- var SPAN_TYPE_TO_KIND = {
20
- // Model spans -> LLM
21
- model_generation: OpenInferenceSpanKind.LLM,
22
- model_step: OpenInferenceSpanKind.LLM,
23
- model_chunk: OpenInferenceSpanKind.LLM,
24
- // Tool spans -> TOOL
25
- tool_call: OpenInferenceSpanKind.TOOL,
26
- mcp_tool_call: OpenInferenceSpanKind.TOOL,
27
- // Agent spans -> AGENT
28
- agent_run: OpenInferenceSpanKind.AGENT
1
+ import { OtelExporter } from "@mastra/otel-exporter";
2
+ import { convertGenAISpanAttributesToOpenInferenceSpanAttributes } from "@arizeai/openinference-genai";
3
+ import { INPUT_MIME_TYPE, INPUT_VALUE, LLM_TOKEN_COUNT_COMPLETION, LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO, LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING, LLM_TOKEN_COUNT_PROMPT, LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO, LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ, LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE, LLM_TOKEN_COUNT_TOTAL, METADATA, OUTPUT_MIME_TYPE, OUTPUT_VALUE, OpenInferenceSpanKind, SESSION_ID, SemanticConventions, TAG_TAGS, USER_ID } from "@arizeai/openinference-semantic-conventions";
4
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
5
+ import { ATTR_GEN_AI_INPUT_MESSAGES, ATTR_GEN_AI_OUTPUT_MESSAGES, ATTR_GEN_AI_TOOL_CALL_ARGUMENTS, ATTR_GEN_AI_TOOL_CALL_RESULT, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS } from "@opentelemetry/semantic-conventions/incubating";
6
+ //#region src/openInferenceOTLPExporter.ts
7
+ const GEN_AI_USAGE_REASONING_TOKENS = "gen_ai.usage.reasoning_tokens";
8
+ const GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read.input_tokens";
9
+ const GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS = "gen_ai.usage.cache_creation.input_tokens";
10
+ const GEN_AI_USAGE_AUDIO_INPUT_TOKENS = "gen_ai.usage.audio_input_tokens";
11
+ const GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS = "gen_ai.usage.audio_output_tokens";
12
+ const MASTRA_GENERAL_PREFIX = "mastra.";
13
+ const MASTRA_METADATA_PREFIX = "mastra.metadata.";
14
+ const MASTRA_MODEL_STEP_INPUT = "mastra.model_step.input";
15
+ const MASTRA_MODEL_STEP_OUTPUT = "mastra.model_step.output";
16
+ const MASTRA_MODEL_CHUNK_OUTPUT = "mastra.model_chunk.output";
17
+ const MASTRA_SPAN_TYPE = "mastra.span.type";
18
+ /**
19
+ * Maps Mastra span types to OpenInference span kinds for proper trace categorization.
20
+ *
21
+ * Only non-CHAIN types are mapped here - all other span types default to CHAIN.
22
+ */
23
+ const SPAN_TYPE_TO_KIND = {
24
+ model_generation: OpenInferenceSpanKind.LLM,
25
+ model_step: OpenInferenceSpanKind.LLM,
26
+ model_chunk: OpenInferenceSpanKind.LLM,
27
+ tool_call: OpenInferenceSpanKind.TOOL,
28
+ mcp_tool_call: OpenInferenceSpanKind.TOOL,
29
+ agent_run: OpenInferenceSpanKind.AGENT
29
30
  };
31
+ /**
32
+ * Converts GenAI usage metrics to OpenInference LLM token count attributes.
33
+ * Maps from OTEL GenAI semantic conventions to OpenInference semantic conventions.
34
+ *
35
+ * @param attributes - The span attributes containing GenAI usage metrics
36
+ * @returns OpenInference token count attributes
37
+ */
30
38
  function convertUsageMetricsToOpenInference(attributes) {
31
- const result = {};
32
- const inputTokens = attributes[ATTR_GEN_AI_USAGE_INPUT_TOKENS];
33
- const outputTokens = attributes[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS];
34
- if (inputTokens !== void 0) {
35
- result[LLM_TOKEN_COUNT_PROMPT] = inputTokens;
36
- }
37
- if (outputTokens !== void 0) {
38
- result[LLM_TOKEN_COUNT_COMPLETION] = outputTokens;
39
- }
40
- if (inputTokens !== void 0 && outputTokens !== void 0) {
41
- result[LLM_TOKEN_COUNT_TOTAL] = inputTokens + outputTokens;
42
- }
43
- const cacheReadInputTokens = attributes[GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS];
44
- if (cacheReadInputTokens !== void 0) {
45
- result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ] = cacheReadInputTokens;
46
- }
47
- const cacheCreationInputTokens = attributes[GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS];
48
- if (cacheCreationInputTokens !== void 0) {
49
- result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE] = cacheCreationInputTokens;
50
- }
51
- const reasoningTokens = attributes[GEN_AI_USAGE_REASONING_TOKENS];
52
- if (reasoningTokens !== void 0) {
53
- result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING] = reasoningTokens;
54
- }
55
- const audioInputTokens = attributes[GEN_AI_USAGE_AUDIO_INPUT_TOKENS];
56
- if (audioInputTokens !== void 0) {
57
- result[LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO] = audioInputTokens;
58
- }
59
- const audioOutputTokens = attributes[GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS];
60
- if (audioOutputTokens !== void 0) {
61
- result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO] = audioOutputTokens;
62
- }
63
- return result;
39
+ const result = {};
40
+ const inputTokens = attributes[ATTR_GEN_AI_USAGE_INPUT_TOKENS];
41
+ const outputTokens = attributes[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS];
42
+ if (inputTokens !== void 0) result[LLM_TOKEN_COUNT_PROMPT] = inputTokens;
43
+ if (outputTokens !== void 0) result[LLM_TOKEN_COUNT_COMPLETION] = outputTokens;
44
+ if (inputTokens !== void 0 && outputTokens !== void 0) result[LLM_TOKEN_COUNT_TOTAL] = inputTokens + outputTokens;
45
+ const cacheReadInputTokens = attributes[GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS];
46
+ if (cacheReadInputTokens !== void 0) result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ] = cacheReadInputTokens;
47
+ const cacheCreationInputTokens = attributes[GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS];
48
+ if (cacheCreationInputTokens !== void 0) result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE] = cacheCreationInputTokens;
49
+ const reasoningTokens = attributes[GEN_AI_USAGE_REASONING_TOKENS];
50
+ if (reasoningTokens !== void 0) result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING] = reasoningTokens;
51
+ const audioInputTokens = attributes[GEN_AI_USAGE_AUDIO_INPUT_TOKENS];
52
+ if (audioInputTokens !== void 0) result[LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO] = audioInputTokens;
53
+ const audioOutputTokens = attributes[GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS];
54
+ if (audioOutputTokens !== void 0) result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO] = audioOutputTokens;
55
+ return result;
64
56
  }
57
+ /**
58
+ * Splits Mastra span attributes into two groups:
59
+ * - `metadata`: keys starting with "mastra.metadata." (prefix removed)
60
+ * - `other`: all remaining keys starting with "mastra."
61
+ *
62
+ * Any attributes not starting with "mastra." are ignored entirely.
63
+ */
65
64
  function splitMastraAttributes(attributes) {
66
- return Object.entries(attributes).reduce(
67
- (acc, [key, value]) => {
68
- if (key.startsWith(MASTRA_GENERAL_PREFIX)) {
69
- if (key.startsWith(MASTRA_METADATA_PREFIX)) {
70
- const strippedKey = key.slice(MASTRA_METADATA_PREFIX.length);
71
- acc.mastraMetadata[strippedKey] = value;
72
- } else {
73
- acc.mastraOther[key] = value;
74
- }
75
- }
76
- return acc;
77
- },
78
- {
79
- mastraMetadata: {},
80
- mastraOther: {}
81
- }
82
- );
65
+ return Object.entries(attributes).reduce((acc, [key, value]) => {
66
+ if (key.startsWith(MASTRA_GENERAL_PREFIX)) if (key.startsWith(MASTRA_METADATA_PREFIX)) {
67
+ const strippedKey = key.slice(16);
68
+ acc.mastraMetadata[strippedKey] = value;
69
+ } else acc.mastraOther[key] = value;
70
+ return acc;
71
+ }, {
72
+ mastraMetadata: {},
73
+ mastraOther: {}
74
+ });
83
75
  }
84
76
  var OpenInferenceOTLPTraceExporter = class extends OTLPTraceExporter {
85
- export(spans, resultCallback) {
86
- const processedSpans = spans.map((span) => {
87
- const attributes = { ...span.attributes ?? {} };
88
- const mutableSpan = span;
89
- const { mastraMetadata, mastraOther } = splitMastraAttributes(attributes);
90
- const processedAttributes = convertGenAISpanAttributesToOpenInferenceSpanAttributes(attributes);
91
- if (processedAttributes) {
92
- const threadId = mastraMetadata["threadId"];
93
- if (threadId) {
94
- delete mastraMetadata["threadId"];
95
- processedAttributes[SESSION_ID] = threadId;
96
- }
97
- if (mastraOther["mastra.tags"]) {
98
- processedAttributes[TAG_TAGS] = mastraOther["mastra.tags"];
99
- delete mastraOther["mastra.tags"];
100
- }
101
- const userId = mastraMetadata["userId"];
102
- if (userId) {
103
- delete mastraMetadata["userId"];
104
- processedAttributes[USER_ID] = userId;
105
- }
106
- if (Object.keys(mastraMetadata).length > 0) {
107
- try {
108
- processedAttributes[METADATA] = JSON.stringify(mastraMetadata);
109
- } catch {
110
- }
111
- }
112
- const inputMessages = attributes[ATTR_GEN_AI_INPUT_MESSAGES] ?? attributes[ATTR_GEN_AI_TOOL_CALL_ARGUMENTS] ?? mastraOther[MASTRA_MODEL_STEP_INPUT];
113
- if (inputMessages) {
114
- processedAttributes[INPUT_MIME_TYPE] = "application/json";
115
- processedAttributes[INPUT_VALUE] = inputMessages;
116
- }
117
- const outputMessages = attributes[ATTR_GEN_AI_OUTPUT_MESSAGES] ?? attributes[ATTR_GEN_AI_TOOL_CALL_RESULT] ?? mastraOther[MASTRA_MODEL_STEP_OUTPUT] ?? mastraOther[MASTRA_MODEL_CHUNK_OUTPUT];
118
- if (outputMessages) {
119
- processedAttributes[OUTPUT_MIME_TYPE] = "application/json";
120
- processedAttributes[OUTPUT_VALUE] = outputMessages;
121
- }
122
- if (!processedAttributes[INPUT_VALUE]) {
123
- for (const key of Object.keys(mastraOther)) {
124
- if (key.endsWith(".input")) {
125
- processedAttributes[INPUT_MIME_TYPE] = "application/json";
126
- processedAttributes[INPUT_VALUE] = mastraOther[key];
127
- break;
128
- }
129
- }
130
- }
131
- if (!processedAttributes[OUTPUT_VALUE]) {
132
- for (const key of Object.keys(mastraOther)) {
133
- if (key.endsWith(".output")) {
134
- processedAttributes[OUTPUT_MIME_TYPE] = "application/json";
135
- processedAttributes[OUTPUT_VALUE] = mastraOther[key];
136
- break;
137
- }
138
- }
139
- }
140
- const usageMetrics = convertUsageMetricsToOpenInference(attributes);
141
- Object.assign(processedAttributes, usageMetrics);
142
- mutableSpan.attributes = { ...processedAttributes, ...mastraOther };
143
- const spanType = mastraOther[MASTRA_SPAN_TYPE];
144
- if (typeof spanType === "string") {
145
- mutableSpan.attributes[SemanticConventions.OPENINFERENCE_SPAN_KIND] = SPAN_TYPE_TO_KIND[spanType] ?? OpenInferenceSpanKind.CHAIN;
146
- }
147
- }
148
- return mutableSpan;
149
- });
150
- super.export(processedSpans, resultCallback);
151
- }
77
+ export(spans, resultCallback) {
78
+ const processedSpans = spans.map((span) => {
79
+ const attributes = { ...span.attributes ?? {} };
80
+ const mutableSpan = span;
81
+ const { mastraMetadata, mastraOther } = splitMastraAttributes(attributes);
82
+ const processedAttributes = convertGenAISpanAttributesToOpenInferenceSpanAttributes(attributes);
83
+ if (processedAttributes) {
84
+ const threadId = mastraMetadata["threadId"];
85
+ if (threadId) {
86
+ delete mastraMetadata["threadId"];
87
+ processedAttributes[SESSION_ID] = threadId;
88
+ }
89
+ if (mastraOther["mastra.tags"]) {
90
+ processedAttributes[TAG_TAGS] = mastraOther["mastra.tags"];
91
+ delete mastraOther["mastra.tags"];
92
+ }
93
+ const userId = mastraMetadata["userId"];
94
+ if (userId) {
95
+ delete mastraMetadata["userId"];
96
+ processedAttributes[USER_ID] = userId;
97
+ }
98
+ if (Object.keys(mastraMetadata).length > 0) try {
99
+ processedAttributes[METADATA] = JSON.stringify(mastraMetadata);
100
+ } catch {}
101
+ const inputMessages = attributes[ATTR_GEN_AI_INPUT_MESSAGES] ?? attributes[ATTR_GEN_AI_TOOL_CALL_ARGUMENTS] ?? mastraOther[MASTRA_MODEL_STEP_INPUT];
102
+ if (inputMessages) {
103
+ processedAttributes[INPUT_MIME_TYPE] = "application/json";
104
+ processedAttributes[INPUT_VALUE] = inputMessages;
105
+ }
106
+ const outputMessages = attributes[ATTR_GEN_AI_OUTPUT_MESSAGES] ?? attributes[ATTR_GEN_AI_TOOL_CALL_RESULT] ?? mastraOther[MASTRA_MODEL_STEP_OUTPUT] ?? mastraOther[MASTRA_MODEL_CHUNK_OUTPUT];
107
+ if (outputMessages) {
108
+ processedAttributes[OUTPUT_MIME_TYPE] = "application/json";
109
+ processedAttributes[OUTPUT_VALUE] = outputMessages;
110
+ }
111
+ if (!processedAttributes[INPUT_VALUE]) {
112
+ for (const key of Object.keys(mastraOther)) if (key.endsWith(".input")) {
113
+ processedAttributes[INPUT_MIME_TYPE] = "application/json";
114
+ processedAttributes[INPUT_VALUE] = mastraOther[key];
115
+ break;
116
+ }
117
+ }
118
+ if (!processedAttributes[OUTPUT_VALUE]) {
119
+ for (const key of Object.keys(mastraOther)) if (key.endsWith(".output")) {
120
+ processedAttributes[OUTPUT_MIME_TYPE] = "application/json";
121
+ processedAttributes[OUTPUT_VALUE] = mastraOther[key];
122
+ break;
123
+ }
124
+ }
125
+ const usageMetrics = convertUsageMetricsToOpenInference(attributes);
126
+ Object.assign(processedAttributes, usageMetrics);
127
+ mutableSpan.attributes = {
128
+ ...processedAttributes,
129
+ ...mastraOther
130
+ };
131
+ const spanType = mastraOther[MASTRA_SPAN_TYPE];
132
+ if (typeof spanType === "string") mutableSpan.attributes[SemanticConventions.OPENINFERENCE_SPAN_KIND] = SPAN_TYPE_TO_KIND[spanType] ?? OpenInferenceSpanKind.CHAIN;
133
+ }
134
+ return mutableSpan;
135
+ });
136
+ super.export(processedSpans, resultCallback);
137
+ }
152
138
  };
153
-
154
- // src/tracing.ts
155
- var LOG_PREFIX = "[ArthurExporter]";
139
+ //#endregion
140
+ //#region src/tracing.ts
141
+ const LOG_PREFIX = "[ArthurExporter]";
142
+ /**
143
+ * Exports Mastra traces to Arthur AI using OpenInference semantic conventions.
144
+ *
145
+ * Supports zero-config setup via environment variables (ARTHUR_API_KEY, ARTHUR_BASE_URL,
146
+ * ARTHUR_TASK_ID) or explicit configuration. Automatically disables itself with a warning
147
+ * when required credentials are missing.
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * const mastra = new Mastra({
152
+ * observability: new Observability({
153
+ * configs: {
154
+ * arthur: {
155
+ * serviceName: 'my-service',
156
+ * exporters: [new ArthurExporter()],
157
+ * },
158
+ * },
159
+ * }),
160
+ * });
161
+ * ```
162
+ */
156
163
  var ArthurExporter = class extends OtelExporter {
157
- name = "arthur";
158
- taskId;
159
- /**
160
- * @param config - Arthur exporter configuration. All fields are optional when
161
- * the corresponding environment variables are set.
162
- */
163
- constructor(config = {}) {
164
- const apiKey = config.apiKey ?? process.env.ARTHUR_API_KEY;
165
- const endpoint = config.endpoint ?? process.env.ARTHUR_BASE_URL;
166
- const taskId = config.taskId ?? process.env.ARTHUR_TASK_ID;
167
- const headers = {
168
- ...config.headers
169
- };
170
- let disabledReason;
171
- if (!apiKey) {
172
- disabledReason = `${LOG_PREFIX} API key is required. Set ARTHUR_API_KEY environment variable or pass apiKey in config.`;
173
- } else {
174
- headers["Authorization"] = `Bearer ${apiKey}`;
175
- }
176
- if (!disabledReason && !endpoint) {
177
- disabledReason = `${LOG_PREFIX} Endpoint is required. Set ARTHUR_BASE_URL environment variable or pass endpoint in config.`;
178
- }
179
- const tracesEndpoint = endpoint ? `${stripTrailingSlashes(endpoint)}/api/v1/traces` : "http://disabled";
180
- if (disabledReason) {
181
- super({
182
- ...config,
183
- provider: {
184
- custom: {
185
- endpoint: "http://disabled",
186
- headers: {},
187
- protocol: "http/protobuf"
188
- }
189
- }
190
- });
191
- this.setDisabled(disabledReason);
192
- return;
193
- }
194
- super({
195
- exporter: new OpenInferenceOTLPTraceExporter({
196
- url: tracesEndpoint,
197
- headers
198
- }),
199
- ...config,
200
- resourceAttributes: {
201
- ...taskId ? { "arthur.task.id": taskId } : {},
202
- ...config.resourceAttributes
203
- },
204
- provider: {
205
- custom: {
206
- endpoint: tracesEndpoint,
207
- headers,
208
- protocol: "http/protobuf"
209
- }
210
- }
211
- });
212
- this.taskId = taskId;
213
- }
214
- /**
215
- * Called after construction with the observability config.
216
- * Validates that traces can be routed to a task via either taskId or serviceName.
217
- */
218
- init(options) {
219
- super.init(options);
220
- const serviceName = options.config?.serviceName;
221
- if (this.taskId && serviceName) {
222
- this.logger.warn(
223
- `${LOG_PREFIX} Both taskId and serviceName are set. Arthur Engine will use serviceName to route traces, ignoring the provided taskId.`
224
- );
225
- } else if (!this.taskId && !serviceName) {
226
- this.logger.warn(
227
- `${LOG_PREFIX} Neither taskId nor serviceName is set. Set ARTHUR_TASK_ID environment variable, pass taskId in config, or set serviceName in the observability config so Arthur Engine can route traces to a task.`
228
- );
229
- }
230
- }
164
+ name = "arthur";
165
+ taskId;
166
+ /**
167
+ * @param config - Arthur exporter configuration. All fields are optional when
168
+ * the corresponding environment variables are set.
169
+ */
170
+ constructor(config = {}) {
171
+ const apiKey = config.apiKey ?? process.env.ARTHUR_API_KEY;
172
+ const endpoint = config.endpoint ?? process.env.ARTHUR_BASE_URL;
173
+ const taskId = config.taskId ?? process.env.ARTHUR_TASK_ID;
174
+ const headers = { ...config.headers };
175
+ let disabledReason;
176
+ if (!apiKey) disabledReason = `${LOG_PREFIX} API key is required. Set ARTHUR_API_KEY environment variable or pass apiKey in config.`;
177
+ else headers["Authorization"] = `Bearer ${apiKey}`;
178
+ if (!disabledReason && !endpoint) disabledReason = `${LOG_PREFIX} Endpoint is required. Set ARTHUR_BASE_URL environment variable or pass endpoint in config.`;
179
+ const tracesEndpoint = endpoint ? `${stripTrailingSlashes(endpoint)}/api/v1/traces` : "http://disabled";
180
+ if (disabledReason) {
181
+ super({
182
+ ...config,
183
+ provider: { custom: {
184
+ endpoint: "http://disabled",
185
+ headers: {},
186
+ protocol: "http/protobuf"
187
+ } }
188
+ });
189
+ this.setDisabled(disabledReason);
190
+ return;
191
+ }
192
+ super({
193
+ exporter: new OpenInferenceOTLPTraceExporter({
194
+ url: tracesEndpoint,
195
+ headers
196
+ }),
197
+ ...config,
198
+ resourceAttributes: {
199
+ ...taskId ? { "arthur.task.id": taskId } : {},
200
+ ...config.resourceAttributes
201
+ },
202
+ provider: { custom: {
203
+ endpoint: tracesEndpoint,
204
+ headers,
205
+ protocol: "http/protobuf"
206
+ } }
207
+ });
208
+ this.taskId = taskId;
209
+ }
210
+ /**
211
+ * Called after construction with the observability config.
212
+ * Validates that traces can be routed to a task via either taskId or serviceName.
213
+ */
214
+ init(options) {
215
+ super.init(options);
216
+ const serviceName = options.config?.serviceName;
217
+ if (this.taskId && serviceName) this.logger.warn(`${LOG_PREFIX} Both taskId and serviceName are set. Arthur Engine will use serviceName to route traces, ignoring the provided taskId.`);
218
+ else if (!this.taskId && !serviceName) this.logger.warn(`${LOG_PREFIX} Neither taskId nor serviceName is set. Set ARTHUR_TASK_ID environment variable, pass taskId in config, or set serviceName in the observability config so Arthur Engine can route traces to a task.`);
219
+ }
231
220
  };
221
+ /**
222
+ * Remove trailing '/' characters procedurally. Avoids the polynomial
223
+ * backtracking that a greedy regex like `/\/+$/` can exhibit when the
224
+ * input is attacker-controlled.
225
+ */
232
226
  function stripTrailingSlashes(s) {
233
- let end = s.length;
234
- while (end > 0 && s.charCodeAt(end - 1) === 47) {
235
- end--;
236
- }
237
- return end === s.length ? s : s.slice(0, end);
227
+ let end = s.length;
228
+ while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
229
+ return end === s.length ? s : s.slice(0, end);
238
230
  }
239
-
231
+ //#endregion
240
232
  export { ArthurExporter };
241
- //# sourceMappingURL=index.js.map
233
+
242
234
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/openInferenceOTLPExporter.ts","../src/tracing.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { convertGenAISpanAttributesToOpenInferenceSpanAttributes } from '@arizeai/openinference-genai';\nimport type { Mutable } from '@arizeai/openinference-genai/types';\nimport {\n INPUT_MIME_TYPE,\n INPUT_VALUE,\n LLM_TOKEN_COUNT_COMPLETION,\n LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO,\n LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING,\n LLM_TOKEN_COUNT_PROMPT,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE,\n LLM_TOKEN_COUNT_TOTAL,\n METADATA,\n OpenInferenceSpanKind,\n OUTPUT_MIME_TYPE,\n OUTPUT_VALUE,\n SemanticConventions,\n SESSION_ID,\n TAG_TAGS,\n USER_ID,\n} from '@arizeai/openinference-semantic-conventions';\nimport type { ExportResult } from '@opentelemetry/core';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport {\n ATTR_GEN_AI_INPUT_MESSAGES,\n ATTR_GEN_AI_OUTPUT_MESSAGES,\n ATTR_GEN_AI_USAGE_INPUT_TOKENS,\n ATTR_GEN_AI_USAGE_OUTPUT_TOKENS,\n ATTR_GEN_AI_TOOL_CALL_ARGUMENTS,\n ATTR_GEN_AI_TOOL_CALL_RESULT,\n} from '@opentelemetry/semantic-conventions/incubating';\n\n// GenAI usage attribute keys (not all are in @opentelemetry/semantic-conventions yet)\nconst GEN_AI_USAGE_REASONING_TOKENS = 'gen_ai.usage.reasoning_tokens';\nconst GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = 'gen_ai.usage.cache_read.input_tokens';\nconst GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS = 'gen_ai.usage.cache_creation.input_tokens';\nconst GEN_AI_USAGE_AUDIO_INPUT_TOKENS = 'gen_ai.usage.audio_input_tokens';\nconst GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS = 'gen_ai.usage.audio_output_tokens';\n\nconst MASTRA_GENERAL_PREFIX = 'mastra.';\nconst MASTRA_METADATA_PREFIX = 'mastra.metadata.';\nconst MASTRA_MODEL_STEP_INPUT = 'mastra.model_step.input';\nconst MASTRA_MODEL_STEP_OUTPUT = 'mastra.model_step.output';\nconst MASTRA_MODEL_CHUNK_OUTPUT = 'mastra.model_chunk.output';\nconst MASTRA_SPAN_TYPE = 'mastra.span.type';\n\n/**\n * Maps Mastra span types to OpenInference span kinds for proper trace categorization.\n *\n * Only non-CHAIN types are mapped here - all other span types default to CHAIN.\n */\nconst SPAN_TYPE_TO_KIND: Record<string, OpenInferenceSpanKind> = {\n // Model spans -> LLM\n model_generation: OpenInferenceSpanKind.LLM,\n model_step: OpenInferenceSpanKind.LLM,\n model_chunk: OpenInferenceSpanKind.LLM,\n // Tool spans -> TOOL\n tool_call: OpenInferenceSpanKind.TOOL,\n mcp_tool_call: OpenInferenceSpanKind.TOOL,\n // Agent spans -> AGENT\n agent_run: OpenInferenceSpanKind.AGENT,\n};\n\n/**\n * Converts GenAI usage metrics to OpenInference LLM token count attributes.\n * Maps from OTEL GenAI semantic conventions to OpenInference semantic conventions.\n *\n * @param attributes - The span attributes containing GenAI usage metrics\n * @returns OpenInference token count attributes\n */\nfunction convertUsageMetricsToOpenInference(attributes: Record<string, any>): Record<string, any> {\n const result: Record<string, any> = {};\n\n const inputTokens = attributes[ATTR_GEN_AI_USAGE_INPUT_TOKENS];\n const outputTokens = attributes[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS];\n\n // Core token counts\n if (inputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT] = inputTokens;\n }\n if (outputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION] = outputTokens;\n }\n\n // Total tokens (compute if we have both input and output)\n if (inputTokens !== undefined && outputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_TOTAL] = inputTokens + outputTokens;\n }\n\n // Cache tokens (prompt details)\n const cacheReadInputTokens = attributes[GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS];\n if (cacheReadInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ] = cacheReadInputTokens;\n }\n\n const cacheCreationInputTokens = attributes[GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS];\n if (cacheCreationInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE] = cacheCreationInputTokens;\n }\n\n // Reasoning tokens (completion details)\n const reasoningTokens = attributes[GEN_AI_USAGE_REASONING_TOKENS];\n if (reasoningTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING] = reasoningTokens;\n }\n\n // Audio tokens\n const audioInputTokens = attributes[GEN_AI_USAGE_AUDIO_INPUT_TOKENS];\n if (audioInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO] = audioInputTokens;\n }\n\n const audioOutputTokens = attributes[GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS];\n if (audioOutputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO] = audioOutputTokens;\n }\n\n return result;\n}\n\n/**\n * Splits Mastra span attributes into two groups:\n * - `metadata`: keys starting with \"mastra.metadata.\" (prefix removed)\n * - `other`: all remaining keys starting with \"mastra.\"\n *\n * Any attributes not starting with \"mastra.\" are ignored entirely.\n */\nfunction splitMastraAttributes(attributes: Record<string, any>): {\n mastraMetadata: Record<string, any>;\n mastraOther: Record<string, any>;\n} {\n return Object.entries(attributes).reduce(\n (acc, [key, value]) => {\n if (key.startsWith(MASTRA_GENERAL_PREFIX)) {\n if (key.startsWith(MASTRA_METADATA_PREFIX)) {\n const strippedKey = key.slice(MASTRA_METADATA_PREFIX.length);\n acc.mastraMetadata[strippedKey] = value;\n } else {\n acc.mastraOther[key] = value;\n }\n }\n return acc;\n },\n {\n mastraMetadata: {} as Record<string, any>,\n mastraOther: {} as Record<string, any>,\n },\n );\n}\n\nexport class OpenInferenceOTLPTraceExporter extends OTLPTraceExporter {\n export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void) {\n const processedSpans = spans.map(span => {\n const attributes = { ...(span.attributes ?? {}) };\n const mutableSpan = span as Mutable<ReadableSpan>;\n\n const { mastraMetadata, mastraOther } = splitMastraAttributes(attributes);\n const processedAttributes = convertGenAISpanAttributesToOpenInferenceSpanAttributes(attributes);\n\n // only add processed attributes if conversion was successful\n if (processedAttributes) {\n const threadId = mastraMetadata['threadId'];\n if (threadId) {\n delete mastraMetadata['threadId'];\n processedAttributes[SESSION_ID] = threadId;\n }\n\n // Map mastra.tags to OpenInference native tag.tags convention (tags are only on root spans)\n if (mastraOther['mastra.tags']) {\n processedAttributes[TAG_TAGS] = mastraOther['mastra.tags'];\n delete mastraOther['mastra.tags'];\n }\n\n const userId = mastraMetadata['userId'];\n if (userId) {\n delete mastraMetadata['userId'];\n processedAttributes[USER_ID] = userId;\n }\n\n // Gather custom metadata into OpenInference metadata (flat best-effort)\n if (Object.keys(mastraMetadata).length > 0) {\n try {\n processedAttributes[METADATA] = JSON.stringify(mastraMetadata);\n } catch {\n // best-effort only\n }\n }\n\n const inputMessages =\n attributes[ATTR_GEN_AI_INPUT_MESSAGES] ??\n attributes[ATTR_GEN_AI_TOOL_CALL_ARGUMENTS] ??\n mastraOther[MASTRA_MODEL_STEP_INPUT];\n if (inputMessages) {\n processedAttributes[INPUT_MIME_TYPE] = 'application/json';\n processedAttributes[INPUT_VALUE] = inputMessages;\n }\n const outputMessages =\n attributes[ATTR_GEN_AI_OUTPUT_MESSAGES] ??\n attributes[ATTR_GEN_AI_TOOL_CALL_RESULT] ??\n mastraOther[MASTRA_MODEL_STEP_OUTPUT] ??\n mastraOther[MASTRA_MODEL_CHUNK_OUTPUT];\n if (outputMessages) {\n processedAttributes[OUTPUT_MIME_TYPE] = 'application/json';\n processedAttributes[OUTPUT_VALUE] = outputMessages;\n }\n\n // Map generic Mastra span input/output to OpenInference input/output\n // These are set by Mastra's gen-ai-semantics.ts for non-LLM/tool spans\n // (e.g., mastra.processor_run.input, mastra.workflow_run.input, etc.)\n if (!processedAttributes[INPUT_VALUE]) {\n for (const key of Object.keys(mastraOther)) {\n if (key.endsWith('.input')) {\n processedAttributes[INPUT_MIME_TYPE] = 'application/json';\n processedAttributes[INPUT_VALUE] = mastraOther[key];\n break;\n }\n }\n }\n if (!processedAttributes[OUTPUT_VALUE]) {\n for (const key of Object.keys(mastraOther)) {\n if (key.endsWith('.output')) {\n processedAttributes[OUTPUT_MIME_TYPE] = 'application/json';\n processedAttributes[OUTPUT_VALUE] = mastraOther[key];\n break;\n }\n }\n }\n\n // Convert GenAI usage metrics to OpenInference token count attributes\n const usageMetrics = convertUsageMetricsToOpenInference(attributes);\n Object.assign(processedAttributes, usageMetrics);\n\n mutableSpan.attributes = { ...processedAttributes, ...mastraOther };\n\n // Set span kind based on mastra.span.type for proper trace categorization\n const spanType = mastraOther[MASTRA_SPAN_TYPE];\n if (typeof spanType === 'string') {\n mutableSpan.attributes[SemanticConventions.OPENINFERENCE_SPAN_KIND] =\n SPAN_TYPE_TO_KIND[spanType] ?? OpenInferenceSpanKind.CHAIN;\n }\n }\n\n return mutableSpan;\n });\n\n super.export(processedSpans, resultCallback);\n }\n}\n","import type { InitExporterOptions } from '@mastra/core/observability';\nimport { OtelExporter } from '@mastra/otel-exporter';\nimport type { OtelExporterConfig } from '@mastra/otel-exporter';\n\nimport { OpenInferenceOTLPTraceExporter } from './openInferenceOTLPExporter.js';\n\nconst LOG_PREFIX = '[ArthurExporter]';\n\nexport type ArthurExporterConfig = Omit<OtelExporterConfig, 'provider' | 'exporter'> & {\n /**\n * Arthur API key for authentication.\n * Falls back to ARTHUR_API_KEY environment variable.\n */\n apiKey?: string;\n /**\n * Arthur platform endpoint (e.g. https://app.arthur.ai).\n * Falls back to ARTHUR_BASE_URL environment variable.\n */\n endpoint?: string;\n /**\n * Arthur task ID to associate traces with.\n * Falls back to ARTHUR_TASK_ID environment variable.\n * At least one of taskId or serviceName (from the observability config) should be provided.\n */\n taskId?: string;\n /**\n * Optional headers to be added to each OTLP request.\n * Note: the Authorization header is managed internally when apiKey is provided.\n */\n headers?: Record<string, string>;\n};\n\n/**\n * Exports Mastra traces to Arthur AI using OpenInference semantic conventions.\n *\n * Supports zero-config setup via environment variables (ARTHUR_API_KEY, ARTHUR_BASE_URL,\n * ARTHUR_TASK_ID) or explicit configuration. Automatically disables itself with a warning\n * when required credentials are missing.\n *\n * @example\n * ```ts\n * const mastra = new Mastra({\n * observability: new Observability({\n * configs: {\n * arthur: {\n * serviceName: 'my-service',\n * exporters: [new ArthurExporter()],\n * },\n * },\n * }),\n * });\n * ```\n */\nexport class ArthurExporter extends OtelExporter {\n name = 'arthur';\n private taskId?: string;\n\n /**\n * @param config - Arthur exporter configuration. All fields are optional when\n * the corresponding environment variables are set.\n */\n constructor(config: ArthurExporterConfig = {}) {\n const apiKey = config.apiKey ?? process.env.ARTHUR_API_KEY;\n const endpoint = config.endpoint ?? process.env.ARTHUR_BASE_URL;\n const taskId = config.taskId ?? process.env.ARTHUR_TASK_ID;\n\n const headers: Record<string, string> = {\n ...config.headers,\n };\n\n let disabledReason: string | undefined;\n\n if (!apiKey) {\n disabledReason =\n `${LOG_PREFIX} API key is required. ` + `Set ARTHUR_API_KEY environment variable or pass apiKey in config.`;\n } else {\n headers['Authorization'] = `Bearer ${apiKey}`;\n }\n\n if (!disabledReason && !endpoint) {\n disabledReason =\n `${LOG_PREFIX} Endpoint is required. ` + `Set ARTHUR_BASE_URL environment variable or pass endpoint in config.`;\n }\n\n // Ensure the endpoint ends with /api/v1/traces\n const tracesEndpoint = endpoint ? `${stripTrailingSlashes(endpoint)}/api/v1/traces` : 'http://disabled';\n\n if (disabledReason) {\n super({\n ...config,\n provider: {\n custom: {\n endpoint: 'http://disabled',\n headers: {},\n protocol: 'http/protobuf',\n },\n },\n });\n this.setDisabled(disabledReason);\n return;\n }\n\n super({\n exporter: new OpenInferenceOTLPTraceExporter({\n url: tracesEndpoint,\n headers,\n }),\n ...config,\n resourceAttributes: {\n ...(taskId ? { 'arthur.task.id': taskId } : {}),\n ...config.resourceAttributes,\n },\n provider: {\n custom: {\n endpoint: tracesEndpoint,\n headers,\n protocol: 'http/protobuf',\n },\n } satisfies OtelExporterConfig['provider'],\n } satisfies OtelExporterConfig);\n\n this.taskId = taskId;\n }\n\n /**\n * Called after construction with the observability config.\n * Validates that traces can be routed to a task via either taskId or serviceName.\n */\n init(options: InitExporterOptions) {\n super.init(options);\n\n const serviceName = options.config?.serviceName;\n\n if (this.taskId && serviceName) {\n this.logger.warn(\n `${LOG_PREFIX} Both taskId and serviceName are set. Arthur Engine will use serviceName to route traces, ` +\n `ignoring the provided taskId.`,\n );\n } else if (!this.taskId && !serviceName) {\n this.logger.warn(\n `${LOG_PREFIX} Neither taskId nor serviceName is set. Set ARTHUR_TASK_ID environment variable, ` +\n `pass taskId in config, or set serviceName in the observability config so Arthur Engine can route traces to a task.`,\n );\n }\n }\n}\n\n/**\n * Remove trailing '/' characters procedurally. Avoids the polynomial\n * backtracking that a greedy regex like `/\\/+$/` can exhibit when the\n * input is attacker-controlled.\n */\nfunction stripTrailingSlashes(s: string): string {\n let end = s.length;\n while (end > 0 && s.charCodeAt(end - 1) === 47 /* '/' */) {\n end--;\n }\n return end === s.length ? s : s.slice(0, end);\n}\n"]}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/openInferenceOTLPExporter.ts","../src/tracing.ts"],"sourcesContent":["import { convertGenAISpanAttributesToOpenInferenceSpanAttributes } from '@arizeai/openinference-genai';\nimport type { Mutable } from '@arizeai/openinference-genai/types';\nimport {\n INPUT_MIME_TYPE,\n INPUT_VALUE,\n LLM_TOKEN_COUNT_COMPLETION,\n LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO,\n LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING,\n LLM_TOKEN_COUNT_PROMPT,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE,\n LLM_TOKEN_COUNT_TOTAL,\n METADATA,\n OpenInferenceSpanKind,\n OUTPUT_MIME_TYPE,\n OUTPUT_VALUE,\n SemanticConventions,\n SESSION_ID,\n TAG_TAGS,\n USER_ID,\n} from '@arizeai/openinference-semantic-conventions';\nimport type { ExportResult } from '@opentelemetry/core';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport {\n ATTR_GEN_AI_INPUT_MESSAGES,\n ATTR_GEN_AI_OUTPUT_MESSAGES,\n ATTR_GEN_AI_USAGE_INPUT_TOKENS,\n ATTR_GEN_AI_USAGE_OUTPUT_TOKENS,\n ATTR_GEN_AI_TOOL_CALL_ARGUMENTS,\n ATTR_GEN_AI_TOOL_CALL_RESULT,\n} from '@opentelemetry/semantic-conventions/incubating';\n\n// GenAI usage attribute keys (not all are in @opentelemetry/semantic-conventions yet)\nconst GEN_AI_USAGE_REASONING_TOKENS = 'gen_ai.usage.reasoning_tokens';\nconst GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = 'gen_ai.usage.cache_read.input_tokens';\nconst GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS = 'gen_ai.usage.cache_creation.input_tokens';\nconst GEN_AI_USAGE_AUDIO_INPUT_TOKENS = 'gen_ai.usage.audio_input_tokens';\nconst GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS = 'gen_ai.usage.audio_output_tokens';\n\nconst MASTRA_GENERAL_PREFIX = 'mastra.';\nconst MASTRA_METADATA_PREFIX = 'mastra.metadata.';\nconst MASTRA_MODEL_STEP_INPUT = 'mastra.model_step.input';\nconst MASTRA_MODEL_STEP_OUTPUT = 'mastra.model_step.output';\nconst MASTRA_MODEL_CHUNK_OUTPUT = 'mastra.model_chunk.output';\nconst MASTRA_SPAN_TYPE = 'mastra.span.type';\n\n/**\n * Maps Mastra span types to OpenInference span kinds for proper trace categorization.\n *\n * Only non-CHAIN types are mapped here - all other span types default to CHAIN.\n */\nconst SPAN_TYPE_TO_KIND: Record<string, OpenInferenceSpanKind> = {\n // Model spans -> LLM\n model_generation: OpenInferenceSpanKind.LLM,\n model_step: OpenInferenceSpanKind.LLM,\n model_chunk: OpenInferenceSpanKind.LLM,\n // Tool spans -> TOOL\n tool_call: OpenInferenceSpanKind.TOOL,\n mcp_tool_call: OpenInferenceSpanKind.TOOL,\n // Agent spans -> AGENT\n agent_run: OpenInferenceSpanKind.AGENT,\n};\n\n/**\n * Converts GenAI usage metrics to OpenInference LLM token count attributes.\n * Maps from OTEL GenAI semantic conventions to OpenInference semantic conventions.\n *\n * @param attributes - The span attributes containing GenAI usage metrics\n * @returns OpenInference token count attributes\n */\nfunction convertUsageMetricsToOpenInference(attributes: Record<string, any>): Record<string, any> {\n const result: Record<string, any> = {};\n\n const inputTokens = attributes[ATTR_GEN_AI_USAGE_INPUT_TOKENS];\n const outputTokens = attributes[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS];\n\n // Core token counts\n if (inputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT] = inputTokens;\n }\n if (outputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION] = outputTokens;\n }\n\n // Total tokens (compute if we have both input and output)\n if (inputTokens !== undefined && outputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_TOTAL] = inputTokens + outputTokens;\n }\n\n // Cache tokens (prompt details)\n const cacheReadInputTokens = attributes[GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS];\n if (cacheReadInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ] = cacheReadInputTokens;\n }\n\n const cacheCreationInputTokens = attributes[GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS];\n if (cacheCreationInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE] = cacheCreationInputTokens;\n }\n\n // Reasoning tokens (completion details)\n const reasoningTokens = attributes[GEN_AI_USAGE_REASONING_TOKENS];\n if (reasoningTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING] = reasoningTokens;\n }\n\n // Audio tokens\n const audioInputTokens = attributes[GEN_AI_USAGE_AUDIO_INPUT_TOKENS];\n if (audioInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO] = audioInputTokens;\n }\n\n const audioOutputTokens = attributes[GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS];\n if (audioOutputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO] = audioOutputTokens;\n }\n\n return result;\n}\n\n/**\n * Splits Mastra span attributes into two groups:\n * - `metadata`: keys starting with \"mastra.metadata.\" (prefix removed)\n * - `other`: all remaining keys starting with \"mastra.\"\n *\n * Any attributes not starting with \"mastra.\" are ignored entirely.\n */\nfunction splitMastraAttributes(attributes: Record<string, any>): {\n mastraMetadata: Record<string, any>;\n mastraOther: Record<string, any>;\n} {\n return Object.entries(attributes).reduce(\n (acc, [key, value]) => {\n if (key.startsWith(MASTRA_GENERAL_PREFIX)) {\n if (key.startsWith(MASTRA_METADATA_PREFIX)) {\n const strippedKey = key.slice(MASTRA_METADATA_PREFIX.length);\n acc.mastraMetadata[strippedKey] = value;\n } else {\n acc.mastraOther[key] = value;\n }\n }\n return acc;\n },\n {\n mastraMetadata: {} as Record<string, any>,\n mastraOther: {} as Record<string, any>,\n },\n );\n}\n\nexport class OpenInferenceOTLPTraceExporter extends OTLPTraceExporter {\n export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void) {\n const processedSpans = spans.map(span => {\n const attributes = { ...(span.attributes ?? {}) };\n const mutableSpan = span as Mutable<ReadableSpan>;\n\n const { mastraMetadata, mastraOther } = splitMastraAttributes(attributes);\n const processedAttributes = convertGenAISpanAttributesToOpenInferenceSpanAttributes(attributes);\n\n // only add processed attributes if conversion was successful\n if (processedAttributes) {\n const threadId = mastraMetadata['threadId'];\n if (threadId) {\n delete mastraMetadata['threadId'];\n processedAttributes[SESSION_ID] = threadId;\n }\n\n // Map mastra.tags to OpenInference native tag.tags convention (tags are only on root spans)\n if (mastraOther['mastra.tags']) {\n processedAttributes[TAG_TAGS] = mastraOther['mastra.tags'];\n delete mastraOther['mastra.tags'];\n }\n\n const userId = mastraMetadata['userId'];\n if (userId) {\n delete mastraMetadata['userId'];\n processedAttributes[USER_ID] = userId;\n }\n\n // Gather custom metadata into OpenInference metadata (flat best-effort)\n if (Object.keys(mastraMetadata).length > 0) {\n try {\n processedAttributes[METADATA] = JSON.stringify(mastraMetadata);\n } catch {\n // best-effort only\n }\n }\n\n const inputMessages =\n attributes[ATTR_GEN_AI_INPUT_MESSAGES] ??\n attributes[ATTR_GEN_AI_TOOL_CALL_ARGUMENTS] ??\n mastraOther[MASTRA_MODEL_STEP_INPUT];\n if (inputMessages) {\n processedAttributes[INPUT_MIME_TYPE] = 'application/json';\n processedAttributes[INPUT_VALUE] = inputMessages;\n }\n const outputMessages =\n attributes[ATTR_GEN_AI_OUTPUT_MESSAGES] ??\n attributes[ATTR_GEN_AI_TOOL_CALL_RESULT] ??\n mastraOther[MASTRA_MODEL_STEP_OUTPUT] ??\n mastraOther[MASTRA_MODEL_CHUNK_OUTPUT];\n if (outputMessages) {\n processedAttributes[OUTPUT_MIME_TYPE] = 'application/json';\n processedAttributes[OUTPUT_VALUE] = outputMessages;\n }\n\n // Map generic Mastra span input/output to OpenInference input/output\n // These are set by Mastra's gen-ai-semantics.ts for non-LLM/tool spans\n // (e.g., mastra.processor_run.input, mastra.workflow_run.input, etc.)\n if (!processedAttributes[INPUT_VALUE]) {\n for (const key of Object.keys(mastraOther)) {\n if (key.endsWith('.input')) {\n processedAttributes[INPUT_MIME_TYPE] = 'application/json';\n processedAttributes[INPUT_VALUE] = mastraOther[key];\n break;\n }\n }\n }\n if (!processedAttributes[OUTPUT_VALUE]) {\n for (const key of Object.keys(mastraOther)) {\n if (key.endsWith('.output')) {\n processedAttributes[OUTPUT_MIME_TYPE] = 'application/json';\n processedAttributes[OUTPUT_VALUE] = mastraOther[key];\n break;\n }\n }\n }\n\n // Convert GenAI usage metrics to OpenInference token count attributes\n const usageMetrics = convertUsageMetricsToOpenInference(attributes);\n Object.assign(processedAttributes, usageMetrics);\n\n mutableSpan.attributes = { ...processedAttributes, ...mastraOther };\n\n // Set span kind based on mastra.span.type for proper trace categorization\n const spanType = mastraOther[MASTRA_SPAN_TYPE];\n if (typeof spanType === 'string') {\n mutableSpan.attributes[SemanticConventions.OPENINFERENCE_SPAN_KIND] =\n SPAN_TYPE_TO_KIND[spanType] ?? OpenInferenceSpanKind.CHAIN;\n }\n }\n\n return mutableSpan;\n });\n\n super.export(processedSpans, resultCallback);\n }\n}\n","import type { InitExporterOptions } from '@mastra/core/observability';\nimport { OtelExporter } from '@mastra/otel-exporter';\nimport type { OtelExporterConfig } from '@mastra/otel-exporter';\n\nimport { OpenInferenceOTLPTraceExporter } from './openInferenceOTLPExporter.js';\n\nconst LOG_PREFIX = '[ArthurExporter]';\n\nexport type ArthurExporterConfig = Omit<OtelExporterConfig, 'provider' | 'exporter'> & {\n /**\n * Arthur API key for authentication.\n * Falls back to ARTHUR_API_KEY environment variable.\n */\n apiKey?: string;\n /**\n * Arthur platform endpoint (e.g. https://app.arthur.ai).\n * Falls back to ARTHUR_BASE_URL environment variable.\n */\n endpoint?: string;\n /**\n * Arthur task ID to associate traces with.\n * Falls back to ARTHUR_TASK_ID environment variable.\n * At least one of taskId or serviceName (from the observability config) should be provided.\n */\n taskId?: string;\n /**\n * Optional headers to be added to each OTLP request.\n * Note: the Authorization header is managed internally when apiKey is provided.\n */\n headers?: Record<string, string>;\n};\n\n/**\n * Exports Mastra traces to Arthur AI using OpenInference semantic conventions.\n *\n * Supports zero-config setup via environment variables (ARTHUR_API_KEY, ARTHUR_BASE_URL,\n * ARTHUR_TASK_ID) or explicit configuration. Automatically disables itself with a warning\n * when required credentials are missing.\n *\n * @example\n * ```ts\n * const mastra = new Mastra({\n * observability: new Observability({\n * configs: {\n * arthur: {\n * serviceName: 'my-service',\n * exporters: [new ArthurExporter()],\n * },\n * },\n * }),\n * });\n * ```\n */\nexport class ArthurExporter extends OtelExporter {\n name = 'arthur';\n private taskId?: string;\n\n /**\n * @param config - Arthur exporter configuration. All fields are optional when\n * the corresponding environment variables are set.\n */\n constructor(config: ArthurExporterConfig = {}) {\n const apiKey = config.apiKey ?? process.env.ARTHUR_API_KEY;\n const endpoint = config.endpoint ?? process.env.ARTHUR_BASE_URL;\n const taskId = config.taskId ?? process.env.ARTHUR_TASK_ID;\n\n const headers: Record<string, string> = {\n ...config.headers,\n };\n\n let disabledReason: string | undefined;\n\n if (!apiKey) {\n disabledReason =\n `${LOG_PREFIX} API key is required. ` + `Set ARTHUR_API_KEY environment variable or pass apiKey in config.`;\n } else {\n headers['Authorization'] = `Bearer ${apiKey}`;\n }\n\n if (!disabledReason && !endpoint) {\n disabledReason =\n `${LOG_PREFIX} Endpoint is required. ` + `Set ARTHUR_BASE_URL environment variable or pass endpoint in config.`;\n }\n\n // Ensure the endpoint ends with /api/v1/traces\n const tracesEndpoint = endpoint ? `${stripTrailingSlashes(endpoint)}/api/v1/traces` : 'http://disabled';\n\n if (disabledReason) {\n super({\n ...config,\n provider: {\n custom: {\n endpoint: 'http://disabled',\n headers: {},\n protocol: 'http/protobuf',\n },\n },\n });\n this.setDisabled(disabledReason);\n return;\n }\n\n super({\n exporter: new OpenInferenceOTLPTraceExporter({\n url: tracesEndpoint,\n headers,\n }),\n ...config,\n resourceAttributes: {\n ...(taskId ? { 'arthur.task.id': taskId } : {}),\n ...config.resourceAttributes,\n },\n provider: {\n custom: {\n endpoint: tracesEndpoint,\n headers,\n protocol: 'http/protobuf',\n },\n } satisfies OtelExporterConfig['provider'],\n } satisfies OtelExporterConfig);\n\n this.taskId = taskId;\n }\n\n /**\n * Called after construction with the observability config.\n * Validates that traces can be routed to a task via either taskId or serviceName.\n */\n init(options: InitExporterOptions) {\n super.init(options);\n\n const serviceName = options.config?.serviceName;\n\n if (this.taskId && serviceName) {\n this.logger.warn(\n `${LOG_PREFIX} Both taskId and serviceName are set. Arthur Engine will use serviceName to route traces, ` +\n `ignoring the provided taskId.`,\n );\n } else if (!this.taskId && !serviceName) {\n this.logger.warn(\n `${LOG_PREFIX} Neither taskId nor serviceName is set. Set ARTHUR_TASK_ID environment variable, ` +\n `pass taskId in config, or set serviceName in the observability config so Arthur Engine can route traces to a task.`,\n );\n }\n }\n}\n\n/**\n * Remove trailing '/' characters procedurally. Avoids the polynomial\n * backtracking that a greedy regex like `/\\/+$/` can exhibit when the\n * input is attacker-controlled.\n */\nfunction stripTrailingSlashes(s: string): string {\n let end = s.length;\n while (end > 0 && s.charCodeAt(end - 1) === 47 /* '/' */) {\n end--;\n }\n return end === s.length ? s : s.slice(0, end);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/arthur",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "Arthur AI observability provider for Mastra - exports traces using OpenInference semantic conventions",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,19 +30,19 @@
30
30
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
31
31
  "@opentelemetry/sdk-trace-base": "^2.7.1",
32
32
  "@opentelemetry/semantic-conventions": "^1.41.1",
33
- "@mastra/otel-exporter": "1.3.5"
33
+ "@mastra/otel-exporter": "1.3.6"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "22.20.1",
37
37
  "@vitest/coverage-v8": "4.1.10",
38
38
  "@vitest/ui": "4.1.10",
39
39
  "eslint": "^10.7.0",
40
- "tsup": "^8.5.1",
40
+ "tsdown": "0.22.9",
41
41
  "typescript": "^6.0.3",
42
42
  "vitest": "4.1.10",
43
- "@internal/lint": "0.0.115",
44
- "@internal/types-builder": "0.0.90",
45
- "@mastra/core": "1.52.0"
43
+ "@internal/lint": "0.0.118",
44
+ "@internal/types-builder": "0.0.93",
45
+ "@mastra/core": "1.54.0"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@mastra/core": ">=1.16.0-0 <2.0.0-0"
@@ -60,10 +60,11 @@
60
60
  "node": ">=22.13.0"
61
61
  },
62
62
  "scripts": {
63
- "build": "tsup --silent --config tsup.config.ts",
63
+ "build": "tsdown --silent --config tsdown.config.ts",
64
64
  "build:watch": "pnpm build --watch",
65
65
  "test": "vitest run",
66
66
  "test:watch": "vitest watch",
67
- "lint": "eslint ."
67
+ "lint": "oxlint . && eslint .",
68
+ "lint:fix": "oxlint --fix . && eslint --fix ."
68
69
  }
69
70
  }