@mastra/langfuse 1.0.0-beta.2 → 1.0.0-beta.4

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,39 @@
1
1
  # @mastra/langfuse
2
2
 
3
+ ## 1.0.0-beta.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixed CachedToken tracking in all Observability Exporters. Also fixed TimeToFirstToken in Langfuse, Braintrust, PostHog exporters. Fixed trace formatting in Posthog Exporter. ([#11029](https://github.com/mastra-ai/mastra/pull/11029))
8
+
9
+ - Updated dependencies [[`edb07e4`](https://github.com/mastra-ai/mastra/commit/edb07e49283e0c28bd094a60e03439bf6ecf0221), [`b7e17d3`](https://github.com/mastra-ai/mastra/commit/b7e17d3f5390bb5a71efc112204413656fcdc18d), [`261473a`](https://github.com/mastra-ai/mastra/commit/261473ac637e633064a22076671e2e02b002214d), [`5d7000f`](https://github.com/mastra-ai/mastra/commit/5d7000f757cd65ea9dc5b05e662fd83dfd44e932), [`4f0331a`](https://github.com/mastra-ai/mastra/commit/4f0331a79bf6eb5ee598a5086e55de4b5a0ada03), [`8a000da`](https://github.com/mastra-ai/mastra/commit/8a000da0c09c679a2312f6b3aa05b2ca78ca7393)]:
10
+ - @mastra/core@1.0.0-beta.10
11
+ - @mastra/observability@1.0.0-beta.4
12
+
13
+ ## 1.0.0-beta.3
14
+
15
+ ### Patch Changes
16
+
17
+ - Add time-to-first-token (TTFT) support for Langfuse integration ([#10781](https://github.com/mastra-ai/mastra/pull/10781))
18
+
19
+ Adds `completionStartTime` to model generation spans, which Langfuse uses to calculate TTFT metrics. The timestamp is automatically captured when the first content chunk arrives during streaming.
20
+
21
+ ```typescript
22
+ // completionStartTime is now automatically captured and sent to Langfuse
23
+ // enabling TTFT metrics in your Langfuse dashboard
24
+ const result = await agent.stream('Hello');
25
+ ```
26
+
27
+ - Fix Langfuse exporter to reuse existing traces when multiple root spans share the same traceId. This resolves an issue where multiple agent.stream() calls with client-side tools would create separate traces in Langfuse instead of grouping them under a single trace. The exporter now checks if a trace already exists before creating a new one, allowing proper trace consolidation for conversations with multiple agent interactions. ([#10838](https://github.com/mastra-ai/mastra/pull/10838))
28
+
29
+ Fixes #8830
30
+
31
+ - link langfuse prompts and helper functions ([#10738](https://github.com/mastra-ai/mastra/pull/10738))
32
+
33
+ - Updated dependencies [[`6c59a40`](https://github.com/mastra-ai/mastra/commit/6c59a40e0ad160467bd13d63a8a287028d75b02d), [`3076c67`](https://github.com/mastra-ai/mastra/commit/3076c6778b18988ae7d5c4c5c466366974b2d63f), [`85d7ee1`](https://github.com/mastra-ai/mastra/commit/85d7ee18ff4e14d625a8a30ec6656bb49804989b), [`c6c1092`](https://github.com/mastra-ai/mastra/commit/c6c1092f8fbf76109303f69e000e96fd1960c4ce), [`81dc110`](https://github.com/mastra-ai/mastra/commit/81dc11008d147cf5bdc8996ead1aa61dbdebb6fc), [`7aedb74`](https://github.com/mastra-ai/mastra/commit/7aedb74883adf66af38e270e4068fd42e7a37036), [`8f02d80`](https://github.com/mastra-ai/mastra/commit/8f02d800777397e4b45d7f1ad041988a8b0c6630), [`d7aad50`](https://github.com/mastra-ai/mastra/commit/d7aad501ce61646b76b4b511e558ac4eea9884d0), [`ce0a73a`](https://github.com/mastra-ai/mastra/commit/ce0a73abeaa75b10ca38f9e40a255a645d50ebfb), [`a02e542`](https://github.com/mastra-ai/mastra/commit/a02e542d23179bad250b044b17ff023caa61739f), [`a372c64`](https://github.com/mastra-ai/mastra/commit/a372c640ad1fd12e8f0613cebdc682fc156b4d95), [`8846867`](https://github.com/mastra-ai/mastra/commit/8846867ffa9a3746767618e314bebac08eb77d87), [`0bada2f`](https://github.com/mastra-ai/mastra/commit/0bada2f2c1234932cf30c1c47a719ffb64b801c5), [`42a42cf`](https://github.com/mastra-ai/mastra/commit/42a42cf3132b9786feecbb8c13c583dce5b0e198), [`cc60ff6`](https://github.com/mastra-ai/mastra/commit/cc60ff616541a3b0fb531a7e469bf9ae7bb90528), [`ae08bf0`](https://github.com/mastra-ai/mastra/commit/ae08bf0ebc6a4e4da992b711c4a389c32ba84cf4), [`21735a7`](https://github.com/mastra-ai/mastra/commit/21735a7ef306963554a69a89b44f06c3bcd85141), [`1d877b8`](https://github.com/mastra-ai/mastra/commit/1d877b8d7b536a251c1a7a18db7ddcf4f68d6f8b)]:
34
+ - @mastra/observability@1.0.0-beta.3
35
+ - @mastra/core@1.0.0-beta.7
36
+
3
37
  ## 1.0.0-beta.2
4
38
 
5
39
  ### Minor Changes
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Langfuse Tracing Options Helpers
3
+ *
4
+ * These helpers integrate with the `buildTracingOptions` pattern from
5
+ * `@mastra/observability` to add Langfuse-specific tracing features.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { buildTracingOptions } from '@mastra/observability';
10
+ * import { withLangfusePrompt } from '@mastra/langfuse';
11
+ *
12
+ * const prompt = await langfuse.getPrompt('my-prompt');
13
+ *
14
+ * const agent = new Agent({
15
+ * defaultGenerateOptions: {
16
+ * tracingOptions: buildTracingOptions(withLangfusePrompt(prompt)),
17
+ * },
18
+ * });
19
+ * ```
20
+ */
21
+ import type { TracingOptionsUpdater } from '@mastra/observability';
22
+ /**
23
+ * Langfuse prompt input - accepts either a Langfuse SDK prompt object
24
+ * or manual fields.
25
+ */
26
+ export interface LangfusePromptInput {
27
+ /** Prompt name */
28
+ name?: string;
29
+ /** Prompt version */
30
+ version?: number;
31
+ /** Prompt UUID */
32
+ id?: string;
33
+ }
34
+ /**
35
+ * Adds Langfuse prompt metadata to the tracing options
36
+ * to enable Langfuse Prompt Tracing.
37
+ *
38
+ * The metadata is added under `metadata.langfuse.prompt` and includes:
39
+ * - `name` - Prompt name
40
+ * - `version` - Prompt version
41
+ * - `id` - Prompt UUID
42
+ *
43
+ * All fields are deeply merged with any existing metadata.
44
+ *
45
+ * @param prompt - A Langfuse prompt object (from `langfuse.getPrompt()`) or manual fields
46
+ * @returns A TracingOptionsUpdater function for use with `buildTracingOptions`
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * import { buildTracingOptions } from '@mastra/observability';
51
+ * import { withLangfusePrompt } from '@mastra/langfuse';
52
+ * import { Langfuse } from 'langfuse';
53
+ *
54
+ * const langfuse = new Langfuse();
55
+ * const prompt = await langfuse.getPrompt('customer-support');
56
+ *
57
+ * // Use with buildTracingOptions
58
+ * const tracingOptions = buildTracingOptions(
59
+ * withLangfusePrompt(prompt),
60
+ * );
61
+ *
62
+ * // Or directly in agent config
63
+ * const agent = new Agent({
64
+ * name: 'support-agent',
65
+ * instructions: prompt.prompt,
66
+ * model: openai('gpt-4o'),
67
+ * defaultGenerateOptions: {
68
+ * tracingOptions: buildTracingOptions(withLangfusePrompt(prompt)),
69
+ * },
70
+ * });
71
+ *
72
+ * // Manual fields also work
73
+ * const tracingOptions = buildTracingOptions(
74
+ * withLangfusePrompt({ name: 'my-prompt', version: 1 }),
75
+ * );
76
+ * ```
77
+ */
78
+ export declare function withLangfusePrompt(prompt: LangfusePromptInput): TracingOptionsUpdater;
79
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAEnE;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,kBAAkB;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,mBAAmB,GAAG,qBAAqB,CAerF"}
package/dist/index.cjs CHANGED
@@ -6,6 +6,33 @@ var observability = require('@mastra/observability');
6
6
  var langfuse = require('langfuse');
7
7
 
8
8
  // src/tracing.ts
9
+ function formatUsageMetrics(usage) {
10
+ if (!usage) return {};
11
+ const metrics = {};
12
+ if (usage.inputTokens !== void 0) {
13
+ metrics.input = usage.inputTokens;
14
+ if (usage.inputDetails?.cacheWrite !== void 0) {
15
+ metrics.cache_write_input_tokens = usage.inputDetails.cacheWrite;
16
+ metrics.input -= metrics.cache_write_input_tokens;
17
+ }
18
+ }
19
+ if (usage.inputDetails?.cacheRead !== void 0) {
20
+ metrics.cache_read_input_tokens = usage.inputDetails.cacheRead;
21
+ }
22
+ if (usage.outputTokens !== void 0) {
23
+ metrics.output = usage.outputTokens;
24
+ }
25
+ if (usage.outputDetails?.reasoning !== void 0) {
26
+ metrics.reasoning = usage.outputDetails.reasoning;
27
+ }
28
+ if (metrics.input && metrics.output) {
29
+ metrics.total = metrics.input + metrics.output;
30
+ if (metrics.cache_write_input_tokens) {
31
+ metrics.total += metrics.cache_write_input_tokens;
32
+ }
33
+ }
34
+ return metrics;
35
+ }
9
36
  var LangfuseExporter = class extends observability.BaseExporter {
10
37
  name = "langfuse";
11
38
  client;
@@ -57,11 +84,18 @@ var LangfuseExporter = class extends observability.BaseExporter {
57
84
  if (!traceData) {
58
85
  return;
59
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
+ }
60
94
  const langfuseParent = this.getLangfuseParent({ traceData, span, method });
61
95
  if (!langfuseParent) {
62
96
  return;
63
97
  }
64
- const payload = this.buildSpanPayload(span, true);
98
+ const payload = this.buildSpanPayload(span, true, traceData);
65
99
  const langfuseSpan = span.type === observability$1.SpanType.MODEL_GENERATION ? langfuseParent.generation(payload) : langfuseParent.span(payload);
66
100
  traceData.spans.set(span.id, langfuseSpan);
67
101
  traceData.activeSpans.add(span.id);
@@ -92,7 +126,7 @@ var LangfuseExporter = class extends observability.BaseExporter {
92
126
  });
93
127
  return;
94
128
  }
95
- langfuseSpan.update(this.buildSpanPayload(span, false));
129
+ langfuseSpan.update(this.buildSpanPayload(span, false, traceData));
96
130
  if (isEnd) {
97
131
  traceData.activeSpans.delete(span.id);
98
132
  if (span.isRootSpan) {
@@ -122,7 +156,7 @@ var LangfuseExporter = class extends observability.BaseExporter {
122
156
  if (!langfuseParent) {
123
157
  return;
124
158
  }
125
- const payload = this.buildSpanPayload(span, true);
159
+ const payload = this.buildSpanPayload(span, true, traceData);
126
160
  const langfuseEvent = langfuseParent.event(payload);
127
161
  traceData.events.set(span.id, langfuseEvent);
128
162
  if (!span.endTime) {
@@ -130,10 +164,25 @@ var LangfuseExporter = class extends observability.BaseExporter {
130
164
  }
131
165
  }
132
166
  initTrace(span) {
167
+ if (this.traceMap.has(span.traceId)) {
168
+ this.logger.debug("Langfuse exporter: Reusing existing trace from local map", {
169
+ traceId: span.traceId,
170
+ spanId: span.id,
171
+ spanName: span.name
172
+ });
173
+ return;
174
+ }
133
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
+ });
134
182
  this.traceMap.set(span.traceId, {
135
183
  trace,
136
184
  spans: /* @__PURE__ */ new Map(),
185
+ spanMetadata,
137
186
  events: /* @__PURE__ */ new Map(),
138
187
  activeSpans: /* @__PURE__ */ new Set(),
139
188
  rootSpanId: span.id
@@ -194,48 +243,22 @@ var LangfuseExporter = class extends observability.BaseExporter {
194
243
  return payload;
195
244
  }
196
245
  /**
197
- * Normalize usage data to handle both AI SDK v4 and v5 formats.
198
- *
199
- * AI SDK v4 uses: promptTokens, completionTokens
200
- * AI SDK v5 uses: inputTokens, outputTokens
201
- *
202
- * This function normalizes to a unified format that Langfuse can consume,
203
- * prioritizing v5 format while maintaining backward compatibility.
204
- *
205
- * @param usage - Token usage data from AI SDK (v4 or v5 format)
206
- * @returns Normalized usage object, or undefined if no usage data available
246
+ * Look up the Langfuse prompt from the closest parent span that has one.
247
+ * This enables prompt inheritance for MODEL_GENERATION spans when the prompt
248
+ * is set on a parent span (e.g., AGENT_RUN) rather than directly on the generation.
207
249
  */
208
- normalizeUsage(usage) {
209
- if (!usage) return void 0;
210
- const normalized = {};
211
- const inputTokens = usage.inputTokens ?? usage.promptTokens;
212
- if (inputTokens !== void 0) {
213
- normalized.input = inputTokens;
214
- }
215
- const outputTokens = usage.outputTokens ?? usage.completionTokens;
216
- if (outputTokens !== void 0) {
217
- normalized.output = outputTokens;
218
- }
219
- if (usage.totalTokens !== void 0) {
220
- normalized.total = usage.totalTokens;
221
- } else if (normalized.input !== void 0 && normalized.output !== void 0) {
222
- normalized.total = normalized.input + normalized.output;
223
- }
224
- if (usage.reasoningTokens !== void 0) {
225
- normalized.reasoning = usage.reasoningTokens;
226
- }
227
- if (usage.cachedInputTokens !== void 0) {
228
- normalized.cachedInput = usage.cachedInputTokens;
229
- }
230
- if (usage.promptCacheHitTokens !== void 0) {
231
- normalized.promptCacheHit = usage.promptCacheHitTokens;
232
- }
233
- if (usage.promptCacheMissTokens !== void 0) {
234
- normalized.promptCacheMiss = usage.promptCacheMissTokens;
250
+ findParentLangfusePrompt(traceData, span) {
251
+ let currentSpanId = span.parentSpanId;
252
+ while (currentSpanId) {
253
+ const parentMetadata = traceData.spanMetadata.get(currentSpanId);
254
+ if (parentMetadata?.langfusePrompt) {
255
+ return parentMetadata.langfusePrompt;
256
+ }
257
+ currentSpanId = parentMetadata?.parentSpanId;
235
258
  }
236
- return Object.keys(normalized).length > 0 ? normalized : void 0;
259
+ return void 0;
237
260
  }
238
- buildSpanPayload(span, isCreate) {
261
+ buildSpanPayload(span, isCreate, traceData) {
239
262
  const payload = {};
240
263
  if (isCreate) {
241
264
  payload.id = span.id;
@@ -246,7 +269,17 @@ var LangfuseExporter = class extends observability.BaseExporter {
246
269
  if (span.output !== void 0) payload.output = span.output;
247
270
  if (span.endTime !== void 0) payload.endTime = span.endTime;
248
271
  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
+ const metadata = {
278
+ ...span.metadata,
279
+ ...inheritedLangfusePrompt ? { langfuse: { prompt: inheritedLangfusePrompt } } : {}
280
+ };
249
281
  const attributesToOmit = [];
282
+ const metadataToOmit = [];
250
283
  if (span.type === observability$1.SpanType.MODEL_GENERATION) {
251
284
  const modelAttr = attributes;
252
285
  if (modelAttr.model !== void 0) {
@@ -254,21 +287,33 @@ var LangfuseExporter = class extends observability.BaseExporter {
254
287
  attributesToOmit.push("model");
255
288
  }
256
289
  if (modelAttr.usage !== void 0) {
257
- const normalizedUsage = this.normalizeUsage(modelAttr.usage);
258
- if (normalizedUsage) {
259
- payload.usage = normalizedUsage;
260
- }
290
+ payload.usageDetails = formatUsageMetrics(modelAttr.usage);
261
291
  attributesToOmit.push("usage");
262
292
  }
263
293
  if (modelAttr.parameters !== void 0) {
264
294
  payload.modelParameters = modelAttr.parameters;
265
295
  attributesToOmit.push("parameters");
266
296
  }
297
+ const langfuseData = metadata.langfuse;
298
+ const promptData = langfuseData?.prompt;
299
+ const hasNameAndVersion = promptData?.name !== void 0 && promptData?.version !== void 0;
300
+ const hasId = promptData?.id !== void 0;
301
+ if (hasNameAndVersion || hasId) {
302
+ payload.prompt = {};
303
+ if (promptData?.name !== void 0) payload.prompt.name = promptData.name;
304
+ if (promptData?.version !== void 0) payload.prompt.version = promptData.version;
305
+ if (promptData?.id !== void 0) payload.prompt.id = promptData.id;
306
+ metadataToOmit.push("langfuse");
307
+ }
308
+ if (modelAttr.completionStartTime !== void 0) {
309
+ payload.completionStartTime = modelAttr.completionStartTime;
310
+ attributesToOmit.push("completionStartTime");
311
+ }
267
312
  }
268
313
  payload.metadata = {
269
314
  spanType: span.type,
270
315
  ...utils.omitKeys(attributes, attributesToOmit),
271
- ...span.metadata
316
+ ...utils.omitKeys(metadata, metadataToOmit)
272
317
  };
273
318
  if (span.errorInfo) {
274
319
  payload.level = "ERROR";
@@ -314,6 +359,26 @@ var LangfuseExporter = class extends observability.BaseExporter {
314
359
  }
315
360
  };
316
361
 
362
+ // src/helpers.ts
363
+ function withLangfusePrompt(prompt) {
364
+ return (opts) => ({
365
+ ...opts,
366
+ metadata: {
367
+ ...opts.metadata,
368
+ langfuse: {
369
+ ...opts.metadata?.langfuse,
370
+ prompt: {
371
+ ...prompt.name !== void 0 && { name: prompt.name },
372
+ ...prompt.version !== void 0 && { version: prompt.version },
373
+ ...prompt.id !== void 0 && { id: prompt.id }
374
+ }
375
+ }
376
+ }
377
+ });
378
+ }
379
+
317
380
  exports.LangfuseExporter = LangfuseExporter;
381
+ exports.formatUsageMetrics = formatUsageMetrics;
382
+ exports.withLangfusePrompt = withLangfusePrompt;
318
383
  //# sourceMappingURL=index.cjs.map
319
384
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tracing.ts"],"names":["BaseExporter","Langfuse","SpanType","omitKeys"],"mappings":"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","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 * Compatible with both AI SDK v4 and v5:\n * - Handles both legacy token usage format (promptTokens/completionTokens)\n * and v5 format (inputTokens/outputTokens)\n * - Supports v5 reasoning tokens and cache-related metrics\n * - Adapts to v5 streaming protocol changes\n */\n\nimport type { TracingEvent, AnyExportedSpan, ModelGenerationAttributes } 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 TraceData = {\n trace: LangfuseTraceClient; // Langfuse trace object\n spans: Map<string, LangfuseSpanClient | LangfuseGenerationClient>; // Maps span.id to Langfuse span/generation\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 * Normalized token usage format compatible with Langfuse.\n * This unified format supports both AI SDK v4 and v5 token structures.\n *\n * @example\n * ```typescript\n * // AI SDK v4 format normalizes to:\n * { input: 100, output: 50, total: 150 }\n *\n * // AI SDK v5 format normalizes to:\n * { input: 120, output: 60, total: 180, reasoning: 1000, cachedInput: 50 }\n * ```\n */\ninterface NormalizedUsage {\n /**\n * Input tokens sent to the model\n * @source AI SDK v5: `inputTokens` | AI SDK v4: `promptTokens`\n */\n input?: number;\n\n /**\n * Output tokens received from the model\n * @source AI SDK v5: `outputTokens` | AI SDK v4: `completionTokens`\n */\n output?: number;\n\n /**\n * Total tokens (input + output + reasoning if applicable)\n * @source AI SDK v4 & v5: `totalTokens`\n */\n total?: number;\n\n /**\n * Reasoning tokens used by reasoning models\n * @source AI SDK v5: `reasoningTokens`\n * @since AI SDK v5.0.0\n * @example Models like o1-preview, o1-mini\n */\n reasoning?: number;\n\n /**\n * Cached input tokens (prompt cache hit)\n * @source AI SDK v5: `cachedInputTokens`\n * @since AI SDK v5.0.0\n * @example Anthropic's prompt caching, OpenAI prompt caching\n */\n cachedInput?: number;\n\n /**\n * Prompt cache hit tokens (legacy format)\n * @source AI SDK v4: `promptCacheHitTokens`\n * @deprecated Prefer `cachedInput` from v5 format\n */\n promptCacheHit?: number;\n\n /**\n * Prompt cache miss tokens (legacy format)\n * @source AI SDK v4: `promptCacheMissTokens`\n * @deprecated Prefer v5 format which uses `cachedInputTokens`\n */\n promptCacheMiss?: number;\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 const langfuseParent = this.getLangfuseParent({ traceData, span, method });\n if (!langfuseParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span, true);\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));\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);\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 const trace = this.client.trace(this.buildTracePayload(span));\n this.traceMap.set(span.traceId, {\n trace,\n spans: new Map(),\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 * Normalize usage data to handle both AI SDK v4 and v5 formats.\n *\n * AI SDK v4 uses: promptTokens, completionTokens\n * AI SDK v5 uses: inputTokens, outputTokens\n *\n * This function normalizes to a unified format that Langfuse can consume,\n * prioritizing v5 format while maintaining backward compatibility.\n *\n * @param usage - Token usage data from AI SDK (v4 or v5 format)\n * @returns Normalized usage object, or undefined if no usage data available\n */\n private normalizeUsage(usage: ModelGenerationAttributes['usage']): NormalizedUsage | undefined {\n if (!usage) return undefined;\n\n const normalized: NormalizedUsage = {};\n\n // Handle input tokens (v5 'inputTokens' or v4 'promptTokens')\n // Using ?? to prioritize v5 format while falling back to v4\n const inputTokens = usage.inputTokens ?? usage.promptTokens;\n if (inputTokens !== undefined) {\n normalized.input = inputTokens;\n }\n\n // Handle output tokens (v5 'outputTokens' or v4 'completionTokens')\n const outputTokens = usage.outputTokens ?? usage.completionTokens;\n if (outputTokens !== undefined) {\n normalized.output = outputTokens;\n }\n\n // Total tokens - calculate if not provided\n if (usage.totalTokens !== undefined) {\n normalized.total = usage.totalTokens;\n } else if (normalized.input !== undefined && normalized.output !== undefined) {\n normalized.total = normalized.input + normalized.output;\n }\n\n // AI SDK v5-specific: reasoning tokens\n if (usage.reasoningTokens !== undefined) {\n normalized.reasoning = usage.reasoningTokens;\n }\n\n // AI SDK v5-specific: cached tokens (cache hit)\n if (usage.cachedInputTokens !== undefined) {\n normalized.cachedInput = usage.cachedInputTokens;\n }\n\n // Legacy cache metrics (promptCacheHitTokens/promptCacheMissTokens)\n if (usage.promptCacheHitTokens !== undefined) {\n normalized.promptCacheHit = usage.promptCacheHitTokens;\n }\n if (usage.promptCacheMissTokens !== undefined) {\n normalized.promptCacheMiss = usage.promptCacheMissTokens;\n }\n\n return Object.keys(normalized).length > 0 ? normalized : undefined;\n }\n\n private buildSpanPayload(span: AnyExportedSpan, isCreate: boolean): 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 // Strip special fields from metadata if used in top-level keys\n const attributesToOmit: 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 // Normalize usage to handle both v4 and v5 formats\n const normalizedUsage = this.normalizeUsage(modelAttr.usage);\n if (normalizedUsage) {\n payload.usage = normalizedUsage;\n }\n attributesToOmit.push('usage');\n }\n\n if (modelAttr.parameters !== undefined) {\n payload.modelParameters = modelAttr.parameters;\n attributesToOmit.push('parameters');\n }\n }\n\n payload.metadata = {\n spanType: span.type,\n ...omitKeys(attributes, attributesToOmit),\n ...span.metadata,\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"]}
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"]}
package/dist/index.d.ts CHANGED
@@ -5,4 +5,5 @@
5
5
  * Currently includes tracing support with plans for additional observability features.
6
6
  */
7
7
  export * from './tracing.js';
8
+ export * from './helpers.js';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,WAAW,CAAC;AAG1B,cAAc,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -4,6 +4,33 @@ import { BaseExporter } from '@mastra/observability';
4
4
  import { Langfuse } from 'langfuse';
5
5
 
6
6
  // src/tracing.ts
7
+ function formatUsageMetrics(usage) {
8
+ if (!usage) return {};
9
+ const metrics = {};
10
+ if (usage.inputTokens !== void 0) {
11
+ metrics.input = usage.inputTokens;
12
+ if (usage.inputDetails?.cacheWrite !== void 0) {
13
+ metrics.cache_write_input_tokens = usage.inputDetails.cacheWrite;
14
+ metrics.input -= metrics.cache_write_input_tokens;
15
+ }
16
+ }
17
+ if (usage.inputDetails?.cacheRead !== void 0) {
18
+ metrics.cache_read_input_tokens = usage.inputDetails.cacheRead;
19
+ }
20
+ if (usage.outputTokens !== void 0) {
21
+ metrics.output = usage.outputTokens;
22
+ }
23
+ if (usage.outputDetails?.reasoning !== void 0) {
24
+ metrics.reasoning = usage.outputDetails.reasoning;
25
+ }
26
+ if (metrics.input && metrics.output) {
27
+ metrics.total = metrics.input + metrics.output;
28
+ if (metrics.cache_write_input_tokens) {
29
+ metrics.total += metrics.cache_write_input_tokens;
30
+ }
31
+ }
32
+ return metrics;
33
+ }
7
34
  var LangfuseExporter = class extends BaseExporter {
8
35
  name = "langfuse";
9
36
  client;
@@ -55,11 +82,18 @@ var LangfuseExporter = class extends BaseExporter {
55
82
  if (!traceData) {
56
83
  return;
57
84
  }
85
+ if (!span.isRootSpan) {
86
+ const langfuseData = span.metadata?.langfuse;
87
+ traceData.spanMetadata.set(span.id, {
88
+ parentSpanId: span.parentSpanId,
89
+ langfusePrompt: langfuseData?.prompt
90
+ });
91
+ }
58
92
  const langfuseParent = this.getLangfuseParent({ traceData, span, method });
59
93
  if (!langfuseParent) {
60
94
  return;
61
95
  }
62
- const payload = this.buildSpanPayload(span, true);
96
+ const payload = this.buildSpanPayload(span, true, traceData);
63
97
  const langfuseSpan = span.type === SpanType.MODEL_GENERATION ? langfuseParent.generation(payload) : langfuseParent.span(payload);
64
98
  traceData.spans.set(span.id, langfuseSpan);
65
99
  traceData.activeSpans.add(span.id);
@@ -90,7 +124,7 @@ var LangfuseExporter = class extends BaseExporter {
90
124
  });
91
125
  return;
92
126
  }
93
- langfuseSpan.update(this.buildSpanPayload(span, false));
127
+ langfuseSpan.update(this.buildSpanPayload(span, false, traceData));
94
128
  if (isEnd) {
95
129
  traceData.activeSpans.delete(span.id);
96
130
  if (span.isRootSpan) {
@@ -120,7 +154,7 @@ var LangfuseExporter = class extends BaseExporter {
120
154
  if (!langfuseParent) {
121
155
  return;
122
156
  }
123
- const payload = this.buildSpanPayload(span, true);
157
+ const payload = this.buildSpanPayload(span, true, traceData);
124
158
  const langfuseEvent = langfuseParent.event(payload);
125
159
  traceData.events.set(span.id, langfuseEvent);
126
160
  if (!span.endTime) {
@@ -128,10 +162,25 @@ var LangfuseExporter = class extends BaseExporter {
128
162
  }
129
163
  }
130
164
  initTrace(span) {
165
+ if (this.traceMap.has(span.traceId)) {
166
+ this.logger.debug("Langfuse exporter: Reusing existing trace from local map", {
167
+ traceId: span.traceId,
168
+ spanId: span.id,
169
+ spanName: span.name
170
+ });
171
+ return;
172
+ }
131
173
  const trace = this.client.trace(this.buildTracePayload(span));
174
+ const langfuseData = span.metadata?.langfuse;
175
+ const spanMetadata = /* @__PURE__ */ new Map();
176
+ spanMetadata.set(span.id, {
177
+ parentSpanId: void 0,
178
+ langfusePrompt: langfuseData?.prompt
179
+ });
132
180
  this.traceMap.set(span.traceId, {
133
181
  trace,
134
182
  spans: /* @__PURE__ */ new Map(),
183
+ spanMetadata,
135
184
  events: /* @__PURE__ */ new Map(),
136
185
  activeSpans: /* @__PURE__ */ new Set(),
137
186
  rootSpanId: span.id
@@ -192,48 +241,22 @@ var LangfuseExporter = class extends BaseExporter {
192
241
  return payload;
193
242
  }
194
243
  /**
195
- * Normalize usage data to handle both AI SDK v4 and v5 formats.
196
- *
197
- * AI SDK v4 uses: promptTokens, completionTokens
198
- * AI SDK v5 uses: inputTokens, outputTokens
199
- *
200
- * This function normalizes to a unified format that Langfuse can consume,
201
- * prioritizing v5 format while maintaining backward compatibility.
202
- *
203
- * @param usage - Token usage data from AI SDK (v4 or v5 format)
204
- * @returns Normalized usage object, or undefined if no usage data available
244
+ * Look up the Langfuse prompt from the closest parent span that has one.
245
+ * This enables prompt inheritance for MODEL_GENERATION spans when the prompt
246
+ * is set on a parent span (e.g., AGENT_RUN) rather than directly on the generation.
205
247
  */
206
- normalizeUsage(usage) {
207
- if (!usage) return void 0;
208
- const normalized = {};
209
- const inputTokens = usage.inputTokens ?? usage.promptTokens;
210
- if (inputTokens !== void 0) {
211
- normalized.input = inputTokens;
212
- }
213
- const outputTokens = usage.outputTokens ?? usage.completionTokens;
214
- if (outputTokens !== void 0) {
215
- normalized.output = outputTokens;
216
- }
217
- if (usage.totalTokens !== void 0) {
218
- normalized.total = usage.totalTokens;
219
- } else if (normalized.input !== void 0 && normalized.output !== void 0) {
220
- normalized.total = normalized.input + normalized.output;
221
- }
222
- if (usage.reasoningTokens !== void 0) {
223
- normalized.reasoning = usage.reasoningTokens;
224
- }
225
- if (usage.cachedInputTokens !== void 0) {
226
- normalized.cachedInput = usage.cachedInputTokens;
227
- }
228
- if (usage.promptCacheHitTokens !== void 0) {
229
- normalized.promptCacheHit = usage.promptCacheHitTokens;
230
- }
231
- if (usage.promptCacheMissTokens !== void 0) {
232
- normalized.promptCacheMiss = usage.promptCacheMissTokens;
248
+ findParentLangfusePrompt(traceData, span) {
249
+ let currentSpanId = span.parentSpanId;
250
+ while (currentSpanId) {
251
+ const parentMetadata = traceData.spanMetadata.get(currentSpanId);
252
+ if (parentMetadata?.langfusePrompt) {
253
+ return parentMetadata.langfusePrompt;
254
+ }
255
+ currentSpanId = parentMetadata?.parentSpanId;
233
256
  }
234
- return Object.keys(normalized).length > 0 ? normalized : void 0;
257
+ return void 0;
235
258
  }
236
- buildSpanPayload(span, isCreate) {
259
+ buildSpanPayload(span, isCreate, traceData) {
237
260
  const payload = {};
238
261
  if (isCreate) {
239
262
  payload.id = span.id;
@@ -244,7 +267,17 @@ var LangfuseExporter = class extends BaseExporter {
244
267
  if (span.output !== void 0) payload.output = span.output;
245
268
  if (span.endTime !== void 0) payload.endTime = span.endTime;
246
269
  const attributes = span.attributes ?? {};
270
+ const resolvedTraceData = traceData ?? this.traceMap.get(span.traceId);
271
+ let inheritedLangfusePrompt;
272
+ if (span.type === SpanType.MODEL_GENERATION && !span.metadata?.langfuse && resolvedTraceData) {
273
+ inheritedLangfusePrompt = this.findParentLangfusePrompt(resolvedTraceData, span);
274
+ }
275
+ const metadata = {
276
+ ...span.metadata,
277
+ ...inheritedLangfusePrompt ? { langfuse: { prompt: inheritedLangfusePrompt } } : {}
278
+ };
247
279
  const attributesToOmit = [];
280
+ const metadataToOmit = [];
248
281
  if (span.type === SpanType.MODEL_GENERATION) {
249
282
  const modelAttr = attributes;
250
283
  if (modelAttr.model !== void 0) {
@@ -252,21 +285,33 @@ var LangfuseExporter = class extends BaseExporter {
252
285
  attributesToOmit.push("model");
253
286
  }
254
287
  if (modelAttr.usage !== void 0) {
255
- const normalizedUsage = this.normalizeUsage(modelAttr.usage);
256
- if (normalizedUsage) {
257
- payload.usage = normalizedUsage;
258
- }
288
+ payload.usageDetails = formatUsageMetrics(modelAttr.usage);
259
289
  attributesToOmit.push("usage");
260
290
  }
261
291
  if (modelAttr.parameters !== void 0) {
262
292
  payload.modelParameters = modelAttr.parameters;
263
293
  attributesToOmit.push("parameters");
264
294
  }
295
+ const langfuseData = metadata.langfuse;
296
+ const promptData = langfuseData?.prompt;
297
+ const hasNameAndVersion = promptData?.name !== void 0 && promptData?.version !== void 0;
298
+ const hasId = promptData?.id !== void 0;
299
+ if (hasNameAndVersion || hasId) {
300
+ payload.prompt = {};
301
+ if (promptData?.name !== void 0) payload.prompt.name = promptData.name;
302
+ if (promptData?.version !== void 0) payload.prompt.version = promptData.version;
303
+ if (promptData?.id !== void 0) payload.prompt.id = promptData.id;
304
+ metadataToOmit.push("langfuse");
305
+ }
306
+ if (modelAttr.completionStartTime !== void 0) {
307
+ payload.completionStartTime = modelAttr.completionStartTime;
308
+ attributesToOmit.push("completionStartTime");
309
+ }
265
310
  }
266
311
  payload.metadata = {
267
312
  spanType: span.type,
268
313
  ...omitKeys(attributes, attributesToOmit),
269
- ...span.metadata
314
+ ...omitKeys(metadata, metadataToOmit)
270
315
  };
271
316
  if (span.errorInfo) {
272
317
  payload.level = "ERROR";
@@ -312,6 +357,24 @@ var LangfuseExporter = class extends BaseExporter {
312
357
  }
313
358
  };
314
359
 
315
- export { LangfuseExporter };
360
+ // src/helpers.ts
361
+ function withLangfusePrompt(prompt) {
362
+ return (opts) => ({
363
+ ...opts,
364
+ metadata: {
365
+ ...opts.metadata,
366
+ langfuse: {
367
+ ...opts.metadata?.langfuse,
368
+ prompt: {
369
+ ...prompt.name !== void 0 && { name: prompt.name },
370
+ ...prompt.version !== void 0 && { version: prompt.version },
371
+ ...prompt.id !== void 0 && { id: prompt.id }
372
+ }
373
+ }
374
+ }
375
+ });
376
+ }
377
+
378
+ export { LangfuseExporter, formatUsageMetrics, withLangfusePrompt };
316
379
  //# sourceMappingURL=index.js.map
317
380
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tracing.ts"],"names":[],"mappings":"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","file":"index.js","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 * Compatible with both AI SDK v4 and v5:\n * - Handles both legacy token usage format (promptTokens/completionTokens)\n * and v5 format (inputTokens/outputTokens)\n * - Supports v5 reasoning tokens and cache-related metrics\n * - Adapts to v5 streaming protocol changes\n */\n\nimport type { TracingEvent, AnyExportedSpan, ModelGenerationAttributes } 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 TraceData = {\n trace: LangfuseTraceClient; // Langfuse trace object\n spans: Map<string, LangfuseSpanClient | LangfuseGenerationClient>; // Maps span.id to Langfuse span/generation\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 * Normalized token usage format compatible with Langfuse.\n * This unified format supports both AI SDK v4 and v5 token structures.\n *\n * @example\n * ```typescript\n * // AI SDK v4 format normalizes to:\n * { input: 100, output: 50, total: 150 }\n *\n * // AI SDK v5 format normalizes to:\n * { input: 120, output: 60, total: 180, reasoning: 1000, cachedInput: 50 }\n * ```\n */\ninterface NormalizedUsage {\n /**\n * Input tokens sent to the model\n * @source AI SDK v5: `inputTokens` | AI SDK v4: `promptTokens`\n */\n input?: number;\n\n /**\n * Output tokens received from the model\n * @source AI SDK v5: `outputTokens` | AI SDK v4: `completionTokens`\n */\n output?: number;\n\n /**\n * Total tokens (input + output + reasoning if applicable)\n * @source AI SDK v4 & v5: `totalTokens`\n */\n total?: number;\n\n /**\n * Reasoning tokens used by reasoning models\n * @source AI SDK v5: `reasoningTokens`\n * @since AI SDK v5.0.0\n * @example Models like o1-preview, o1-mini\n */\n reasoning?: number;\n\n /**\n * Cached input tokens (prompt cache hit)\n * @source AI SDK v5: `cachedInputTokens`\n * @since AI SDK v5.0.0\n * @example Anthropic's prompt caching, OpenAI prompt caching\n */\n cachedInput?: number;\n\n /**\n * Prompt cache hit tokens (legacy format)\n * @source AI SDK v4: `promptCacheHitTokens`\n * @deprecated Prefer `cachedInput` from v5 format\n */\n promptCacheHit?: number;\n\n /**\n * Prompt cache miss tokens (legacy format)\n * @source AI SDK v4: `promptCacheMissTokens`\n * @deprecated Prefer v5 format which uses `cachedInputTokens`\n */\n promptCacheMiss?: number;\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 const langfuseParent = this.getLangfuseParent({ traceData, span, method });\n if (!langfuseParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span, true);\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));\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);\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 const trace = this.client.trace(this.buildTracePayload(span));\n this.traceMap.set(span.traceId, {\n trace,\n spans: new Map(),\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 * Normalize usage data to handle both AI SDK v4 and v5 formats.\n *\n * AI SDK v4 uses: promptTokens, completionTokens\n * AI SDK v5 uses: inputTokens, outputTokens\n *\n * This function normalizes to a unified format that Langfuse can consume,\n * prioritizing v5 format while maintaining backward compatibility.\n *\n * @param usage - Token usage data from AI SDK (v4 or v5 format)\n * @returns Normalized usage object, or undefined if no usage data available\n */\n private normalizeUsage(usage: ModelGenerationAttributes['usage']): NormalizedUsage | undefined {\n if (!usage) return undefined;\n\n const normalized: NormalizedUsage = {};\n\n // Handle input tokens (v5 'inputTokens' or v4 'promptTokens')\n // Using ?? to prioritize v5 format while falling back to v4\n const inputTokens = usage.inputTokens ?? usage.promptTokens;\n if (inputTokens !== undefined) {\n normalized.input = inputTokens;\n }\n\n // Handle output tokens (v5 'outputTokens' or v4 'completionTokens')\n const outputTokens = usage.outputTokens ?? usage.completionTokens;\n if (outputTokens !== undefined) {\n normalized.output = outputTokens;\n }\n\n // Total tokens - calculate if not provided\n if (usage.totalTokens !== undefined) {\n normalized.total = usage.totalTokens;\n } else if (normalized.input !== undefined && normalized.output !== undefined) {\n normalized.total = normalized.input + normalized.output;\n }\n\n // AI SDK v5-specific: reasoning tokens\n if (usage.reasoningTokens !== undefined) {\n normalized.reasoning = usage.reasoningTokens;\n }\n\n // AI SDK v5-specific: cached tokens (cache hit)\n if (usage.cachedInputTokens !== undefined) {\n normalized.cachedInput = usage.cachedInputTokens;\n }\n\n // Legacy cache metrics (promptCacheHitTokens/promptCacheMissTokens)\n if (usage.promptCacheHitTokens !== undefined) {\n normalized.promptCacheHit = usage.promptCacheHitTokens;\n }\n if (usage.promptCacheMissTokens !== undefined) {\n normalized.promptCacheMiss = usage.promptCacheMissTokens;\n }\n\n return Object.keys(normalized).length > 0 ? normalized : undefined;\n }\n\n private buildSpanPayload(span: AnyExportedSpan, isCreate: boolean): 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 // Strip special fields from metadata if used in top-level keys\n const attributesToOmit: 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 // Normalize usage to handle both v4 and v5 formats\n const normalizedUsage = this.normalizeUsage(modelAttr.usage);\n if (normalizedUsage) {\n payload.usage = normalizedUsage;\n }\n attributesToOmit.push('usage');\n }\n\n if (modelAttr.parameters !== undefined) {\n payload.modelParameters = modelAttr.parameters;\n attributesToOmit.push('parameters');\n }\n }\n\n payload.metadata = {\n spanType: span.type,\n ...omitKeys(attributes, attributesToOmit),\n ...span.metadata,\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"]}
1
+ {"version":3,"sources":["../src/tracing.ts","../src/helpers.ts"],"names":[],"mappings":"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","file":"index.js","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"]}
package/dist/tracing.d.ts CHANGED
@@ -4,14 +4,8 @@
4
4
  * This exporter sends observability data to Langfuse.
5
5
  * Root spans start traces in Langfuse.
6
6
  * MODEL_GENERATION spans become Langfuse generations, all others become spans.
7
- *
8
- * Compatible with both AI SDK v4 and v5:
9
- * - Handles both legacy token usage format (promptTokens/completionTokens)
10
- * and v5 format (inputTokens/outputTokens)
11
- * - Supports v5 reasoning tokens and cache-related metrics
12
- * - Adapts to v5 streaming protocol changes
13
7
  */
14
- import type { TracingEvent } from '@mastra/core/observability';
8
+ import type { TracingEvent, UsageStats } from '@mastra/core/observability';
15
9
  import { BaseExporter } from '@mastra/observability';
16
10
  import type { BaseExporterConfig } from '@mastra/observability';
17
11
  export interface LangfuseExporterConfig extends BaseExporterConfig {
@@ -26,6 +20,21 @@ export interface LangfuseExporterConfig extends BaseExporterConfig {
26
20
  /** Additional options to pass to the Langfuse client */
27
21
  options?: any;
28
22
  }
23
+ /**
24
+ * Token usage format compatible with Langfuse.
25
+ */
26
+ export interface LangfuseUsageMetrics {
27
+ input?: number;
28
+ output?: number;
29
+ total?: number;
30
+ reasoning?: number;
31
+ cache_read_input_tokens?: number;
32
+ cache_write_input_tokens?: number;
33
+ }
34
+ /**
35
+ * Formats UsageStats to Langfuse's expected format.
36
+ */
37
+ export declare function formatUsageMetrics(usage?: UsageStats): LangfuseUsageMetrics;
29
38
  export declare class LangfuseExporter extends BaseExporter {
30
39
  name: string;
31
40
  private client;
@@ -41,18 +50,11 @@ export declare class LangfuseExporter extends BaseExporter {
41
50
  private getLangfuseParent;
42
51
  private buildTracePayload;
43
52
  /**
44
- * Normalize usage data to handle both AI SDK v4 and v5 formats.
45
- *
46
- * AI SDK v4 uses: promptTokens, completionTokens
47
- * AI SDK v5 uses: inputTokens, outputTokens
48
- *
49
- * This function normalizes to a unified format that Langfuse can consume,
50
- * prioritizing v5 format while maintaining backward compatibility.
51
- *
52
- * @param usage - Token usage data from AI SDK (v4 or v5 format)
53
- * @returns Normalized usage object, or undefined if no usage data available
53
+ * Look up the Langfuse prompt from the closest parent span that has one.
54
+ * This enables prompt inheritance for MODEL_GENERATION spans when the prompt
55
+ * is set on a parent span (e.g., AGENT_RUN) rather than directly on the generation.
54
56
  */
55
- private normalizeUsage;
57
+ private findParentLangfusePrompt;
56
58
  private buildSpanPayload;
57
59
  addScoreToTrace({ traceId, spanId, score, reason, scorerName, metadata, }: {
58
60
  traceId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAA8C,MAAM,4BAA4B,CAAC;AAG3G,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAIhE,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,OAAO,CAAC,EAAE,GAAG,CAAC;CACf;AA2ED,qBAAa,gBAAiB,SAAQ,YAAY;IAChD,IAAI,SAAc;IAClB,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,QAAQ,CAAgC;gBAEpC,MAAM,EAAE,sBAAsB;cAsB1B,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;YAwBzD,iBAAiB;YAyBjB,qBAAqB;YAmDrB,eAAe;IAkC7B,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,iBAAiB;IA4BzB,OAAO,CAAC,iBAAiB;IAuBzB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IA8CtB,OAAO,CAAC,gBAAgB;IAuDlB,eAAe,CAAC,EACpB,OAAO,EACP,MAAM,EACN,KAAK,EACL,MAAM,EACN,UAAU,EACV,QAAQ,GACT,EAAE;QACD,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KAChC,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBX,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAOhC"}
1
+ {"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAA8C,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAGvH,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAIhE,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,OAAO,CAAC,EAAE,GAAG,CAAC;CACf;AAoBD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,oBAAoB,CAkC3E;AAED,qBAAa,gBAAiB,SAAQ,YAAY;IAChD,IAAI,SAAc;IAClB,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,QAAQ,CAAgC;gBAEpC,MAAM,EAAE,sBAAsB;cAsB1B,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;YAwBzD,iBAAiB;YAkCjB,qBAAqB;YAmDrB,eAAe;IAkC7B,OAAO,CAAC,SAAS;IAwCjB,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,iBAAiB;IA4BzB,OAAO,CAAC,iBAAiB;IAuBzB;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;IAchC,OAAO,CAAC,gBAAgB;IAkGlB,eAAe,CAAC,EACpB,OAAO,EACP,MAAM,EACN,KAAK,EACL,MAAM,EACN,UAAU,EACV,QAAQ,GACT,EAAE;QACD,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KAChC,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBX,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAOhC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/langfuse",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.4",
4
4
  "description": "Langfuse observability provider for Mastra - includes tracing and future observability features",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,7 +25,7 @@
25
25
  "license": "Apache-2.0",
26
26
  "dependencies": {
27
27
  "langfuse": "^3.38.6",
28
- "@mastra/observability": "1.0.0-beta.2"
28
+ "@mastra/observability": "1.0.0-beta.4"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@microsoft/api-extractor": "^7.52.8",
@@ -38,7 +38,7 @@
38
38
  "vitest": "4.0.12",
39
39
  "@internal/lint": "0.0.53",
40
40
  "@internal/types-builder": "0.0.28",
41
- "@mastra/core": "1.0.0-beta.6"
41
+ "@mastra/core": "1.0.0-beta.10"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@mastra/core": ">=1.0.0-0 <2.0.0-0"