@mastra/langfuse 1.4.5 → 1.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,23 @@
1
1
  # @mastra/langfuse
2
2
 
3
+ ## 1.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), [`d1b7e3a`](https://github.com/mastra-ai/mastra/commit/d1b7e3a978a309a5653eeaa490d2d6c7c53bd093)]:
8
+ - @mastra/core@1.54.0
9
+ - @mastra/observability@1.16.3
10
+ - @mastra/otel-exporter@1.3.6
11
+
12
+ ## 1.4.6-alpha.0
13
+
14
+ ### Patch Changes
15
+
16
+ - 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), [`d1b7e3a`](https://github.com/mastra-ai/mastra/commit/d1b7e3a978a309a5653eeaa490d2d6c7c53bd093)]:
17
+ - @mastra/core@1.54.0-alpha.1
18
+ - @mastra/observability@1.16.3-alpha.0
19
+ - @mastra/otel-exporter@1.3.6-alpha.0
20
+
3
21
  ## 1.4.5
4
22
 
5
23
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -1,302 +1,307 @@
1
- 'use strict';
2
-
3
- var client = require('@langfuse/client');
4
- var otel = require('@langfuse/otel');
5
- var observability$1 = require('@mastra/core/observability');
6
- var observability = require('@mastra/observability');
7
- var otelExporter = require('@mastra/otel-exporter');
8
-
9
- // src/tracing.ts
10
- var LOG_PREFIX = "[LangfuseExporter]";
11
- var LANGFUSE_DEFAULT_BASE_URL = "https://cloud.langfuse.com";
12
- var LangfuseExporter = class extends observability.BaseExporter {
13
- name = "langfuse";
14
- #processor;
15
- #client;
16
- #spanConverter;
17
- #realtime;
18
- #environment;
19
- #release;
20
- constructor(config = {}) {
21
- super(config);
22
- const publicKey = config.publicKey ?? process.env.LANGFUSE_PUBLIC_KEY;
23
- const secretKey = config.secretKey ?? process.env.LANGFUSE_SECRET_KEY;
24
- const baseUrl = stripTrailingSlashes(config.baseUrl ?? process.env.LANGFUSE_BASE_URL ?? LANGFUSE_DEFAULT_BASE_URL);
25
- this.#realtime = config.realtime ?? false;
26
- if (!publicKey || !secretKey) {
27
- const publicKeySource = config.publicKey ? "from config" : process.env.LANGFUSE_PUBLIC_KEY ? "from env" : "missing";
28
- const secretKeySource = config.secretKey ? "from config" : process.env.LANGFUSE_SECRET_KEY ? "from env" : "missing";
29
- this.setDisabled(
30
- `${LOG_PREFIX} Missing required credentials (publicKey: ${publicKeySource}, secretKey: ${secretKeySource}). Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY environment variables or pass them in config.`
31
- );
32
- return;
33
- }
34
- this.#processor = new otel.LangfuseSpanProcessor({
35
- publicKey,
36
- secretKey,
37
- baseUrl,
38
- environment: config.environment,
39
- release: config.release,
40
- exportMode: this.#realtime ? "immediate" : "batched",
41
- flushAt: config.flushAt,
42
- flushInterval: config.flushInterval,
43
- // Export all spans — the default filter only passes spans with gen_ai.* attributes
44
- // or known LLM instrumentation scopes, but Mastra spans use mastra.* attributes.
45
- shouldExportSpan: () => true
46
- });
47
- this.#client = new client.LangfuseClient({
48
- publicKey,
49
- secretKey,
50
- baseUrl
51
- });
52
- this.#environment = config.environment ?? process.env.LANGFUSE_TRACING_ENVIRONMENT;
53
- this.#release = config.release ?? process.env.LANGFUSE_RELEASE;
54
- }
55
- init(options) {
56
- this.#spanConverter = new otelExporter.SpanConverter({
57
- packageName: "@mastra/langfuse",
58
- serviceName: options.config?.serviceName,
59
- format: "GenAI_v1_38_0"
60
- });
61
- }
62
- async _exportTracingEvent(event) {
63
- if (event.type !== observability$1.TracingEventType.SPAN_ENDED) return;
64
- if (!this.#processor) return;
65
- await this.exportSpan(event.exportedSpan);
66
- }
67
- async exportSpan(span) {
68
- if (!this.#spanConverter) {
69
- this.#spanConverter = new otelExporter.SpanConverter({
70
- packageName: "@mastra/langfuse",
71
- serviceName: "mastra-service",
72
- format: "GenAI_v1_38_0"
73
- });
74
- }
75
- try {
76
- const otelSpan = await this.#spanConverter.convertSpan(span);
77
- mapMastraToLangfuseAttributes(otelSpan.attributes, span, this.#environment, this.#release);
78
- this.#processor.onEnd(otelSpan);
79
- } catch (error) {
80
- this.logger.error(`${LOG_PREFIX} Failed to export span ${span.id}:`, error);
81
- }
82
- }
83
- /**
84
- * The LangfuseClient instance for advanced Langfuse features.
85
- * Use this for prompt management, evaluations, datasets, and direct API access.
86
- */
87
- get client() {
88
- return this.#client;
89
- }
90
- /**
91
- * Submit a score to Langfuse. Used by both the new `onScoreEvent` path and the
92
- * deprecated `addScoreToTrace` wrapper.
93
- */
94
- submitScore(args) {
95
- if (!this.#client) return;
96
- const { id, traceId, spanId, name, value, comment, metadata } = args;
97
- try {
98
- this.#client.score.create({
99
- id,
100
- traceId,
101
- ...spanId ? { observationId: spanId } : {},
102
- name,
103
- value,
104
- ...comment ? { comment } : {},
105
- ...metadata ? { metadata } : {},
106
- dataType: "NUMERIC"
107
- });
108
- } catch (error) {
109
- this.logger.error(`${LOG_PREFIX} Error submitting score`, {
110
- error,
111
- traceId,
112
- spanId,
113
- name
114
- });
115
- }
116
- }
117
- async onScoreEvent(event) {
118
- const { score } = event;
119
- if (!score.traceId) return;
120
- this.submitScore({
121
- id: score.scoreId,
122
- traceId: score.traceId,
123
- spanId: score.spanId,
124
- name: score.scorerName ?? score.scorerId,
125
- value: score.score,
126
- comment: score.reason,
127
- metadata: score.metadata
128
- });
129
- }
130
- /**
131
- * @deprecated Use the observability score event pipeline (`mastra.observability.addScore`)
132
- * instead. This method is preserved for backwards compatibility and forwards to the same
133
- * underlying client call as `onScoreEvent`.
134
- */
135
- async addScoreToTrace({
136
- traceId,
137
- spanId,
138
- score,
139
- reason,
140
- scorerName,
141
- metadata
142
- }) {
143
- this.submitScore({
144
- id: `${traceId}-${spanId || ""}-${scorerName}`,
145
- traceId,
146
- spanId,
147
- name: scorerName,
148
- value: score,
149
- comment: reason,
150
- metadata
151
- });
152
- }
153
- async flush() {
154
- await Promise.all([this.#processor?.forceFlush(), this.#client?.flush()]);
155
- }
156
- async shutdown() {
157
- await Promise.all([this.#processor?.shutdown(), this.#client?.shutdown()]);
158
- }
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _langfuse_client = require("@langfuse/client");
3
+ let _langfuse_otel = require("@langfuse/otel");
4
+ let _mastra_core_observability = require("@mastra/core/observability");
5
+ let _mastra_observability = require("@mastra/observability");
6
+ let _mastra_otel_exporter = require("@mastra/otel-exporter");
7
+ //#region src/tracing.ts
8
+ /**
9
+ * Langfuse Exporter for Mastra Observability
10
+ *
11
+ * Sends observability data to Langfuse using the official @langfuse/otel span processor
12
+ * and @langfuse/client for non-tracing features (scoring, prompt management, evaluations).
13
+ *
14
+ * @see https://langfuse.com/docs/observability/sdk/typescript/overview
15
+ */
16
+ const LOG_PREFIX = "[LangfuseExporter]";
17
+ const LANGFUSE_DEFAULT_BASE_URL = "https://cloud.langfuse.com";
18
+ var LangfuseExporter = class extends _mastra_observability.BaseExporter {
19
+ name = "langfuse";
20
+ #processor;
21
+ #client;
22
+ #spanConverter;
23
+ #realtime;
24
+ #environment;
25
+ #release;
26
+ constructor(config = {}) {
27
+ super(config);
28
+ const publicKey = config.publicKey ?? process.env.LANGFUSE_PUBLIC_KEY;
29
+ const secretKey = config.secretKey ?? process.env.LANGFUSE_SECRET_KEY;
30
+ const baseUrl = stripTrailingSlashes(config.baseUrl ?? process.env.LANGFUSE_BASE_URL ?? "https://cloud.langfuse.com");
31
+ this.#realtime = config.realtime ?? false;
32
+ if (!publicKey || !secretKey) {
33
+ const publicKeySource = config.publicKey ? "from config" : process.env.LANGFUSE_PUBLIC_KEY ? "from env" : "missing";
34
+ const secretKeySource = config.secretKey ? "from config" : process.env.LANGFUSE_SECRET_KEY ? "from env" : "missing";
35
+ this.setDisabled(`${LOG_PREFIX} Missing required credentials (publicKey: ${publicKeySource}, secretKey: ${secretKeySource}). Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY environment variables or pass them in config.`);
36
+ return;
37
+ }
38
+ this.#processor = new _langfuse_otel.LangfuseSpanProcessor({
39
+ publicKey,
40
+ secretKey,
41
+ baseUrl,
42
+ environment: config.environment,
43
+ release: config.release,
44
+ exportMode: this.#realtime ? "immediate" : "batched",
45
+ flushAt: config.flushAt,
46
+ flushInterval: config.flushInterval,
47
+ shouldExportSpan: () => true
48
+ });
49
+ this.#client = new _langfuse_client.LangfuseClient({
50
+ publicKey,
51
+ secretKey,
52
+ baseUrl
53
+ });
54
+ this.#environment = config.environment ?? process.env.LANGFUSE_TRACING_ENVIRONMENT;
55
+ this.#release = config.release ?? process.env.LANGFUSE_RELEASE;
56
+ }
57
+ init(options) {
58
+ this.#spanConverter = new _mastra_otel_exporter.SpanConverter({
59
+ packageName: "@mastra/langfuse",
60
+ serviceName: options.config?.serviceName,
61
+ format: "GenAI_v1_38_0"
62
+ });
63
+ }
64
+ async _exportTracingEvent(event) {
65
+ if (event.type !== _mastra_core_observability.TracingEventType.SPAN_ENDED) return;
66
+ if (!this.#processor) return;
67
+ await this.exportSpan(event.exportedSpan);
68
+ }
69
+ async exportSpan(span) {
70
+ if (!this.#spanConverter) this.#spanConverter = new _mastra_otel_exporter.SpanConverter({
71
+ packageName: "@mastra/langfuse",
72
+ serviceName: "mastra-service",
73
+ format: "GenAI_v1_38_0"
74
+ });
75
+ try {
76
+ const otelSpan = await this.#spanConverter.convertSpan(span);
77
+ mapMastraToLangfuseAttributes(otelSpan.attributes, span, this.#environment, this.#release);
78
+ this.#processor.onEnd(otelSpan);
79
+ } catch (error) {
80
+ this.logger.error(`${LOG_PREFIX} Failed to export span ${span.id}:`, error);
81
+ }
82
+ }
83
+ /**
84
+ * The LangfuseClient instance for advanced Langfuse features.
85
+ * Use this for prompt management, evaluations, datasets, and direct API access.
86
+ */
87
+ get client() {
88
+ return this.#client;
89
+ }
90
+ /**
91
+ * Submit a score to Langfuse. Used by both the new `onScoreEvent` path and the
92
+ * deprecated `addScoreToTrace` wrapper.
93
+ */
94
+ submitScore(args) {
95
+ if (!this.#client) return;
96
+ const { id, traceId, spanId, name, value, comment, metadata } = args;
97
+ try {
98
+ this.#client.score.create({
99
+ id,
100
+ traceId,
101
+ ...spanId ? { observationId: spanId } : {},
102
+ name,
103
+ value,
104
+ ...comment ? { comment } : {},
105
+ ...metadata ? { metadata } : {},
106
+ dataType: "NUMERIC"
107
+ });
108
+ } catch (error) {
109
+ this.logger.error(`${LOG_PREFIX} Error submitting score`, {
110
+ error,
111
+ traceId,
112
+ spanId,
113
+ name
114
+ });
115
+ }
116
+ }
117
+ async onScoreEvent(event) {
118
+ const { score } = event;
119
+ if (!score.traceId) return;
120
+ this.submitScore({
121
+ id: score.scoreId,
122
+ traceId: score.traceId,
123
+ spanId: score.spanId,
124
+ name: score.scorerName ?? score.scorerId,
125
+ value: score.score,
126
+ comment: score.reason,
127
+ metadata: score.metadata
128
+ });
129
+ }
130
+ /**
131
+ * @deprecated Use the observability score event pipeline (`mastra.observability.addScore`)
132
+ * instead. This method is preserved for backwards compatibility and forwards to the same
133
+ * underlying client call as `onScoreEvent`.
134
+ */
135
+ async addScoreToTrace({ traceId, spanId, score, reason, scorerName, metadata }) {
136
+ this.submitScore({
137
+ id: `${traceId}-${spanId || ""}-${scorerName}`,
138
+ traceId,
139
+ spanId,
140
+ name: scorerName,
141
+ value: score,
142
+ comment: reason,
143
+ metadata
144
+ });
145
+ }
146
+ async flush() {
147
+ await Promise.all([this.#processor?.forceFlush(), this.#client?.flush()]);
148
+ }
149
+ async shutdown() {
150
+ await Promise.all([this.#processor?.shutdown(), this.#client?.shutdown()]);
151
+ }
159
152
  };
153
+ /**
154
+ * Maps Mastra-specific OTel attributes to the langfuse.* namespace that
155
+ * Langfuse's OTLP endpoint reads for prompt linking, TTFT, and other features.
156
+ *
157
+ * SpanConverter produces attributes like mastra.metadata.*, mastra.completion_start_time, etc.
158
+ * Langfuse's OTLP server only reads langfuse.observation.prompt.name, langfuse.observation.completion_start_time, etc.
159
+ *
160
+ * This function mutates the attributes object in place.
161
+ * @see https://langfuse.com/integrations/native/opentelemetry#property-mapping
162
+ */
160
163
  function mapMastraToLangfuseAttributes(attributes, span, environment, release) {
161
- if (environment) {
162
- attributes["langfuse.environment"] = environment;
163
- }
164
- if (release) {
165
- attributes["langfuse.release"] = release;
166
- }
167
- const langfuseMetadata = attributes["mastra.metadata.langfuse"];
168
- if (langfuseMetadata) {
169
- try {
170
- const parsed = typeof langfuseMetadata === "string" ? JSON.parse(langfuseMetadata) : langfuseMetadata;
171
- if (parsed && typeof parsed === "object") {
172
- if (parsed.prompt) {
173
- if (parsed.prompt.name !== void 0) {
174
- attributes["langfuse.observation.prompt.name"] = parsed.prompt.name;
175
- }
176
- if (parsed.prompt.version !== void 0) {
177
- attributes["langfuse.observation.prompt.version"] = parsed.prompt.version;
178
- }
179
- }
180
- for (const [key, value] of Object.entries(parsed)) {
181
- if (key === "prompt" || value === null || value === void 0) {
182
- continue;
183
- }
184
- const traceKey = `langfuse.trace.metadata.${key}`;
185
- if (attributes[traceKey] === void 0) {
186
- attributes[traceKey] = typeof value === "string" ? value : JSON.stringify(value);
187
- }
188
- }
189
- }
190
- } catch {
191
- }
192
- delete attributes["mastra.metadata.langfuse"];
193
- }
194
- if (attributes["mastra.completion_start_time"]) {
195
- attributes["langfuse.observation.completion_start_time"] = attributes["mastra.completion_start_time"];
196
- delete attributes["mastra.completion_start_time"];
197
- }
198
- if (attributes["mastra.metadata.userId"]) {
199
- attributes["user.id"] = attributes["mastra.metadata.userId"];
200
- delete attributes["mastra.metadata.userId"];
201
- }
202
- const sessionId = attributes["mastra.metadata.sessionId"] ?? attributes["mastra.metadata.threadId"];
203
- if (sessionId) {
204
- attributes["session.id"] = sessionId;
205
- delete attributes["mastra.metadata.sessionId"];
206
- delete attributes["mastra.metadata.threadId"];
207
- }
208
- if (attributes["mastra.tags"]) {
209
- attributes["langfuse.trace.tags"] = attributes["mastra.tags"];
210
- delete attributes["mastra.tags"];
211
- }
212
- if (attributes["mastra.metadata.traceName"]) {
213
- attributes["langfuse.trace.name"] = attributes["mastra.metadata.traceName"];
214
- delete attributes["mastra.metadata.traceName"];
215
- }
216
- if (attributes["mastra.metadata.version"]) {
217
- attributes["langfuse.trace.version"] = attributes["mastra.metadata.version"];
218
- delete attributes["mastra.metadata.version"];
219
- }
220
- if (span.isRootSpan) {
221
- if (span.type === observability$1.SpanType.AGENT_RUN) {
222
- if (!attributes["langfuse.trace.name"] && (span.entityName || span.entityId)) {
223
- attributes["langfuse.trace.name"] = span.entityName ?? span.entityId;
224
- }
225
- if (span.entityId) {
226
- attributes["langfuse.trace.metadata.agentId"] = span.entityId;
227
- }
228
- if (span.entityName) {
229
- attributes["langfuse.trace.metadata.agentName"] = span.entityName;
230
- }
231
- } else if (span.type === observability$1.SpanType.WORKFLOW_RUN) {
232
- if (!attributes["langfuse.trace.name"] && (span.entityName || span.entityId)) {
233
- attributes["langfuse.trace.name"] = span.entityName ?? span.entityId;
234
- }
235
- if (span.entityId) {
236
- attributes["langfuse.trace.metadata.workflowId"] = span.entityId;
237
- }
238
- if (span.entityName) {
239
- attributes["langfuse.trace.metadata.workflowName"] = span.entityName;
240
- }
241
- }
242
- }
243
- if (attributes["gen_ai.agent.id"]) {
244
- attributes["langfuse.observation.metadata.agentId"] = attributes["gen_ai.agent.id"];
245
- }
246
- if (attributes["gen_ai.agent.name"]) {
247
- attributes["langfuse.observation.metadata.agentName"] = attributes["gen_ai.agent.name"];
248
- }
249
- if (attributes["mastra.span.type"]) {
250
- attributes["langfuse.observation.metadata.spanType"] = attributes["mastra.span.type"];
251
- }
252
- if (attributes["gen_ai.operation.name"]) {
253
- attributes["langfuse.observation.metadata.operationName"] = attributes["gen_ai.operation.name"];
254
- }
255
- if (!attributes["gen_ai.input.messages"] && !attributes["gen_ai.tool.call.arguments"]) {
256
- for (const key of Object.keys(attributes)) {
257
- if (key.startsWith("mastra.") && key.endsWith(".input")) {
258
- attributes["langfuse.observation.input"] = attributes[key];
259
- break;
260
- }
261
- }
262
- }
263
- if (!attributes["gen_ai.output.messages"] && !attributes["gen_ai.tool.call.result"]) {
264
- for (const key of Object.keys(attributes)) {
265
- if (key.startsWith("mastra.") && key.endsWith(".output")) {
266
- attributes["langfuse.observation.output"] = attributes[key];
267
- break;
268
- }
269
- }
270
- }
164
+ if (environment) attributes["langfuse.environment"] = environment;
165
+ if (release) attributes["langfuse.release"] = release;
166
+ const langfuseMetadata = attributes["mastra.metadata.langfuse"];
167
+ if (langfuseMetadata) {
168
+ try {
169
+ const parsed = typeof langfuseMetadata === "string" ? JSON.parse(langfuseMetadata) : langfuseMetadata;
170
+ if (parsed && typeof parsed === "object") {
171
+ if (parsed.prompt) {
172
+ if (parsed.prompt.name !== void 0) attributes["langfuse.observation.prompt.name"] = parsed.prompt.name;
173
+ if (parsed.prompt.version !== void 0) attributes["langfuse.observation.prompt.version"] = parsed.prompt.version;
174
+ }
175
+ for (const [key, value] of Object.entries(parsed)) {
176
+ if (key === "prompt" || value === null || value === void 0) continue;
177
+ const traceKey = `langfuse.trace.metadata.${key}`;
178
+ if (attributes[traceKey] === void 0) attributes[traceKey] = typeof value === "string" ? value : JSON.stringify(value);
179
+ }
180
+ }
181
+ } catch {}
182
+ delete attributes["mastra.metadata.langfuse"];
183
+ }
184
+ if (attributes["mastra.completion_start_time"]) {
185
+ attributes["langfuse.observation.completion_start_time"] = attributes["mastra.completion_start_time"];
186
+ delete attributes["mastra.completion_start_time"];
187
+ }
188
+ if (attributes["mastra.metadata.userId"]) {
189
+ attributes["user.id"] = attributes["mastra.metadata.userId"];
190
+ delete attributes["mastra.metadata.userId"];
191
+ }
192
+ const sessionId = attributes["mastra.metadata.sessionId"] ?? attributes["mastra.metadata.threadId"];
193
+ if (sessionId) {
194
+ attributes["session.id"] = sessionId;
195
+ delete attributes["mastra.metadata.sessionId"];
196
+ delete attributes["mastra.metadata.threadId"];
197
+ }
198
+ if (attributes["mastra.tags"]) {
199
+ attributes["langfuse.trace.tags"] = attributes["mastra.tags"];
200
+ delete attributes["mastra.tags"];
201
+ }
202
+ if (attributes["mastra.metadata.traceName"]) {
203
+ attributes["langfuse.trace.name"] = attributes["mastra.metadata.traceName"];
204
+ delete attributes["mastra.metadata.traceName"];
205
+ }
206
+ if (attributes["mastra.metadata.version"]) {
207
+ attributes["langfuse.trace.version"] = attributes["mastra.metadata.version"];
208
+ delete attributes["mastra.metadata.version"];
209
+ }
210
+ if (span.isRootSpan) {
211
+ if (span.type === _mastra_core_observability.SpanType.AGENT_RUN) {
212
+ if (!attributes["langfuse.trace.name"] && (span.entityName || span.entityId)) attributes["langfuse.trace.name"] = span.entityName ?? span.entityId;
213
+ if (span.entityId) attributes["langfuse.trace.metadata.agentId"] = span.entityId;
214
+ if (span.entityName) attributes["langfuse.trace.metadata.agentName"] = span.entityName;
215
+ } else if (span.type === _mastra_core_observability.SpanType.WORKFLOW_RUN) {
216
+ if (!attributes["langfuse.trace.name"] && (span.entityName || span.entityId)) attributes["langfuse.trace.name"] = span.entityName ?? span.entityId;
217
+ if (span.entityId) attributes["langfuse.trace.metadata.workflowId"] = span.entityId;
218
+ if (span.entityName) attributes["langfuse.trace.metadata.workflowName"] = span.entityName;
219
+ }
220
+ }
221
+ if (attributes["gen_ai.agent.id"]) attributes["langfuse.observation.metadata.agentId"] = attributes["gen_ai.agent.id"];
222
+ if (attributes["gen_ai.agent.name"]) attributes["langfuse.observation.metadata.agentName"] = attributes["gen_ai.agent.name"];
223
+ if (attributes["mastra.span.type"]) attributes["langfuse.observation.metadata.spanType"] = attributes["mastra.span.type"];
224
+ if (attributes["gen_ai.operation.name"]) attributes["langfuse.observation.metadata.operationName"] = attributes["gen_ai.operation.name"];
225
+ if (!attributes["gen_ai.input.messages"] && !attributes["gen_ai.tool.call.arguments"]) {
226
+ for (const key of Object.keys(attributes)) if (key.startsWith("mastra.") && key.endsWith(".input")) {
227
+ attributes["langfuse.observation.input"] = attributes[key];
228
+ break;
229
+ }
230
+ }
231
+ if (!attributes["gen_ai.output.messages"] && !attributes["gen_ai.tool.call.result"]) {
232
+ for (const key of Object.keys(attributes)) if (key.startsWith("mastra.") && key.endsWith(".output")) {
233
+ attributes["langfuse.observation.output"] = attributes[key];
234
+ break;
235
+ }
236
+ }
271
237
  }
238
+ /**
239
+ * Remove trailing "/" characters procedurally. Avoids polynomial
240
+ * backtracking that a greedy regex like `/\/+$/` can exhibit when the
241
+ * input is attacker-controlled.
242
+ */
272
243
  function stripTrailingSlashes(s) {
273
- let end = s.length;
274
- while (end > 0 && s.charCodeAt(end - 1) === 47) {
275
- end--;
276
- }
277
- return end === s.length ? s : s.slice(0, end);
244
+ let end = s.length;
245
+ while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
246
+ return end === s.length ? s : s.slice(0, end);
278
247
  }
279
-
280
- // src/helpers.ts
248
+ //#endregion
249
+ //#region src/helpers.ts
250
+ /**
251
+ * Adds Langfuse prompt metadata to the tracing options
252
+ * to enable Langfuse Prompt Tracing.
253
+ *
254
+ * The metadata is added under `metadata.langfuse.prompt` and includes:
255
+ * - `name` - Prompt name (required for Langfuse v5)
256
+ * - `version` - Prompt version (required for Langfuse v5)
257
+ *
258
+ * All fields are deeply merged with any existing metadata.
259
+ *
260
+ * @param prompt - Prompt fields for linking (`name` and `version` required)
261
+ * @returns A TracingOptionsUpdater function for use with `buildTracingOptions`
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * import { buildTracingOptions } from '@mastra/observability';
266
+ * import { withLangfusePrompt } from '@mastra/langfuse';
267
+ *
268
+ * // Link a generation to a Langfuse prompt by name and version
269
+ * const tracingOptions = buildTracingOptions(
270
+ * withLangfusePrompt({ name: 'customer-support', version: 1 }),
271
+ * );
272
+ *
273
+ * // Or directly in agent config
274
+ * const agent = new Agent({
275
+ * name: 'support-agent',
276
+ * instructions: 'You are a helpful assistant',
277
+ * model: openai('gpt-4o'),
278
+ * defaultGenerateOptions: {
279
+ * tracingOptions: buildTracingOptions(
280
+ * withLangfusePrompt({ name: 'my-prompt', version: 1 }),
281
+ * ),
282
+ * },
283
+ * });
284
+ * ```
285
+ */
281
286
  function withLangfusePrompt(prompt) {
282
- return (opts) => ({
283
- ...opts,
284
- metadata: {
285
- ...opts.metadata,
286
- langfuse: {
287
- ...opts.metadata?.langfuse,
288
- prompt: {
289
- ...prompt.name !== void 0 && { name: prompt.name },
290
- ...prompt.version !== void 0 && { version: prompt.version },
291
- ...prompt.id !== void 0 && { id: prompt.id }
292
- }
293
- }
294
- }
295
- });
287
+ return (opts) => ({
288
+ ...opts,
289
+ metadata: {
290
+ ...opts.metadata,
291
+ langfuse: {
292
+ ...opts.metadata?.langfuse,
293
+ prompt: {
294
+ ...prompt.name !== void 0 && { name: prompt.name },
295
+ ...prompt.version !== void 0 && { version: prompt.version },
296
+ ...prompt.id !== void 0 && { id: prompt.id }
297
+ }
298
+ }
299
+ }
300
+ });
296
301
  }
297
-
302
+ //#endregion
298
303
  exports.LANGFUSE_DEFAULT_BASE_URL = LANGFUSE_DEFAULT_BASE_URL;
299
304
  exports.LangfuseExporter = LangfuseExporter;
300
305
  exports.withLangfusePrompt = withLangfusePrompt;
301
- //# sourceMappingURL=index.cjs.map
306
+
302
307
  //# sourceMappingURL=index.cjs.map