@mastra/langfuse 1.0.0-beta.9 → 1.0.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6,18 +6,22 @@ var observability = require('@mastra/observability');
6
6
  var langfuse = require('langfuse');
7
7
 
8
8
  // src/tracing.ts
9
+
10
+ // src/metrics.ts
9
11
  function formatUsageMetrics(usage) {
10
12
  if (!usage) return {};
11
13
  const metrics = {};
12
14
  if (usage.inputTokens !== void 0) {
13
15
  metrics.input = usage.inputTokens;
16
+ if (usage.inputDetails?.cacheRead !== void 0) {
17
+ metrics.cache_read_input_tokens = usage.inputDetails.cacheRead;
18
+ metrics.input -= metrics.cache_read_input_tokens;
19
+ }
14
20
  if (usage.inputDetails?.cacheWrite !== void 0) {
15
21
  metrics.cache_write_input_tokens = usage.inputDetails.cacheWrite;
16
22
  metrics.input -= metrics.cache_write_input_tokens;
17
23
  }
18
- }
19
- if (usage.inputDetails?.cacheRead !== void 0) {
20
- metrics.cache_read_input_tokens = usage.inputDetails.cacheRead;
24
+ if (metrics.input < 0) metrics.input = 0;
21
25
  }
22
26
  if (usage.outputTokens !== void 0) {
23
27
  metrics.output = usage.outputTokens;
@@ -25,204 +29,109 @@ function formatUsageMetrics(usage) {
25
29
  if (usage.outputDetails?.reasoning !== void 0) {
26
30
  metrics.reasoning = usage.outputDetails.reasoning;
27
31
  }
28
- if (metrics.input && metrics.output) {
32
+ if (metrics.input != null && metrics.output != null) {
29
33
  metrics.total = metrics.input + metrics.output;
30
- if (metrics.cache_write_input_tokens) {
34
+ if (metrics.cache_read_input_tokens != null) {
35
+ metrics.total += metrics.cache_read_input_tokens;
36
+ }
37
+ if (metrics.cache_write_input_tokens != null) {
31
38
  metrics.total += metrics.cache_write_input_tokens;
32
39
  }
33
40
  }
34
41
  return metrics;
35
42
  }
36
- var LangfuseExporter = class extends observability.BaseExporter {
43
+
44
+ // src/tracing.ts
45
+ var LangfuseExporter = class extends observability.TrackingExporter {
37
46
  name = "langfuse";
38
- client;
39
- realtime;
40
- traceMap = /* @__PURE__ */ new Map();
41
- constructor(config) {
42
- super(config);
43
- this.realtime = config.realtime ?? false;
44
- if (!config.publicKey || !config.secretKey) {
47
+ #client;
48
+ #realtime;
49
+ constructor(config = {}) {
50
+ const publicKey = config.publicKey ?? process.env.LANGFUSE_PUBLIC_KEY;
51
+ const secretKey = config.secretKey ?? process.env.LANGFUSE_SECRET_KEY;
52
+ const baseUrl = config.baseUrl ?? process.env.LANGFUSE_BASE_URL;
53
+ super({
54
+ ...config,
55
+ publicKey,
56
+ secretKey,
57
+ baseUrl
58
+ });
59
+ this.#realtime = config.realtime ?? false;
60
+ if (!publicKey || !secretKey) {
61
+ const publicKeySource = config.publicKey ? "from config" : process.env.LANGFUSE_PUBLIC_KEY ? "from env" : "missing";
62
+ const secretKeySource = config.secretKey ? "from config" : process.env.LANGFUSE_SECRET_KEY ? "from env" : "missing";
45
63
  this.setDisabled(
46
- `Missing required credentials (publicKey: ${!!config.publicKey}, secretKey: ${!!config.secretKey})`
64
+ `Missing required credentials (publicKey: ${publicKeySource}, secretKey: ${secretKeySource}). Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY environment variables or pass them in config.`
47
65
  );
48
- this.client = null;
49
66
  return;
50
67
  }
51
- this.client = new langfuse.Langfuse({
52
- publicKey: config.publicKey,
53
- secretKey: config.secretKey,
54
- baseUrl: config.baseUrl,
68
+ this.#client = new langfuse.Langfuse({
69
+ publicKey,
70
+ secretKey,
71
+ baseUrl,
55
72
  ...config.options
56
73
  });
57
74
  }
58
- async _exportTracingEvent(event) {
59
- if (event.exportedSpan.isEvent) {
60
- await this.handleEventSpan(event.exportedSpan);
61
- return;
62
- }
63
- switch (event.type) {
64
- case "span_started":
65
- await this.handleSpanStarted(event.exportedSpan);
66
- break;
67
- case "span_updated":
68
- await this.handleSpanUpdateOrEnd(event.exportedSpan, false);
69
- break;
70
- case "span_ended":
71
- await this.handleSpanUpdateOrEnd(event.exportedSpan, true);
72
- break;
73
- }
74
- if (this.realtime) {
75
- await this.client.flushAsync();
75
+ async _postExportTracingEvent() {
76
+ if (this.#realtime) {
77
+ await this.#client?.flushAsync();
76
78
  }
77
79
  }
78
- async handleSpanStarted(span) {
79
- if (span.isRootSpan) {
80
- this.initTrace(span);
81
- }
82
- const method = "handleSpanStarted";
83
- const traceData = this.getTraceData({ span, method });
84
- if (!traceData) {
85
- return;
86
- }
87
- if (!span.isRootSpan) {
88
- const langfuseData = span.metadata?.langfuse;
89
- traceData.spanMetadata.set(span.id, {
90
- parentSpanId: span.parentSpanId,
91
- langfusePrompt: langfuseData?.prompt
92
- });
93
- }
94
- const langfuseParent = this.getLangfuseParent({ traceData, span, method });
80
+ async _buildRoot(args) {
81
+ const { span } = args;
82
+ return this.#client?.trace(this.buildTracePayload(span));
83
+ }
84
+ async _buildEvent(args) {
85
+ const { span, traceData } = args;
86
+ const langfuseParent = traceData.getParentOrRoot({ span });
95
87
  if (!langfuseParent) {
96
88
  return;
97
89
  }
98
90
  const payload = this.buildSpanPayload(span, true, traceData);
99
- const langfuseSpan = span.type === observability$1.SpanType.MODEL_GENERATION ? langfuseParent.generation(payload) : langfuseParent.span(payload);
100
- traceData.spans.set(span.id, langfuseSpan);
101
- traceData.activeSpans.add(span.id);
91
+ return langfuseParent.event(payload);
102
92
  }
103
- async handleSpanUpdateOrEnd(span, isEnd) {
104
- const method = isEnd ? "handleSpanEnd" : "handleSpanUpdate";
105
- const traceData = this.getTraceData({ span, method });
106
- if (!traceData) {
107
- return;
108
- }
109
- const langfuseSpan = traceData.spans.get(span.id);
110
- if (!langfuseSpan) {
111
- if (isEnd && span.isEvent) {
112
- traceData.activeSpans.delete(span.id);
113
- if (traceData.activeSpans.size === 0) {
114
- this.traceMap.delete(span.traceId);
115
- }
116
- return;
117
- }
118
- this.logger.warn("Langfuse exporter: No Langfuse span found for span update/end", {
119
- traceId: span.traceId,
120
- spanId: span.id,
121
- spanName: span.name,
122
- spanType: span.type,
123
- isRootSpan: span.isRootSpan,
124
- parentSpanId: span.parentSpanId,
125
- method
126
- });
127
- return;
128
- }
129
- langfuseSpan.update(this.buildSpanPayload(span, false, traceData));
130
- if (isEnd) {
131
- traceData.activeSpans.delete(span.id);
132
- if (span.isRootSpan) {
133
- traceData.trace.update({ output: span.output });
134
- }
135
- if (traceData.activeSpans.size === 0) {
136
- this.traceMap.delete(span.traceId);
137
- }
138
- }
139
- }
140
- async handleEventSpan(span) {
141
- if (span.isRootSpan) {
142
- this.logger.debug("Langfuse exporter: Creating trace", {
143
- traceId: span.traceId,
144
- spanId: span.id,
145
- spanName: span.name,
146
- method: "handleEventSpan"
147
- });
148
- this.initTrace(span);
149
- }
150
- const method = "handleEventSpan";
151
- const traceData = this.getTraceData({ span, method });
152
- if (!traceData) {
153
- return;
154
- }
155
- const langfuseParent = this.getLangfuseParent({ traceData, span, method });
93
+ async _buildSpan(args) {
94
+ const { span, traceData } = args;
95
+ const langfuseParent = traceData.getParentOrRoot({ span });
156
96
  if (!langfuseParent) {
157
97
  return;
158
98
  }
159
99
  const payload = this.buildSpanPayload(span, true, traceData);
160
- const langfuseEvent = langfuseParent.event(payload);
161
- traceData.events.set(span.id, langfuseEvent);
162
- if (!span.endTime) {
163
- traceData.activeSpans.add(span.id);
164
- }
100
+ const langfuseSpan = span.type === observability$1.SpanType.MODEL_GENERATION ? langfuseParent.generation(payload) : langfuseParent.span(payload);
101
+ this.logger.debug(`${this.name}: built span`, {
102
+ traceId: span.traceId,
103
+ spanId: payload.id,
104
+ method: "_buildSpan"
105
+ });
106
+ return langfuseSpan;
165
107
  }
166
- initTrace(span) {
167
- if (this.traceMap.has(span.traceId)) {
168
- this.logger.debug("Langfuse exporter: Reusing existing trace from local map", {
108
+ async _updateSpan(args) {
109
+ const { span, traceData } = args;
110
+ const langfuseSpan = traceData.getSpan({ spanId: span.id });
111
+ if (langfuseSpan) {
112
+ this.logger.debug(`${this.name}: found span for update`, {
169
113
  traceId: span.traceId,
170
- spanId: span.id,
171
- spanName: span.name
114
+ spanId: langfuseSpan.id,
115
+ method: "_updateSpan"
172
116
  });
173
- return;
117
+ const updatePayload = this.buildSpanPayload(span, false, traceData);
118
+ langfuseSpan.update(updatePayload);
174
119
  }
175
- const trace = this.client.trace(this.buildTracePayload(span));
176
- const langfuseData = span.metadata?.langfuse;
177
- const spanMetadata = /* @__PURE__ */ new Map();
178
- spanMetadata.set(span.id, {
179
- parentSpanId: void 0,
180
- langfusePrompt: langfuseData?.prompt
181
- });
182
- this.traceMap.set(span.traceId, {
183
- trace,
184
- spans: /* @__PURE__ */ new Map(),
185
- spanMetadata,
186
- events: /* @__PURE__ */ new Map(),
187
- activeSpans: /* @__PURE__ */ new Set(),
188
- rootSpanId: span.id
189
- });
190
120
  }
191
- getTraceData(options) {
192
- const { span, method } = options;
193
- if (this.traceMap.has(span.traceId)) {
194
- return this.traceMap.get(span.traceId);
121
+ async _finishSpan(args) {
122
+ const { span, traceData } = args;
123
+ const langfuseSpan = traceData.getSpan({ spanId: span.id });
124
+ langfuseSpan?.update(this.buildSpanPayload(span, false, traceData));
125
+ if (span.isRootSpan) {
126
+ const langfuseRoot = traceData.getRoot();
127
+ langfuseRoot?.update({ output: span.output });
195
128
  }
196
- this.logger.warn("Langfuse exporter: No trace data found for span", {
197
- traceId: span.traceId,
198
- spanId: span.id,
199
- spanName: span.name,
200
- spanType: span.type,
201
- isRootSpan: span.isRootSpan,
202
- parentSpanId: span.parentSpanId,
203
- method
204
- });
205
129
  }
206
- getLangfuseParent(options) {
207
- const { traceData, span, method } = options;
208
- const parentId = span.parentSpanId;
209
- if (!parentId) {
210
- return traceData.trace;
211
- }
212
- if (traceData.spans.has(parentId)) {
213
- return traceData.spans.get(parentId);
214
- }
215
- if (traceData.events.has(parentId)) {
216
- return traceData.events.get(parentId);
217
- }
218
- this.logger.warn("Langfuse exporter: No parent data found for span", {
219
- traceId: span.traceId,
220
- spanId: span.id,
221
- spanName: span.name,
222
- spanType: span.type,
223
- isRootSpan: span.isRootSpan,
224
- parentSpanId: span.parentSpanId,
225
- method
130
+ async _abortSpan(args) {
131
+ const { span, reason } = args;
132
+ span.end({
133
+ level: "ERROR",
134
+ statusMessage: reason.message
226
135
  });
227
136
  }
228
137
  buildTracePayload(span) {
@@ -243,18 +152,27 @@ var LangfuseExporter = class extends observability.BaseExporter {
243
152
  return payload;
244
153
  }
245
154
  /**
246
- * Look up the Langfuse prompt from the closest parent span that has one.
155
+ * Look up the Langfuse prompt from the closest span that has one.
247
156
  * This enables prompt inheritance for MODEL_GENERATION spans when the prompt
248
157
  * is set on a parent span (e.g., AGENT_RUN) rather than directly on the generation.
158
+ * This enables prompt linking when:
159
+ * - A workflow calls multiple agents, each with different prompts
160
+ * - Nested agents have different prompts
161
+ * - The prompt is set on AGENT_RUN but MODEL_GENERATION inherits it
249
162
  */
250
- findParentLangfusePrompt(traceData, span) {
251
- let currentSpanId = span.parentSpanId;
163
+ findLangfusePrompt(traceData, span) {
164
+ let currentSpanId = span.id;
252
165
  while (currentSpanId) {
253
- const parentMetadata = traceData.spanMetadata.get(currentSpanId);
254
- if (parentMetadata?.langfusePrompt) {
255
- return parentMetadata.langfusePrompt;
166
+ const providerMetadata = traceData.getMetadata({ spanId: currentSpanId });
167
+ if (providerMetadata?.prompt) {
168
+ this.logger.debug(`${this.name}: found prompt in provider metadata`, {
169
+ traceId: span.traceId,
170
+ spanId: span.id,
171
+ prompt: providerMetadata?.prompt
172
+ });
173
+ return providerMetadata.prompt;
256
174
  }
257
- currentSpanId = parentMetadata?.parentSpanId;
175
+ currentSpanId = traceData.getParentId({ spanId: currentSpanId });
258
176
  }
259
177
  return void 0;
260
178
  }
@@ -264,19 +182,13 @@ var LangfuseExporter = class extends observability.BaseExporter {
264
182
  payload.id = span.id;
265
183
  payload.name = span.name;
266
184
  payload.startTime = span.startTime;
267
- if (span.input !== void 0) payload.input = span.input;
268
185
  }
186
+ if (span.input !== void 0) payload.input = span.input;
269
187
  if (span.output !== void 0) payload.output = span.output;
270
188
  if (span.endTime !== void 0) payload.endTime = span.endTime;
271
189
  const attributes = span.attributes ?? {};
272
- const resolvedTraceData = traceData ?? this.traceMap.get(span.traceId);
273
- let inheritedLangfusePrompt;
274
- if (span.type === observability$1.SpanType.MODEL_GENERATION && !span.metadata?.langfuse && resolvedTraceData) {
275
- inheritedLangfusePrompt = this.findParentLangfusePrompt(resolvedTraceData, span);
276
- }
277
190
  const metadata = {
278
- ...span.metadata,
279
- ...inheritedLangfusePrompt ? { langfuse: { prompt: inheritedLangfusePrompt } } : {}
191
+ ...span.metadata
280
192
  };
281
193
  const attributesToOmit = [];
282
194
  const metadataToOmit = [];
@@ -294,8 +206,7 @@ var LangfuseExporter = class extends observability.BaseExporter {
294
206
  payload.modelParameters = modelAttr.parameters;
295
207
  attributesToOmit.push("parameters");
296
208
  }
297
- const langfuseData = metadata.langfuse;
298
- const promptData = langfuseData?.prompt;
209
+ const promptData = this.findLangfusePrompt(traceData, span);
299
210
  const hasNameAndVersion = promptData?.name !== void 0 && promptData?.version !== void 0;
300
211
  const hasId = promptData?.id !== void 0;
301
212
  if (hasNameAndVersion || hasId) {
@@ -329,9 +240,9 @@ var LangfuseExporter = class extends observability.BaseExporter {
329
240
  scorerName,
330
241
  metadata
331
242
  }) {
332
- if (!this.client) return;
243
+ if (!this.#client) return;
333
244
  try {
334
- await this.client.score({
245
+ await this.#client.score({
335
246
  id: `${traceId}-${scorerName}`,
336
247
  traceId,
337
248
  observationId: spanId,
@@ -350,12 +261,18 @@ var LangfuseExporter = class extends observability.BaseExporter {
350
261
  });
351
262
  }
352
263
  }
353
- async shutdown() {
354
- if (this.client) {
355
- await this.client.shutdownAsync();
264
+ /**
265
+ * Force flush any buffered data to Langfuse without shutting down.
266
+ */
267
+ async _flush() {
268
+ if (this.#client) {
269
+ await this.#client.flushAsync();
270
+ }
271
+ }
272
+ async _postShutdown() {
273
+ if (this.#client) {
274
+ await this.#client.shutdownAsync();
356
275
  }
357
- this.traceMap.clear();
358
- await super.shutdown();
359
276
  }
360
277
  };
361
278
 
@@ -378,7 +295,6 @@ function withLangfusePrompt(prompt) {
378
295
  }
379
296
 
380
297
  exports.LangfuseExporter = LangfuseExporter;
381
- exports.formatUsageMetrics = formatUsageMetrics;
382
298
  exports.withLangfusePrompt = withLangfusePrompt;
383
299
  //# sourceMappingURL=index.cjs.map
384
300
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tracing.ts","../src/helpers.ts"],"names":["BaseExporter","Langfuse","SpanType","omitKeys"],"mappings":";;;;;;;;AA8DO,SAAS,mBAAmB,KAAA,EAA0C;AAC3E,EAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AAEpB,EAAA,MAAM,UAAgC,EAAC;AAEvC,EAAA,IAAI,KAAA,CAAM,gBAAgB,MAAA,EAAW;AACnC,IAAA,OAAA,CAAQ,QAAQ,KAAA,CAAM,WAAA;AAEtB,IAAA,IAAI,KAAA,CAAM,YAAA,EAAc,UAAA,KAAe,MAAA,EAAW;AAChD,MAAA,OAAA,CAAQ,wBAAA,GAA2B,MAAM,YAAA,CAAa,UAAA;AACtD,MAAA,OAAA,CAAQ,SAAS,OAAA,CAAQ,wBAAA;AAAA,IAC3B;AAAA,EACF;AAEA,EAAA,IAAI,KAAA,CAAM,YAAA,EAAc,SAAA,KAAc,MAAA,EAAW;AAC/C,IAAA,OAAA,CAAQ,uBAAA,GAA0B,MAAM,YAAA,CAAa,SAAA;AAAA,EACvD;AAEA,EAAA,IAAI,KAAA,CAAM,iBAAiB,MAAA,EAAW;AACpC,IAAA,OAAA,CAAQ,SAAS,KAAA,CAAM,YAAA;AAAA,EACzB;AAEA,EAAA,IAAI,KAAA,CAAM,aAAA,EAAe,SAAA,KAAc,MAAA,EAAW;AAChD,IAAA,OAAA,CAAQ,SAAA,GAAY,MAAM,aAAA,CAAc,SAAA;AAAA,EAC1C;AAEA,EAAA,IAAI,OAAA,CAAQ,KAAA,IAAS,OAAA,CAAQ,MAAA,EAAQ;AACnC,IAAA,OAAA,CAAQ,KAAA,GAAQ,OAAA,CAAQ,KAAA,GAAQ,OAAA,CAAQ,MAAA;AACxC,IAAA,IAAI,QAAQ,wBAAA,EAA0B;AACpC,MAAA,OAAA,CAAQ,SAAS,OAAA,CAAQ,wBAAA;AAAA,IAC3B;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;AAEO,IAAM,gBAAA,GAAN,cAA+BA,0BAAA,CAAa;AAAA,EACjD,IAAA,GAAO,UAAA;AAAA,EACC,MAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA,uBAAe,GAAA,EAAuB;AAAA,EAE9C,YAAY,MAAA,EAAgC;AAC1C,IAAA,KAAA,CAAM,MAAM,CAAA;AAEZ,IAAA,IAAA,CAAK,QAAA,GAAW,OAAO,QAAA,IAAY,KAAA;AAEnC,IAAA,IAAI,CAAC,MAAA,CAAO,SAAA,IAAa,CAAC,OAAO,SAAA,EAAW;AAC1C,MAAA,IAAA,CAAK,WAAA;AAAA,QACH,CAAA,yCAAA,EAA4C,CAAC,CAAC,MAAA,CAAO,SAAS,CAAA,aAAA,EAAgB,CAAC,CAAC,MAAA,CAAO,SAAS,CAAA,CAAA;AAAA,OAClG;AAEA,MAAA,IAAA,CAAK,MAAA,GAAS,IAAA;AACd,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,MAAA,GAAS,IAAIC,iBAAA,CAAS;AAAA,MACzB,WAAW,MAAA,CAAO,SAAA;AAAA,MAClB,WAAW,MAAA,CAAO,SAAA;AAAA,MAClB,SAAS,MAAA,CAAO,OAAA;AAAA,MAChB,GAAG,MAAA,CAAO;AAAA,KACX,CAAA;AAAA,EACH;AAAA,EAEA,MAAgB,oBAAoB,KAAA,EAAoC;AACtE,IAAA,IAAI,KAAA,CAAM,aAAa,OAAA,EAAS;AAC9B,MAAA,MAAM,IAAA,CAAK,eAAA,CAAgB,KAAA,CAAM,YAAY,CAAA;AAC7C,MAAA;AAAA,IACF;AAEA,IAAA,QAAQ,MAAM,IAAA;AAAM,MAClB,KAAK,cAAA;AACH,QAAA,MAAM,IAAA,CAAK,iBAAA,CAAkB,KAAA,CAAM,YAAY,CAAA;AAC/C,QAAA;AAAA,MACF,KAAK,cAAA;AACH,QAAA,MAAM,IAAA,CAAK,qBAAA,CAAsB,KAAA,CAAM,YAAA,EAAc,KAAK,CAAA;AAC1D,QAAA;AAAA,MACF,KAAK,YAAA;AACH,QAAA,MAAM,IAAA,CAAK,qBAAA,CAAsB,KAAA,CAAM,YAAA,EAAc,IAAI,CAAA;AACzD,QAAA;AAAA;AAIJ,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA,MAAM,IAAA,CAAK,OAAO,UAAA,EAAW;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,MAAc,kBAAkB,IAAA,EAAsC;AACpE,IAAA,IAAI,KAAK,UAAA,EAAY;AACnB,MAAA,IAAA,CAAK,UAAU,IAAI,CAAA;AAAA,IACrB;AACA,IAAA,MAAM,MAAA,GAAS,mBAAA;AAEf,IAAA,MAAM,YAAY,IAAA,CAAK,YAAA,CAAa,EAAE,IAAA,EAAM,QAAQ,CAAA;AACpD,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,CAAC,KAAK,UAAA,EAAY;AACpB,MAAA,MAAM,YAAA,GAAe,KAAK,QAAA,EAAU,QAAA;AACpC,MAAA,SAAA,CAAU,YAAA,CAAa,GAAA,CAAI,IAAA,CAAK,EAAA,EAAI;AAAA,QAClC,cAAc,IAAA,CAAK,YAAA;AAAA,QACnB,gBAAgB,YAAA,EAAc;AAAA,OAC/B,CAAA;AAAA,IACH;AAEA,IAAA,MAAM,iBAAiB,IAAA,CAAK,iBAAA,CAAkB,EAAE,SAAA,EAAW,IAAA,EAAM,QAAQ,CAAA;AACzE,IAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,gBAAA,CAAiB,IAAA,EAAM,MAAM,SAAS,CAAA;AAE3D,IAAA,MAAM,YAAA,GACJ,IAAA,CAAK,IAAA,KAASC,wBAAA,CAAS,gBAAA,GAAmB,cAAA,CAAe,UAAA,CAAW,OAAO,CAAA,GAAI,cAAA,CAAe,IAAA,CAAK,OAAO,CAAA;AAE5G,IAAA,SAAA,CAAU,KAAA,CAAM,GAAA,CAAI,IAAA,CAAK,EAAA,EAAI,YAAY,CAAA;AACzC,IAAA,SAAA,CAAU,WAAA,CAAY,GAAA,CAAI,IAAA,CAAK,EAAE,CAAA;AAAA,EACnC;AAAA,EAEA,MAAc,qBAAA,CAAsB,IAAA,EAAuB,KAAA,EAA+B;AACxF,IAAA,MAAM,MAAA,GAAS,QAAQ,eAAA,GAAkB,kBAAA;AAEzC,IAAA,MAAM,YAAY,IAAA,CAAK,YAAA,CAAa,EAAE,IAAA,EAAM,QAAQ,CAAA;AACpD,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,YAAA,GAAe,SAAA,CAAU,KAAA,CAAM,GAAA,CAAI,KAAK,EAAE,CAAA;AAChD,IAAA,IAAI,CAAC,YAAA,EAAc;AAEjB,MAAA,IAAI,KAAA,IAAS,KAAK,OAAA,EAAS;AAEzB,QAAA,SAAA,CAAU,WAAA,CAAY,MAAA,CAAO,IAAA,CAAK,EAAE,CAAA;AACpC,QAAA,IAAI,SAAA,CAAU,WAAA,CAAY,IAAA,KAAS,CAAA,EAAG;AACpC,UAAA,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA;AAAA,QACnC;AACA,QAAA;AAAA,MACF;AAEA,MAAA,IAAA,CAAK,MAAA,CAAO,KAAK,+DAAA,EAAiE;AAAA,QAChF,SAAS,IAAA,CAAK,OAAA;AAAA,QACd,QAAQ,IAAA,CAAK,EAAA;AAAA,QACb,UAAU,IAAA,CAAK,IAAA;AAAA,QACf,UAAU,IAAA,CAAK,IAAA;AAAA,QACf,YAAY,IAAA,CAAK,UAAA;AAAA,QACjB,cAAc,IAAA,CAAK,YAAA;AAAA,QACnB;AAAA,OACD,CAAA;AACD,MAAA;AAAA,IACF;AAIA,IAAA,YAAA,CAAa,OAAO,IAAA,CAAK,gBAAA,CAAiB,IAAA,EAAM,KAAA,EAAO,SAAS,CAAC,CAAA;AAEjE,IAAA,IAAI,KAAA,EAAO;AAET,MAAA,SAAA,CAAU,WAAA,CAAY,MAAA,CAAO,IAAA,CAAK,EAAE,CAAA;AAEpC,MAAA,IAAI,KAAK,UAAA,EAAY;AACnB,QAAA,SAAA,CAAU,MAAM,MAAA,CAAO,EAAE,MAAA,EAAQ,IAAA,CAAK,QAAQ,CAAA;AAAA,MAChD;AAGA,MAAA,IAAI,SAAA,CAAU,WAAA,CAAY,IAAA,KAAS,CAAA,EAAG;AACpC,QAAA,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,gBAAgB,IAAA,EAAsC;AAClE,IAAA,IAAI,KAAK,UAAA,EAAY;AACnB,MAAA,IAAA,CAAK,MAAA,CAAO,MAAM,mCAAA,EAAqC;AAAA,QACrD,SAAS,IAAA,CAAK,OAAA;AAAA,QACd,QAAQ,IAAA,CAAK,EAAA;AAAA,QACb,UAAU,IAAA,CAAK,IAAA;AAAA,QACf,MAAA,EAAQ;AAAA,OACT,CAAA;AACD,MAAA,IAAA,CAAK,UAAU,IAAI,CAAA;AAAA,IACrB;AACA,IAAA,MAAM,MAAA,GAAS,iBAAA;AAEf,IAAA,MAAM,YAAY,IAAA,CAAK,YAAA,CAAa,EAAE,IAAA,EAAM,QAAQ,CAAA;AACpD,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,iBAAiB,IAAA,CAAK,iBAAA,CAAkB,EAAE,SAAA,EAAW,IAAA,EAAM,QAAQ,CAAA;AACzE,IAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,gBAAA,CAAiB,IAAA,EAAM,MAAM,SAAS,CAAA;AAE3D,IAAA,MAAM,aAAA,GAAgB,cAAA,CAAe,KAAA,CAAM,OAAO,CAAA;AAElD,IAAA,SAAA,CAAU,MAAA,CAAO,GAAA,CAAI,IAAA,CAAK,EAAA,EAAI,aAAa,CAAA;AAG3C,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AACjB,MAAA,SAAA,CAAU,WAAA,CAAY,GAAA,CAAI,IAAA,CAAK,EAAE,CAAA;AAAA,IACnC;AAAA,EACF;AAAA,EAEQ,UAAU,IAAA,EAA6B;AAI7C,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,IAAA,CAAK,OAAO,CAAA,EAAG;AACnC,MAAA,IAAA,CAAK,MAAA,CAAO,MAAM,0DAAA,EAA4D;AAAA,QAC5E,SAAS,IAAA,CAAK,OAAA;AAAA,QACd,QAAQ,IAAA,CAAK,EAAA;AAAA,QACb,UAAU,IAAA,CAAK;AAAA,OAChB,CAAA;AACD,MAAA;AAAA,IACF;AAOA,IAAA,MAAM,QAAQ,IAAA,CAAK,MAAA,CAAO,MAAM,IAAA,CAAK,iBAAA,CAAkB,IAAI,CAAC,CAAA;AAG5D,IAAA,MAAM,YAAA,GAAe,KAAK,QAAA,EAAU,QAAA;AACpC,IAAA,MAAM,YAAA,uBAAmB,GAAA,EAA0B;AAGnD,IAAA,YAAA,CAAa,GAAA,CAAI,KAAK,EAAA,EAAI;AAAA,MACxB,YAAA,EAAc,MAAA;AAAA,MACd,gBAAgB,YAAA,EAAc;AAAA,KAC/B,CAAA;AAED,IAAA,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS;AAAA,MAC9B,KAAA;AAAA,MACA,KAAA,sBAAW,GAAA,EAAI;AAAA,MACf,YAAA;AAAA,MACA,MAAA,sBAAY,GAAA,EAAI;AAAA,MAChB,WAAA,sBAAiB,GAAA,EAAI;AAAA,MACrB,YAAY,IAAA,CAAK;AAAA,KAClB,CAAA;AAAA,EACH;AAAA,EAEQ,aAAa,OAAA,EAA2E;AAC9F,IAAA,MAAM,EAAE,IAAA,EAAM,MAAA,EAAO,GAAI,OAAA;AAEzB,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,IAAA,CAAK,OAAO,CAAA,EAAG;AACnC,MAAA,OAAO,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,IAAA,CAAK,OAAO,CAAA;AAAA,IACvC;AAEA,IAAA,IAAA,CAAK,MAAA,CAAO,KAAK,iDAAA,EAAmD;AAAA,MAClE,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,QAAQ,IAAA,CAAK,EAAA;AAAA,MACb,UAAU,IAAA,CAAK,IAAA;AAAA,MACf,UAAU,IAAA,CAAK,IAAA;AAAA,MACf,YAAY,IAAA,CAAK,UAAA;AAAA,MACjB,cAAc,IAAA,CAAK,YAAA;AAAA,MACnB;AAAA,KACD,CAAA;AAAA,EACH;AAAA,EAEQ,kBAAkB,OAAA,EAIK;AAC7B,IAAA,MAAM,EAAE,SAAA,EAAW,IAAA,EAAM,MAAA,EAAO,GAAI,OAAA;AAEpC,IAAA,MAAM,WAAW,IAAA,CAAK,YAAA;AACtB,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,OAAO,SAAA,CAAU,KAAA;AAAA,IACnB;AACA,IAAA,IAAI,SAAA,CAAU,KAAA,CAAM,GAAA,CAAI,QAAQ,CAAA,EAAG;AACjC,MAAA,OAAO,SAAA,CAAU,KAAA,CAAM,GAAA,CAAI,QAAQ,CAAA;AAAA,IACrC;AACA,IAAA,IAAI,SAAA,CAAU,MAAA,CAAO,GAAA,CAAI,QAAQ,CAAA,EAAG;AAClC,MAAA,OAAO,SAAA,CAAU,MAAA,CAAO,GAAA,CAAI,QAAQ,CAAA;AAAA,IACtC;AACA,IAAA,IAAA,CAAK,MAAA,CAAO,KAAK,kDAAA,EAAoD;AAAA,MACnE,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,QAAQ,IAAA,CAAK,EAAA;AAAA,MACb,UAAU,IAAA,CAAK,IAAA;AAAA,MACf,UAAU,IAAA,CAAK,IAAA;AAAA,MACf,YAAY,IAAA,CAAK,UAAA;AAAA,MACjB,cAAc,IAAA,CAAK,YAAA;AAAA,MACnB;AAAA,KACD,CAAA;AAAA,EACH;AAAA,EAEQ,kBAAkB,IAAA,EAA4C;AACpE,IAAA,MAAM,OAAA,GAA+B;AAAA,MACnC,IAAI,IAAA,CAAK,OAAA;AAAA,MACT,MAAM,IAAA,CAAK;AAAA,KACb;AAEA,IAAA,MAAM,EAAE,QAAQ,SAAA,EAAW,GAAG,mBAAkB,GAAI,IAAA,CAAK,YAAY,EAAC;AAEtE,IAAA,IAAI,MAAA,UAAgB,MAAA,GAAS,MAAA;AAC7B,IAAA,IAAI,SAAA,UAAmB,SAAA,GAAY,SAAA;AACnC,IAAA,IAAI,IAAA,CAAK,KAAA,EAAO,OAAA,CAAQ,KAAA,GAAQ,IAAA,CAAK,KAAA;AAErC,IAAA,IAAI,IAAA,CAAK,IAAA,EAAM,MAAA,EAAQ,OAAA,CAAQ,OAAO,IAAA,CAAK,IAAA;AAE3C,IAAA,OAAA,CAAQ,QAAA,GAAW;AAAA,MACjB,UAAU,IAAA,CAAK,IAAA;AAAA,MACf,GAAG,IAAA,CAAK,UAAA;AAAA,MACR,GAAG;AAAA,KACL;AAEA,IAAA,OAAO,OAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,wBAAA,CAAyB,WAAsB,IAAA,EAAuD;AAC5G,IAAA,IAAI,gBAAgB,IAAA,CAAK,YAAA;AAEzB,IAAA,OAAO,aAAA,EAAe;AACpB,MAAA,MAAM,cAAA,GAAiB,SAAA,CAAU,YAAA,CAAa,GAAA,CAAI,aAAa,CAAA;AAC/D,MAAA,IAAI,gBAAgB,cAAA,EAAgB;AAClC,QAAA,OAAO,cAAA,CAAe,cAAA;AAAA,MACxB;AACA,MAAA,aAAA,GAAgB,cAAA,EAAgB,YAAA;AAAA,IAClC;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA,EAEQ,gBAAA,CAAiB,IAAA,EAAuB,QAAA,EAAmB,SAAA,EAA4C;AAC7G,IAAA,MAAM,UAA+B,EAAC;AAEtC,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,OAAA,CAAQ,KAAK,IAAA,CAAK,EAAA;AAClB,MAAA,OAAA,CAAQ,OAAO,IAAA,CAAK,IAAA;AACpB,MAAA,OAAA,CAAQ,YAAY,IAAA,CAAK,SAAA;AACzB,MAAA,IAAI,IAAA,CAAK,KAAA,KAAU,MAAA,EAAW,OAAA,CAAQ,QAAQ,IAAA,CAAK,KAAA;AAAA,IACrD;AAEA,IAAA,IAAI,IAAA,CAAK,MAAA,KAAW,MAAA,EAAW,OAAA,CAAQ,SAAS,IAAA,CAAK,MAAA;AACrD,IAAA,IAAI,IAAA,CAAK,OAAA,KAAY,MAAA,EAAW,OAAA,CAAQ,UAAU,IAAA,CAAK,OAAA;AAEvD,IAAA,MAAM,UAAA,GAAc,IAAA,CAAK,UAAA,IAAc,EAAC;AAOxC,IAAA,MAAM,oBAAoB,SAAA,IAAa,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,KAAK,OAAO,CAAA;AACrE,IAAA,IAAI,uBAAA;AAEJ,IAAA,IAAI,IAAA,CAAK,SAASA,wBAAA,CAAS,gBAAA,IAAoB,CAAC,IAAA,CAAK,QAAA,EAAU,YAAY,iBAAA,EAAmB;AAC5F,MAAA,uBAAA,GAA0B,IAAA,CAAK,wBAAA,CAAyB,iBAAA,EAAmB,IAAI,CAAA;AAAA,IACjF;AAEA,IAAA,MAAM,QAAA,GAAgC;AAAA,MACpC,GAAG,IAAA,CAAK,QAAA;AAAA,MACR,GAAI,0BAA0B,EAAE,QAAA,EAAU,EAAE,MAAA,EAAQ,uBAAA,EAAwB,EAAE,GAAI;AAAC,KACrF;AAGA,IAAA,MAAM,mBAA6B,EAAC;AACpC,IAAA,MAAM,iBAA2B,EAAC;AAElC,IAAA,IAAI,IAAA,CAAK,IAAA,KAASA,wBAAA,CAAS,gBAAA,EAAkB;AAC3C,MAAA,MAAM,SAAA,GAAY,UAAA;AAElB,MAAA,IAAI,SAAA,CAAU,UAAU,MAAA,EAAW;AACjC,QAAA,OAAA,CAAQ,QAAQ,SAAA,CAAU,KAAA;AAC1B,QAAA,gBAAA,CAAiB,KAAK,OAAO,CAAA;AAAA,MAC/B;AAEA,MAAA,IAAI,SAAA,CAAU,UAAU,MAAA,EAAW;AACjC,QAAA,OAAA,CAAQ,YAAA,GAAe,kBAAA,CAAmB,SAAA,CAAU,KAAK,CAAA;AACzD,QAAA,gBAAA,CAAiB,KAAK,OAAO,CAAA;AAAA,MAC/B;AAEA,MAAA,IAAI,SAAA,CAAU,eAAe,MAAA,EAAW;AACtC,QAAA,OAAA,CAAQ,kBAAkB,SAAA,CAAU,UAAA;AACpC,QAAA,gBAAA,CAAiB,KAAK,YAAY,CAAA;AAAA,MACpC;AAQA,MAAA,MAAM,eAAe,QAAA,CAAS,QAAA;AAG9B,MAAA,MAAM,aAAa,YAAA,EAAc,MAAA;AACjC,MAAA,MAAM,iBAAA,GAAoB,UAAA,EAAY,IAAA,KAAS,MAAA,IAAa,YAAY,OAAA,KAAY,MAAA;AACpF,MAAA,MAAM,KAAA,GAAQ,YAAY,EAAA,KAAO,MAAA;AAEjC,MAAA,IAAI,qBAAqB,KAAA,EAAO;AAC9B,QAAA,OAAA,CAAQ,SAAS,EAAC;AAElB,QAAA,IAAI,YAAY,IAAA,KAAS,MAAA,EAAW,OAAA,CAAQ,MAAA,CAAO,OAAO,UAAA,CAAW,IAAA;AACrE,QAAA,IAAI,YAAY,OAAA,KAAY,MAAA,EAAW,OAAA,CAAQ,MAAA,CAAO,UAAU,UAAA,CAAW,OAAA;AAC3E,QAAA,IAAI,YAAY,EAAA,KAAO,MAAA,EAAW,OAAA,CAAQ,MAAA,CAAO,KAAK,UAAA,CAAW,EAAA;AAEjE,QAAA,cAAA,CAAe,KAAK,UAAU,CAAA;AAAA,MAChC;AAGA,MAAA,IAAI,SAAA,CAAU,wBAAwB,MAAA,EAAW;AAC/C,QAAA,OAAA,CAAQ,sBAAsB,SAAA,CAAU,mBAAA;AACxC,QAAA,gBAAA,CAAiB,KAAK,qBAAqB,CAAA;AAAA,MAC7C;AAAA,IACF;AAEA,IAAA,OAAA,CAAQ,QAAA,GAAW;AAAA,MACjB,UAAU,IAAA,CAAK,IAAA;AAAA,MACf,GAAGC,cAAA,CAAS,UAAA,EAAY,gBAAgB,CAAA;AAAA,MACxC,GAAGA,cAAA,CAAS,QAAA,EAAU,cAAc;AAAA,KACtC;AAEA,IAAA,IAAI,KAAK,SAAA,EAAW;AAClB,MAAA,OAAA,CAAQ,KAAA,GAAQ,OAAA;AAChB,MAAA,OAAA,CAAQ,aAAA,GAAgB,KAAK,SAAA,CAAU,OAAA;AAAA,IACzC;AAEA,IAAA,OAAO,OAAA;AAAA,EACT;AAAA,EAEA,MAAM,eAAA,CAAgB;AAAA,IACpB,OAAA;AAAA,IACA,MAAA;AAAA,IACA,KAAA;AAAA,IACA,MAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GACF,EAOkB;AAChB,IAAA,IAAI,CAAC,KAAK,MAAA,EAAQ;AAElB,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,CAAK,OAAO,KAAA,CAAM;AAAA,QACtB,EAAA,EAAI,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,UAAU,CAAA,CAAA;AAAA,QAC5B,OAAA;AAAA,QACA,aAAA,EAAe,MAAA;AAAA,QACf,IAAA,EAAM,UAAA;AAAA,QACN,KAAA,EAAO,KAAA;AAAA,QACP,GAAI,UAAU,SAAA,GAAY,EAAE,WAAW,QAAA,CAAS,SAAA,KAAc,EAAC;AAAA,QAC/D,QAAA,EAAU,EAAE,GAAI,MAAA,GAAS,EAAE,MAAA,EAAO,GAAI,EAAC,EAAG;AAAA,QAC1C,QAAA,EAAU;AAAA,OACX,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,MAAA,CAAO,MAAM,gDAAA,EAAkD;AAAA,QAClE,KAAA;AAAA,QACA,OAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OACD,CAAA;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,QAAA,GAA0B;AAC9B,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,MAAM,IAAA,CAAK,OAAO,aAAA,EAAc;AAAA,IAClC;AACA,IAAA,IAAA,CAAK,SAAS,KAAA,EAAM;AACpB,IAAA,MAAM,MAAM,QAAA,EAAS;AAAA,EACvB;AACF;;;AC5cO,SAAS,mBAAmB,MAAA,EAAoD;AACrF,EAAA,OAAO,CAAA,IAAA,MAAS;AAAA,IACd,GAAG,IAAA;AAAA,IACH,QAAA,EAAU;AAAA,MACR,GAAG,IAAA,CAAK,QAAA;AAAA,MACR,QAAA,EAAU;AAAA,QACR,GAAI,KAAK,QAAA,EAAU,QAAA;AAAA,QACnB,MAAA,EAAQ;AAAA,UACN,GAAI,MAAA,CAAO,IAAA,KAAS,UAAa,EAAE,IAAA,EAAM,OAAO,IAAA,EAAK;AAAA,UACrD,GAAI,MAAA,CAAO,OAAA,KAAY,UAAa,EAAE,OAAA,EAAS,OAAO,OAAA,EAAQ;AAAA,UAC9D,GAAI,MAAA,CAAO,EAAA,KAAO,UAAa,EAAE,EAAA,EAAI,OAAO,EAAA;AAAG;AACjD;AACF;AACF,GACF,CAAA;AACF","file":"index.cjs","sourcesContent":["/**\n * Langfuse Exporter for Mastra Observability\n *\n * This exporter sends observability data to Langfuse.\n * Root spans start traces in Langfuse.\n * MODEL_GENERATION spans become Langfuse generations, all others become spans.\n */\n\nimport type { TracingEvent, AnyExportedSpan, ModelGenerationAttributes, UsageStats } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { BaseExporter } from '@mastra/observability';\nimport type { BaseExporterConfig } from '@mastra/observability';\nimport { Langfuse } from 'langfuse';\nimport type { LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient, LangfuseEventClient } from 'langfuse';\n\nexport interface LangfuseExporterConfig extends BaseExporterConfig {\n /** Langfuse API key */\n publicKey?: string;\n /** Langfuse secret key */\n secretKey?: string;\n /** Langfuse host URL */\n baseUrl?: string;\n /** Enable realtime mode - flushes after each event for immediate visibility */\n realtime?: boolean;\n /** Additional options to pass to the Langfuse client */\n options?: any;\n}\n\ntype LangfusePromptData = { name?: string; version?: number; id?: string };\n\ntype SpanMetadata = {\n parentSpanId?: string;\n langfusePrompt?: LangfusePromptData;\n};\n\ntype TraceData = {\n trace: LangfuseTraceClient; // Langfuse trace object\n spans: Map<string, LangfuseSpanClient | LangfuseGenerationClient>; // Maps span.id to Langfuse span/generation\n spanMetadata: Map<string, SpanMetadata>; // Maps span.id to span metadata for prompt inheritance\n events: Map<string, LangfuseEventClient>; // Maps span.id to Langfuse event\n activeSpans: Set<string>; // Tracks which spans haven't ended yet\n rootSpanId?: string; // Track the root span ID\n};\n\ntype LangfuseParent = LangfuseTraceClient | LangfuseSpanClient | LangfuseGenerationClient | LangfuseEventClient;\n\n/**\n * Token usage format compatible with Langfuse.\n */\nexport interface LangfuseUsageMetrics {\n input?: number;\n output?: number;\n total?: number;\n reasoning?: number;\n cache_read_input_tokens?: number;\n cache_write_input_tokens?: number;\n}\n\n/**\n * Formats UsageStats to Langfuse's expected format.\n */\nexport function formatUsageMetrics(usage?: UsageStats): LangfuseUsageMetrics {\n if (!usage) return {};\n\n const metrics: LangfuseUsageMetrics = {};\n\n if (usage.inputTokens !== undefined) {\n metrics.input = usage.inputTokens;\n\n if (usage.inputDetails?.cacheWrite !== undefined) {\n metrics.cache_write_input_tokens = usage.inputDetails.cacheWrite;\n metrics.input -= metrics.cache_write_input_tokens;\n }\n }\n\n if (usage.inputDetails?.cacheRead !== undefined) {\n metrics.cache_read_input_tokens = usage.inputDetails.cacheRead;\n }\n\n if (usage.outputTokens !== undefined) {\n metrics.output = usage.outputTokens;\n }\n\n if (usage.outputDetails?.reasoning !== undefined) {\n metrics.reasoning = usage.outputDetails.reasoning;\n }\n\n if (metrics.input && metrics.output) {\n metrics.total = metrics.input + metrics.output;\n if (metrics.cache_write_input_tokens) {\n metrics.total += metrics.cache_write_input_tokens;\n }\n }\n\n return metrics;\n}\n\nexport class LangfuseExporter extends BaseExporter {\n name = 'langfuse';\n private client: Langfuse;\n private realtime: boolean;\n private traceMap = new Map<string, TraceData>();\n\n constructor(config: LangfuseExporterConfig) {\n super(config);\n\n this.realtime = config.realtime ?? false;\n\n if (!config.publicKey || !config.secretKey) {\n this.setDisabled(\n `Missing required credentials (publicKey: ${!!config.publicKey}, secretKey: ${!!config.secretKey})`,\n );\n // Create a no-op client to prevent runtime errors\n this.client = null as any;\n return;\n }\n\n this.client = new Langfuse({\n publicKey: config.publicKey,\n secretKey: config.secretKey,\n baseUrl: config.baseUrl,\n ...config.options,\n });\n }\n\n protected async _exportTracingEvent(event: TracingEvent): Promise<void> {\n if (event.exportedSpan.isEvent) {\n await this.handleEventSpan(event.exportedSpan);\n return;\n }\n\n switch (event.type) {\n case 'span_started':\n await this.handleSpanStarted(event.exportedSpan);\n break;\n case 'span_updated':\n await this.handleSpanUpdateOrEnd(event.exportedSpan, false);\n break;\n case 'span_ended':\n await this.handleSpanUpdateOrEnd(event.exportedSpan, true);\n break;\n }\n\n // Flush immediately in realtime mode for instant visibility\n if (this.realtime) {\n await this.client.flushAsync();\n }\n }\n\n private async handleSpanStarted(span: AnyExportedSpan): Promise<void> {\n if (span.isRootSpan) {\n this.initTrace(span);\n }\n const method = 'handleSpanStarted';\n\n const traceData = this.getTraceData({ span, method });\n if (!traceData) {\n return;\n }\n\n // Store span metadata for prompt inheritance lookup (for non-root spans)\n if (!span.isRootSpan) {\n const langfuseData = span.metadata?.langfuse as { prompt?: LangfusePromptData } | undefined;\n traceData.spanMetadata.set(span.id, {\n parentSpanId: span.parentSpanId,\n langfusePrompt: langfuseData?.prompt,\n });\n }\n\n const langfuseParent = this.getLangfuseParent({ traceData, span, method });\n if (!langfuseParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span, true, traceData);\n\n const langfuseSpan =\n span.type === SpanType.MODEL_GENERATION ? langfuseParent.generation(payload) : langfuseParent.span(payload);\n\n traceData.spans.set(span.id, langfuseSpan);\n traceData.activeSpans.add(span.id); // Track as active\n }\n\n private async handleSpanUpdateOrEnd(span: AnyExportedSpan, isEnd: boolean): Promise<void> {\n const method = isEnd ? 'handleSpanEnd' : 'handleSpanUpdate';\n\n const traceData = this.getTraceData({ span, method });\n if (!traceData) {\n return;\n }\n\n const langfuseSpan = traceData.spans.get(span.id);\n if (!langfuseSpan) {\n // For event spans that only send SPAN_ENDED, we might not have the span yet\n if (isEnd && span.isEvent) {\n // Just make sure it's not in active spans\n traceData.activeSpans.delete(span.id);\n if (traceData.activeSpans.size === 0) {\n this.traceMap.delete(span.traceId);\n }\n return;\n }\n\n this.logger.warn('Langfuse exporter: No Langfuse span found for span update/end', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n return;\n }\n\n // use update for both update & end, so that we can use the\n // end time we set when ending the span.\n langfuseSpan.update(this.buildSpanPayload(span, false, traceData));\n\n if (isEnd) {\n // Remove from active spans\n traceData.activeSpans.delete(span.id);\n\n if (span.isRootSpan) {\n traceData.trace.update({ output: span.output });\n }\n\n // Only clean up the trace when ALL spans have ended\n if (traceData.activeSpans.size === 0) {\n this.traceMap.delete(span.traceId);\n }\n }\n }\n\n private async handleEventSpan(span: AnyExportedSpan): Promise<void> {\n if (span.isRootSpan) {\n this.logger.debug('Langfuse exporter: Creating trace', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n method: 'handleEventSpan',\n });\n this.initTrace(span);\n }\n const method = 'handleEventSpan';\n\n const traceData = this.getTraceData({ span, method });\n if (!traceData) {\n return;\n }\n\n const langfuseParent = this.getLangfuseParent({ traceData, span, method });\n if (!langfuseParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span, true, traceData);\n\n const langfuseEvent = langfuseParent.event(payload);\n\n traceData.events.set(span.id, langfuseEvent);\n\n // Event spans are typically immediately ended, but let's track them properly\n if (!span.endTime) {\n traceData.activeSpans.add(span.id);\n }\n }\n\n private initTrace(span: AnyExportedSpan): void {\n // Check if trace already exists in our local traceMap\n // This allows multiple root spans (e.g., from multiple agent.stream calls)\n // to be grouped under the same Langfuse trace\n if (this.traceMap.has(span.traceId)) {\n this.logger.debug('Langfuse exporter: Reusing existing trace from local map', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n });\n return; // Reuse existing trace\n }\n\n // Note: If the traceId already exists in Langfuse (e.g., from a previous server instance\n // or session), the Langfuse SDK handles this gracefully. Calling client.trace() with\n // an existing ID is idempotent - it will update/continue the existing trace rather than\n // failing or creating a duplicate. This is by design for distributed tracing scenarios.\n // See: https://langfuse.com/docs/tracing-features/trace-ids\n const trace = this.client.trace(this.buildTracePayload(span));\n\n // Extract langfuse prompt data from root span\n const langfuseData = span.metadata?.langfuse as { prompt?: LangfusePromptData } | undefined;\n const spanMetadata = new Map<string, SpanMetadata>();\n\n // Store root span metadata for prompt inheritance\n spanMetadata.set(span.id, {\n parentSpanId: undefined,\n langfusePrompt: langfuseData?.prompt,\n });\n\n this.traceMap.set(span.traceId, {\n trace,\n spans: new Map(),\n spanMetadata,\n events: new Map(),\n activeSpans: new Set(),\n rootSpanId: span.id,\n });\n }\n\n private getTraceData(options: { span: AnyExportedSpan; method: string }): TraceData | undefined {\n const { span, method } = options;\n\n if (this.traceMap.has(span.traceId)) {\n return this.traceMap.get(span.traceId);\n }\n\n this.logger.warn('Langfuse exporter: No trace data found for span', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n }\n\n private getLangfuseParent(options: {\n traceData: TraceData;\n span: AnyExportedSpan;\n method: string;\n }): LangfuseParent | undefined {\n const { traceData, span, method } = options;\n\n const parentId = span.parentSpanId;\n if (!parentId) {\n return traceData.trace;\n }\n if (traceData.spans.has(parentId)) {\n return traceData.spans.get(parentId);\n }\n if (traceData.events.has(parentId)) {\n return traceData.events.get(parentId);\n }\n this.logger.warn('Langfuse exporter: No parent data found for span', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n }\n\n private buildTracePayload(span: AnyExportedSpan): Record<string, any> {\n const payload: Record<string, any> = {\n id: span.traceId,\n name: span.name,\n };\n\n const { userId, sessionId, ...remainingMetadata } = span.metadata ?? {};\n\n if (userId) payload.userId = userId;\n if (sessionId) payload.sessionId = sessionId;\n if (span.input) payload.input = span.input;\n // Include tags if present (only for root spans, which is always the case here)\n if (span.tags?.length) payload.tags = span.tags;\n\n payload.metadata = {\n spanType: span.type,\n ...span.attributes,\n ...remainingMetadata,\n };\n\n return payload;\n }\n\n /**\n * Look up the Langfuse prompt from the closest parent span that has one.\n * This enables prompt inheritance for MODEL_GENERATION spans when the prompt\n * is set on a parent span (e.g., AGENT_RUN) rather than directly on the generation.\n */\n private findParentLangfusePrompt(traceData: TraceData, span: AnyExportedSpan): LangfusePromptData | undefined {\n let currentSpanId = span.parentSpanId;\n\n while (currentSpanId) {\n const parentMetadata = traceData.spanMetadata.get(currentSpanId);\n if (parentMetadata?.langfusePrompt) {\n return parentMetadata.langfusePrompt;\n }\n currentSpanId = parentMetadata?.parentSpanId;\n }\n\n return undefined;\n }\n\n private buildSpanPayload(span: AnyExportedSpan, isCreate: boolean, traceData?: TraceData): Record<string, any> {\n const payload: Record<string, any> = {};\n\n if (isCreate) {\n payload.id = span.id;\n payload.name = span.name;\n payload.startTime = span.startTime;\n if (span.input !== undefined) payload.input = span.input;\n }\n\n if (span.output !== undefined) payload.output = span.output;\n if (span.endTime !== undefined) payload.endTime = span.endTime;\n\n const attributes = (span.attributes ?? {}) as Record<string, any>;\n\n // For MODEL_GENERATION spans without langfuse metadata, look up the closest\n // parent span that has langfuse prompt data. This enables prompt linking when:\n // - A workflow calls multiple agents, each with different prompts\n // - Nested agents have different prompts\n // - The prompt is set on AGENT_RUN but MODEL_GENERATION inherits it\n const resolvedTraceData = traceData ?? this.traceMap.get(span.traceId);\n let inheritedLangfusePrompt: LangfusePromptData | undefined;\n\n if (span.type === SpanType.MODEL_GENERATION && !span.metadata?.langfuse && resolvedTraceData) {\n inheritedLangfusePrompt = this.findParentLangfusePrompt(resolvedTraceData, span);\n }\n\n const metadata: Record<string, any> = {\n ...span.metadata,\n ...(inheritedLangfusePrompt ? { langfuse: { prompt: inheritedLangfusePrompt } } : {}),\n };\n\n // Strip special fields from metadata if used in top-level keys\n const attributesToOmit: string[] = [];\n const metadataToOmit: string[] = [];\n\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = attributes as ModelGenerationAttributes;\n\n if (modelAttr.model !== undefined) {\n payload.model = modelAttr.model;\n attributesToOmit.push('model');\n }\n\n if (modelAttr.usage !== undefined) {\n payload.usageDetails = formatUsageMetrics(modelAttr.usage);\n attributesToOmit.push('usage');\n }\n\n if (modelAttr.parameters !== undefined) {\n payload.modelParameters = modelAttr.parameters;\n attributesToOmit.push('parameters');\n }\n\n // Handle Langfuse prompt linking\n // Users can set metadata.langfuse.prompt to link generations to Langfuse Prompt Management\n // Supported formats:\n // - { id } - link by prompt UUID alone\n // - { name, version } - link by name and version\n // - { name, version, id } - link with all fields\n const langfuseData = metadata.langfuse as\n | { prompt?: { name?: string; version?: number; id?: string } }\n | undefined;\n const promptData = langfuseData?.prompt;\n const hasNameAndVersion = promptData?.name !== undefined && promptData?.version !== undefined;\n const hasId = promptData?.id !== undefined;\n\n if (hasNameAndVersion || hasId) {\n payload.prompt = {};\n\n if (promptData?.name !== undefined) payload.prompt.name = promptData.name;\n if (promptData?.version !== undefined) payload.prompt.version = promptData.version;\n if (promptData?.id !== undefined) payload.prompt.id = promptData.id;\n\n metadataToOmit.push('langfuse');\n }\n\n // completionStartTime is used by Langfuse to calculate time-to-first-token (TTFT)\n if (modelAttr.completionStartTime !== undefined) {\n payload.completionStartTime = modelAttr.completionStartTime;\n attributesToOmit.push('completionStartTime');\n }\n }\n\n payload.metadata = {\n spanType: span.type,\n ...omitKeys(attributes, attributesToOmit),\n ...omitKeys(metadata, metadataToOmit),\n };\n\n if (span.errorInfo) {\n payload.level = 'ERROR';\n payload.statusMessage = span.errorInfo.message;\n }\n\n return payload;\n }\n\n async addScoreToTrace({\n traceId,\n spanId,\n score,\n reason,\n scorerName,\n metadata,\n }: {\n traceId: string;\n spanId?: string;\n score: number;\n reason?: string;\n scorerName: string;\n metadata?: Record<string, any>;\n }): Promise<void> {\n if (!this.client) return;\n\n try {\n await this.client.score({\n id: `${traceId}-${scorerName}`,\n traceId,\n observationId: spanId,\n name: scorerName,\n value: score,\n ...(metadata?.sessionId ? { sessionId: metadata.sessionId } : {}),\n metadata: { ...(reason ? { reason } : {}) },\n dataType: 'NUMERIC',\n });\n } catch (error) {\n this.logger.error('Langfuse exporter: Error adding score to trace', {\n error,\n traceId,\n spanId,\n scorerName,\n });\n }\n }\n\n async shutdown(): Promise<void> {\n if (this.client) {\n await this.client.shutdownAsync();\n }\n this.traceMap.clear();\n await super.shutdown();\n }\n}\n","/**\n * Langfuse Tracing Options Helpers\n *\n * These helpers integrate with the `buildTracingOptions` pattern from\n * `@mastra/observability` to add Langfuse-specific tracing features.\n *\n * @example\n * ```typescript\n * import { buildTracingOptions } from '@mastra/observability';\n * import { withLangfusePrompt } from '@mastra/langfuse';\n *\n * const prompt = await langfuse.getPrompt('my-prompt');\n *\n * const agent = new Agent({\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(withLangfusePrompt(prompt)),\n * },\n * });\n * ```\n */\n\nimport type { TracingOptionsUpdater } from '@mastra/observability';\n\n/**\n * Langfuse prompt input - accepts either a Langfuse SDK prompt object\n * or manual fields.\n */\nexport interface LangfusePromptInput {\n /** Prompt name */\n name?: string;\n /** Prompt version */\n version?: number;\n /** Prompt UUID */\n id?: string;\n}\n\n/**\n * Adds Langfuse prompt metadata to the tracing options\n * to enable Langfuse Prompt Tracing.\n *\n * The metadata is added under `metadata.langfuse.prompt` and includes:\n * - `name` - Prompt name\n * - `version` - Prompt version\n * - `id` - Prompt UUID\n *\n * All fields are deeply merged with any existing metadata.\n *\n * @param prompt - A Langfuse prompt object (from `langfuse.getPrompt()`) or manual fields\n * @returns A TracingOptionsUpdater function for use with `buildTracingOptions`\n *\n * @example\n * ```typescript\n * import { buildTracingOptions } from '@mastra/observability';\n * import { withLangfusePrompt } from '@mastra/langfuse';\n * import { Langfuse } from 'langfuse';\n *\n * const langfuse = new Langfuse();\n * const prompt = await langfuse.getPrompt('customer-support');\n *\n * // Use with buildTracingOptions\n * const tracingOptions = buildTracingOptions(\n * withLangfusePrompt(prompt),\n * );\n *\n * // Or directly in agent config\n * const agent = new Agent({\n * name: 'support-agent',\n * instructions: prompt.prompt,\n * model: openai('gpt-4o'),\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(withLangfusePrompt(prompt)),\n * },\n * });\n *\n * // Manual fields also work\n * const tracingOptions = buildTracingOptions(\n * withLangfusePrompt({ name: 'my-prompt', version: 1 }),\n * );\n * ```\n */\nexport function withLangfusePrompt(prompt: LangfusePromptInput): TracingOptionsUpdater {\n return opts => ({\n ...opts,\n metadata: {\n ...opts.metadata,\n langfuse: {\n ...(opts.metadata?.langfuse as Record<string, unknown>),\n prompt: {\n ...(prompt.name !== undefined && { name: prompt.name }),\n ...(prompt.version !== undefined && { version: prompt.version }),\n ...(prompt.id !== undefined && { id: prompt.id }),\n },\n },\n },\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/metrics.ts","../src/tracing.ts","../src/helpers.ts"],"names":["TrackingExporter","Langfuse","SpanType","omitKeys"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { UsageStats } from '@mastra/core/observability';\n\n/**\n * Token usage format compatible with Langfuse.\n */\nexport interface LangfuseUsageMetrics {\n input?: number;\n output?: number;\n total?: number;\n reasoning?: number;\n cache_read_input_tokens?: number;\n cache_write_input_tokens?: number;\n}\n\n/**\n * Formats UsageStats to Langfuse's expected format.\n */\nexport function formatUsageMetrics(usage?: UsageStats): LangfuseUsageMetrics {\n if (!usage) return {};\n\n const metrics: LangfuseUsageMetrics = {};\n\n if (usage.inputTokens !== undefined) {\n // Start with total input tokens (which includes cached tokens from usage.ts)\n metrics.input = usage.inputTokens;\n\n // Langfuse expects 'input' to be NON-cached tokens only.\n // Subtract cache tokens to get the actual non-cached input count.\n // See: https://langfuse.com/docs/observability/features/token-and-cost-tracking\n if (usage.inputDetails?.cacheRead !== undefined) {\n metrics.cache_read_input_tokens = usage.inputDetails.cacheRead;\n metrics.input -= metrics.cache_read_input_tokens;\n }\n\n if (usage.inputDetails?.cacheWrite !== undefined) {\n metrics.cache_write_input_tokens = usage.inputDetails.cacheWrite;\n metrics.input -= metrics.cache_write_input_tokens;\n }\n\n // Defensive clamp: ensure input tokens is never negative\n if (metrics.input < 0) metrics.input = 0;\n }\n\n if (usage.outputTokens !== undefined) {\n metrics.output = usage.outputTokens;\n }\n\n if (usage.outputDetails?.reasoning !== undefined) {\n metrics.reasoning = usage.outputDetails.reasoning;\n }\n\n // Calculate total: input + cache_read + cache_write + output\n // Use explicit null checks to handle zero values correctly\n if (metrics.input != null && metrics.output != null) {\n metrics.total = metrics.input + metrics.output;\n if (metrics.cache_read_input_tokens != null) {\n metrics.total += metrics.cache_read_input_tokens;\n }\n if (metrics.cache_write_input_tokens != null) {\n metrics.total += metrics.cache_write_input_tokens;\n }\n }\n\n return metrics;\n}\n","/**\n * Langfuse Exporter for Mastra Observability\n *\n * This exporter sends observability data to Langfuse.\n * Root spans start traces in Langfuse.\n * MODEL_GENERATION spans become Langfuse generations, all others become spans.\n */\n\nimport type { AnyExportedSpan, ModelGenerationAttributes, SpanErrorInfo } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { TrackingExporter } from '@mastra/observability';\nimport type { TrackingExporterConfig, TraceData } from '@mastra/observability';\nimport { Langfuse } from 'langfuse';\nimport type { LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient, LangfuseEventClient } from 'langfuse';\nimport { formatUsageMetrics } from './metrics';\n\nexport interface LangfuseExporterConfig extends TrackingExporterConfig {\n /** Langfuse API key */\n publicKey?: string;\n /** Langfuse secret key */\n secretKey?: string;\n /** Langfuse host URL */\n baseUrl?: string;\n /** Enable realtime mode - flushes after each event for immediate visibility */\n realtime?: boolean;\n /** Additional options to pass to the Langfuse client */\n options?: any;\n}\n\ntype LangfusePromptData = { name?: string; version?: number; id?: string };\n\n/**\n * With Langfuse, data from the root span is stored in both the Root and the\n * first span.\n */\n\ntype LangfuseRoot = LangfuseTraceClient;\ntype LangfuseSpan = LangfuseSpanClient | LangfuseGenerationClient;\ntype LangfuseEvent = LangfuseEventClient;\ntype LangfuseMetadata = { prompt?: LangfusePromptData };\ntype LangfuseTraceData = TraceData<LangfuseRoot, LangfuseSpan, LangfuseEvent, LangfuseMetadata>;\n\nexport class LangfuseExporter extends TrackingExporter<\n LangfuseRoot,\n LangfuseSpan,\n LangfuseEvent,\n LangfuseMetadata,\n LangfuseExporterConfig\n> {\n name = 'langfuse';\n #client: Langfuse | undefined;\n #realtime: boolean;\n\n constructor(config: LangfuseExporterConfig = {}) {\n // Resolve env vars BEFORE calling super (config is readonly in base class)\n const publicKey = config.publicKey ?? process.env.LANGFUSE_PUBLIC_KEY;\n const secretKey = config.secretKey ?? process.env.LANGFUSE_SECRET_KEY;\n const baseUrl = config.baseUrl ?? process.env.LANGFUSE_BASE_URL;\n\n super({\n ...config,\n publicKey,\n secretKey,\n baseUrl,\n });\n\n this.#realtime = config.realtime ?? false;\n\n if (!publicKey || !secretKey) {\n const publicKeySource = config.publicKey\n ? 'from config'\n : process.env.LANGFUSE_PUBLIC_KEY\n ? 'from env'\n : 'missing';\n const secretKeySource = config.secretKey\n ? 'from config'\n : process.env.LANGFUSE_SECRET_KEY\n ? 'from env'\n : 'missing';\n this.setDisabled(\n `Missing required credentials (publicKey: ${publicKeySource}, secretKey: ${secretKeySource}). ` +\n `Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY environment variables or pass them in config.`,\n );\n return;\n }\n\n this.#client = new Langfuse({\n publicKey,\n secretKey,\n baseUrl,\n ...config.options,\n });\n }\n\n protected override async _postExportTracingEvent(): Promise<void> {\n // Flush immediately in realtime mode for instant visibility\n if (this.#realtime) {\n await this.#client?.flushAsync();\n }\n }\n\n protected override async _buildRoot(args: {\n span: AnyExportedSpan;\n traceData: LangfuseTraceData;\n }): Promise<LangfuseTraceClient | undefined> {\n const { span } = args;\n // Note: If the traceId already exists in Langfuse (e.g., from a previous server instance\n // or session), the Langfuse SDK handles this gracefully. Calling client.trace() with\n // an existing ID is idempotent - it will update/continue the existing trace rather than\n // failing or creating a duplicate. This is by design for distributed tracing scenarios.\n // See: https://langfuse.com/docs/tracing-features/trace-ids\n return this.#client?.trace(this.buildTracePayload(span));\n }\n\n protected override async _buildEvent(args: {\n span: AnyExportedSpan;\n traceData: LangfuseTraceData;\n }): Promise<LangfuseEvent | undefined> {\n const { span, traceData } = args;\n const langfuseParent = traceData.getParentOrRoot({ span });\n if (!langfuseParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span, true, traceData);\n return langfuseParent.event(payload);\n }\n\n protected override async _buildSpan(args: {\n span: AnyExportedSpan;\n traceData: LangfuseTraceData;\n }): Promise<LangfuseSpan | undefined> {\n const { span, traceData } = args;\n const langfuseParent = traceData.getParentOrRoot({ span });\n if (!langfuseParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span, true, traceData);\n\n const langfuseSpan =\n span.type === SpanType.MODEL_GENERATION ? langfuseParent.generation(payload) : langfuseParent.span(payload);\n\n this.logger.debug(`${this.name}: built span`, {\n traceId: span.traceId,\n spanId: payload.id,\n method: '_buildSpan',\n });\n\n return langfuseSpan;\n }\n\n protected override async _updateSpan(args: { span: AnyExportedSpan; traceData: LangfuseTraceData }): Promise<void> {\n const { span, traceData } = args;\n const langfuseSpan = traceData.getSpan({ spanId: span.id });\n if (langfuseSpan) {\n this.logger.debug(`${this.name}: found span for update`, {\n traceId: span.traceId,\n spanId: langfuseSpan.id,\n method: '_updateSpan',\n });\n\n const updatePayload = this.buildSpanPayload(span, false, traceData);\n\n // use update for both update & end, so that we can use the\n // end time we set when ending the span.\n langfuseSpan.update(updatePayload);\n }\n }\n\n protected override async _finishSpan(args: { span: AnyExportedSpan; traceData: LangfuseTraceData }): Promise<void> {\n const { span, traceData } = args;\n const langfuseSpan = traceData.getSpan({ spanId: span.id });\n // use update for both update & end, so that we can use the\n // end time we set when ending the span.\n langfuseSpan?.update(this.buildSpanPayload(span, false, traceData));\n\n if (span.isRootSpan) {\n const langfuseRoot = traceData.getRoot();\n langfuseRoot?.update({ output: span.output });\n }\n }\n\n protected override async _abortSpan(args: { span: LangfuseSpan; reason: SpanErrorInfo }): Promise<void> {\n const { span, reason } = args;\n span.end({\n level: 'ERROR',\n statusMessage: reason.message,\n });\n }\n\n private buildTracePayload(span: AnyExportedSpan): Record<string, any> {\n const payload: Record<string, any> = {\n id: span.traceId,\n name: span.name,\n };\n\n const { userId, sessionId, ...remainingMetadata } = span.metadata ?? {};\n\n if (userId) payload.userId = userId;\n if (sessionId) payload.sessionId = sessionId;\n if (span.input) payload.input = span.input;\n // Include tags if present (only for root spans, which is always the case here)\n if (span.tags?.length) payload.tags = span.tags;\n\n payload.metadata = {\n spanType: span.type,\n ...span.attributes,\n ...remainingMetadata,\n };\n\n return payload;\n }\n\n /**\n * Look up the Langfuse prompt from the closest span that has one.\n * This enables prompt inheritance for MODEL_GENERATION spans when the prompt\n * is set on a parent span (e.g., AGENT_RUN) rather than directly on the generation.\n * This enables prompt linking when:\n * - A workflow calls multiple agents, each with different prompts\n * - Nested agents have different prompts\n * - The prompt is set on AGENT_RUN but MODEL_GENERATION inherits it\n */\n private findLangfusePrompt(traceData: LangfuseTraceData, span: AnyExportedSpan): LangfusePromptData | undefined {\n let currentSpanId: string | undefined = span.id;\n\n while (currentSpanId) {\n const providerMetadata = traceData.getMetadata({ spanId: currentSpanId });\n\n if (providerMetadata?.prompt) {\n this.logger.debug(`${this.name}: found prompt in provider metadata`, {\n traceId: span.traceId,\n spanId: span.id,\n prompt: providerMetadata?.prompt,\n });\n return providerMetadata.prompt;\n }\n currentSpanId = traceData.getParentId({ spanId: currentSpanId });\n }\n\n return undefined;\n }\n\n private buildSpanPayload(\n span: AnyExportedSpan,\n isCreate: boolean,\n traceData: LangfuseTraceData,\n ): Record<string, any> {\n const payload: Record<string, any> = {};\n\n if (isCreate) {\n payload.id = span.id;\n payload.name = span.name;\n payload.startTime = span.startTime;\n }\n\n if (span.input !== undefined) payload.input = span.input;\n if (span.output !== undefined) payload.output = span.output;\n if (span.endTime !== undefined) payload.endTime = span.endTime;\n\n const attributes = (span.attributes ?? {}) as Record<string, any>;\n\n const metadata: Record<string, any> = {\n ...span.metadata,\n };\n\n // Strip special fields from metadata if used in top-level keys\n const attributesToOmit: string[] = [];\n const metadataToOmit: string[] = [];\n\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = attributes as ModelGenerationAttributes;\n\n if (modelAttr.model !== undefined) {\n payload.model = modelAttr.model;\n attributesToOmit.push('model');\n }\n\n if (modelAttr.usage !== undefined) {\n payload.usageDetails = formatUsageMetrics(modelAttr.usage);\n attributesToOmit.push('usage');\n }\n\n if (modelAttr.parameters !== undefined) {\n payload.modelParameters = modelAttr.parameters;\n attributesToOmit.push('parameters');\n }\n\n // Users can set metadata.langfuse.prompt to link generations to Langfuse Prompt Management\n // Supported formats:\n // - { id } - link by prompt UUID alone\n // - { name, version } - link by name and version\n // - { name, version, id } - link with all fields\n const promptData = this.findLangfusePrompt(traceData, span);\n const hasNameAndVersion = promptData?.name !== undefined && promptData?.version !== undefined;\n const hasId = promptData?.id !== undefined;\n\n if (hasNameAndVersion || hasId) {\n payload.prompt = {};\n\n if (promptData?.name !== undefined) payload.prompt.name = promptData.name;\n if (promptData?.version !== undefined) payload.prompt.version = promptData.version;\n if (promptData?.id !== undefined) payload.prompt.id = promptData.id;\n\n metadataToOmit.push('langfuse');\n }\n\n // completionStartTime is used by Langfuse to calculate time-to-first-token (TTFT)\n if (modelAttr.completionStartTime !== undefined) {\n payload.completionStartTime = modelAttr.completionStartTime;\n attributesToOmit.push('completionStartTime');\n }\n }\n\n payload.metadata = {\n spanType: span.type,\n ...omitKeys(attributes, attributesToOmit),\n ...omitKeys(metadata, metadataToOmit),\n };\n\n if (span.errorInfo) {\n payload.level = 'ERROR';\n payload.statusMessage = span.errorInfo.message;\n }\n\n return payload;\n }\n\n async addScoreToTrace({\n traceId,\n spanId,\n score,\n reason,\n scorerName,\n metadata,\n }: {\n traceId: string;\n spanId?: string;\n score: number;\n reason?: string;\n scorerName: string;\n metadata?: Record<string, any>;\n }): Promise<void> {\n if (!this.#client) return;\n\n try {\n await this.#client.score({\n id: `${traceId}-${scorerName}`,\n traceId,\n observationId: spanId,\n name: scorerName,\n value: score,\n ...(metadata?.sessionId ? { sessionId: metadata.sessionId } : {}),\n metadata: { ...(reason ? { reason } : {}) },\n dataType: 'NUMERIC',\n });\n } catch (error) {\n this.logger.error('Langfuse exporter: Error adding score to trace', {\n error,\n traceId,\n spanId,\n scorerName,\n });\n }\n }\n\n /**\n * Force flush any buffered data to Langfuse without shutting down.\n */\n protected override async _flush(): Promise<void> {\n if (this.#client) {\n await this.#client.flushAsync();\n }\n }\n\n override async _postShutdown(): Promise<void> {\n if (this.#client) {\n await this.#client.shutdownAsync();\n }\n }\n}\n","/**\n * Langfuse Tracing Options Helpers\n *\n * These helpers integrate with the `buildTracingOptions` pattern from\n * `@mastra/observability` to add Langfuse-specific tracing features.\n *\n * @example\n * ```typescript\n * import { buildTracingOptions } from '@mastra/observability';\n * import { withLangfusePrompt } from '@mastra/langfuse';\n *\n * const prompt = await langfuse.getPrompt('my-prompt');\n *\n * const agent = new Agent({\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(withLangfusePrompt(prompt)),\n * },\n * });\n * ```\n */\n\nimport type { TracingOptionsUpdater } from '@mastra/observability';\n\n/**\n * Langfuse prompt input - accepts either a Langfuse SDK prompt object\n * or manual fields.\n */\nexport interface LangfusePromptInput {\n /** Prompt name */\n name?: string;\n /** Prompt version */\n version?: number;\n /** Prompt UUID */\n id?: string;\n}\n\n/**\n * Adds Langfuse prompt metadata to the tracing options\n * to enable Langfuse Prompt Tracing.\n *\n * The metadata is added under `metadata.langfuse.prompt` and includes:\n * - `name` - Prompt name\n * - `version` - Prompt version\n * - `id` - Prompt UUID\n *\n * All fields are deeply merged with any existing metadata.\n *\n * @param prompt - A Langfuse prompt object (from `langfuse.getPrompt()`) or manual fields\n * @returns A TracingOptionsUpdater function for use with `buildTracingOptions`\n *\n * @example\n * ```typescript\n * import { buildTracingOptions } from '@mastra/observability';\n * import { withLangfusePrompt } from '@mastra/langfuse';\n * import { Langfuse } from 'langfuse';\n *\n * const langfuse = new Langfuse();\n * const prompt = await langfuse.getPrompt('customer-support');\n *\n * // Use with buildTracingOptions\n * const tracingOptions = buildTracingOptions(\n * withLangfusePrompt(prompt),\n * );\n *\n * // Or directly in agent config\n * const agent = new Agent({\n * name: 'support-agent',\n * instructions: prompt.prompt,\n * model: openai('gpt-4o'),\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(withLangfusePrompt(prompt)),\n * },\n * });\n *\n * // Manual fields also work\n * const tracingOptions = buildTracingOptions(\n * withLangfusePrompt({ name: 'my-prompt', version: 1 }),\n * );\n * ```\n */\nexport function withLangfusePrompt(prompt: LangfusePromptInput): TracingOptionsUpdater {\n return opts => ({\n ...opts,\n metadata: {\n ...opts.metadata,\n langfuse: {\n ...(opts.metadata?.langfuse as Record<string, unknown>),\n prompt: {\n ...(prompt.name !== undefined && { name: prompt.name }),\n ...(prompt.version !== undefined && { version: prompt.version }),\n ...(prompt.id !== undefined && { id: prompt.id }),\n },\n },\n },\n });\n}\n"]}