@mastra/langfuse 1.6.3-alpha.1 → 1.6.3-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -4,7 +4,7 @@ let _langfuse_otel = require("@langfuse/otel");
4
4
  let _mastra_core_observability = require("@mastra/core/observability");
5
5
  let _mastra_observability = require("@mastra/observability");
6
6
  let _mastra_otel_exporter = require("@mastra/otel-exporter");
7
- //#region src/tracing.ts
7
+ // src/tracing.ts
8
8
  /**
9
9
  * Langfuse Exporter for Mastra Observability
10
10
  *
@@ -318,8 +318,7 @@ function stripTrailingSlashes(s) {
318
318
  while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
319
319
  return end === s.length ? s : s.slice(0, end);
320
320
  }
321
- //#endregion
322
- //#region src/helpers.ts
321
+ // src/helpers.ts
323
322
  /**
324
323
  * Adds Langfuse prompt metadata to the tracing options
325
324
  * to enable Langfuse Prompt Tracing.
@@ -372,7 +371,6 @@ function withLangfusePrompt(prompt) {
372
371
  }
373
372
  });
374
373
  }
375
- //#endregion
376
374
  exports.LANGFUSE_DEFAULT_BASE_URL = LANGFUSE_DEFAULT_BASE_URL;
377
375
  exports.LangfuseExporter = LangfuseExporter;
378
376
  exports.withLangfusePrompt = withLangfusePrompt;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["BaseExporter","LangfuseSpanProcessor","LangfuseClient","SpanConverter","TracingEventType","SpanType"],"sources":["../src/tracing.ts","../src/helpers.ts"],"sourcesContent":["/**\n * Langfuse Exporter for Mastra Observability\n *\n * Sends observability data to Langfuse using the official @langfuse/otel span processor\n * and @langfuse/client for non-tracing features (scoring, prompt management, evaluations).\n *\n * @see https://langfuse.com/docs/observability/sdk/typescript/overview\n */\n\nimport { LangfuseClient } from '@langfuse/client';\nimport { LangfuseSpanProcessor } from '@langfuse/otel';\nimport type {\n TracingEvent,\n AnyExportedSpan,\n InitExporterOptions,\n ModelGenerationAttributes,\n ScoreEvent,\n} from '@mastra/core/observability';\nimport { SpanType, TracingEventType } from '@mastra/core/observability';\nimport { BaseExporter } from '@mastra/observability';\nimport type { BaseExporterConfig } from '@mastra/observability';\nimport { SpanConverter } from '@mastra/otel-exporter';\nimport type { OtelExporterConfig } from '@mastra/otel-exporter';\n\nconst LOG_PREFIX = '[LangfuseExporter]';\nconst MASTRA_METADATA_PREFIX = 'mastra.metadata.';\n/** Metadata keys mapped to dedicated Langfuse fields; never forwarded as trace metadata. */\nconst OM_CALLER_THREAD_ID = '__mastraObservationalMemoryCallerThreadId';\nconst DEDICATED_METADATA_KEYS = new Set([\n 'userId',\n 'sessionId',\n 'threadId',\n 'traceName',\n 'version',\n 'langfuse',\n OM_CALLER_THREAD_ID,\n]);\n\nexport const LANGFUSE_DEFAULT_BASE_URL = 'https://cloud.langfuse.com';\n\nexport interface LangfuseExporterConfig extends BaseExporterConfig {\n /** Langfuse public key */\n publicKey?: string;\n /** Langfuse secret key */\n secretKey?: string;\n /** Langfuse host URL (defaults to https://cloud.langfuse.com) */\n baseUrl?: string;\n /** Additional headers sent with requests to Langfuse */\n additionalHeaders?: Record<string, string>;\n /** Enable realtime mode - flushes after each event for immediate visibility */\n realtime?: boolean;\n /** Maximum number of spans per OTEL export batch */\n flushAt?: number;\n /** Maximum time in seconds before pending spans are exported */\n flushInterval?: number;\n /** Langfuse environment tag for traces */\n environment?: string;\n /** Langfuse release tag for traces */\n release?: string;\n /** Override OpenTelemetry resource attributes, including service.version. */\n resourceAttributes?: OtelExporterConfig['resourceAttributes'];\n}\n\nexport class LangfuseExporter extends BaseExporter {\n name = 'langfuse';\n #processor: LangfuseSpanProcessor | undefined;\n #client: LangfuseClient | undefined;\n #spanConverter: SpanConverter | undefined;\n #realtime: boolean;\n #environment: string | undefined;\n #release: string | undefined;\n #resourceAttributes: OtelExporterConfig['resourceAttributes'];\n\n constructor(config: LangfuseExporterConfig = {}) {\n super(config);\n\n const publicKey = config.publicKey ?? process.env.LANGFUSE_PUBLIC_KEY;\n const secretKey = config.secretKey ?? process.env.LANGFUSE_SECRET_KEY;\n const baseUrl = stripTrailingSlashes(config.baseUrl ?? process.env.LANGFUSE_BASE_URL ?? LANGFUSE_DEFAULT_BASE_URL);\n this.#realtime = config.realtime ?? false;\n this.#resourceAttributes = config.resourceAttributes;\n\n if (!publicKey || !secretKey) {\n const publicKeySource = config.publicKey\n ? 'from config'\n : process.env.LANGFUSE_PUBLIC_KEY\n ? 'from env'\n : 'missing';\n const secretKeySource = config.secretKey\n ? 'from config'\n : process.env.LANGFUSE_SECRET_KEY\n ? 'from env'\n : 'missing';\n this.setDisabled(\n `${LOG_PREFIX} Missing required credentials (publicKey: ${publicKeySource}, secretKey: ${secretKeySource}). ` +\n `Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY environment variables or pass them in config.`,\n );\n return;\n }\n\n this.#processor = new LangfuseSpanProcessor({\n publicKey,\n secretKey,\n baseUrl,\n additionalHeaders: config.additionalHeaders,\n environment: config.environment,\n release: config.release,\n exportMode: this.#realtime ? 'immediate' : 'batched',\n flushAt: config.flushAt,\n flushInterval: config.flushInterval,\n // Export all spans — the default filter only passes spans with gen_ai.* attributes\n // or known LLM instrumentation scopes, but Mastra spans use mastra.* attributes.\n shouldExportSpan: () => true,\n });\n\n this.#client = new LangfuseClient({\n publicKey,\n secretKey,\n baseUrl,\n additionalHeaders: config.additionalHeaders,\n });\n\n this.#environment = config.environment ?? process.env.LANGFUSE_TRACING_ENVIRONMENT;\n this.#release = config.release ?? process.env.LANGFUSE_RELEASE;\n }\n\n init(options: InitExporterOptions) {\n this.#spanConverter = new SpanConverter({\n packageName: '@mastra/langfuse',\n serviceName: options.config?.serviceName,\n config: { resourceAttributes: this.#resourceAttributes },\n format: 'GenAI_v1_38_0',\n });\n }\n\n protected async _exportTracingEvent(event: TracingEvent): Promise<void> {\n if (event.type !== TracingEventType.SPAN_ENDED) return;\n if (!this.#processor) return;\n\n await this.exportSpan(event.exportedSpan);\n }\n\n private async exportSpan(span: AnyExportedSpan): Promise<void> {\n if (!this.#spanConverter) {\n // Fallback if init() was not called (e.g., standalone usage without Mastra)\n this.#spanConverter = new SpanConverter({\n packageName: '@mastra/langfuse',\n serviceName: 'mastra-service',\n config: { resourceAttributes: this.#resourceAttributes },\n format: 'GenAI_v1_38_0',\n });\n }\n\n try {\n const otelSpan = await this.#spanConverter.convertSpan(span);\n\n // Map mastra.* attributes to langfuse.* namespace so that Langfuse's OTLP\n // endpoint reads them correctly. SpanConverter produces mastra.* attributes,\n // but Langfuse only reads langfuse.* attributes for prompt linking, TTFT, etc.\n // @see https://langfuse.com/integrations/native/opentelemetry#property-mapping\n mapMastraToLangfuseAttributes(otelSpan.attributes, span, this.#environment, this.#release);\n\n this.#processor!.onEnd(otelSpan);\n } catch (error) {\n this.logger.error(`${LOG_PREFIX} Failed to export span ${span.id}:`, error);\n }\n }\n\n /**\n * The LangfuseClient instance for advanced Langfuse features.\n * Use this for prompt management, evaluations, datasets, and direct API access.\n */\n get client(): LangfuseClient | undefined {\n return this.#client;\n }\n\n /**\n * Submit a score to Langfuse. Used by both the new `onScoreEvent` path and the\n * deprecated `addScoreToTrace` wrapper.\n */\n private submitScore(args: {\n id: string;\n traceId: string;\n spanId?: string;\n name: string;\n value: number;\n comment?: string;\n metadata?: Record<string, unknown>;\n }): void {\n if (!this.#client) return;\n\n const { id, traceId, spanId, name, value, comment, metadata } = args;\n try {\n this.#client.score.create({\n id,\n traceId,\n ...(spanId ? { observationId: spanId } : {}),\n name,\n value,\n ...(comment ? { comment } : {}),\n ...(metadata ? { metadata } : {}),\n ...(this.#environment ? { environment: this.#environment } : {}),\n dataType: 'NUMERIC' as const,\n });\n } catch (error) {\n this.logger.error(`${LOG_PREFIX} Error submitting score`, {\n error,\n traceId,\n spanId,\n name,\n });\n }\n }\n\n async onScoreEvent(event: ScoreEvent): Promise<void> {\n const { score } = event;\n if (!score.traceId) return;\n this.submitScore({\n id: score.scoreId,\n traceId: score.traceId,\n spanId: score.spanId,\n name: score.scorerName ?? score.scorerId,\n value: score.score,\n comment: score.reason,\n metadata: score.metadata,\n });\n }\n\n /**\n * @deprecated Use the observability score event pipeline (`mastra.observability.addScore`)\n * instead. This method is preserved for backwards compatibility and forwards to the same\n * underlying client call as `onScoreEvent`.\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 this.submitScore({\n id: `${traceId}-${spanId || ''}-${scorerName}`,\n traceId,\n spanId,\n name: scorerName,\n value: score,\n comment: reason,\n metadata,\n });\n }\n\n async flush(): Promise<void> {\n await Promise.all([this.#processor?.forceFlush(), this.#client?.flush()]);\n }\n\n async shutdown(): Promise<void> {\n await Promise.all([this.#processor?.shutdown(), this.#client?.shutdown()]);\n }\n}\n\n/**\n * Maps Mastra-specific OTel attributes to the langfuse.* namespace that\n * Langfuse's OTLP endpoint reads for prompt linking, TTFT, and other features.\n *\n * SpanConverter produces attributes like mastra.metadata.*, mastra.completion_start_time, etc.\n * Langfuse's OTLP server only reads langfuse.observation.prompt.name, langfuse.observation.completion_start_time, etc.\n *\n * This function mutates the attributes object in place.\n * @see https://langfuse.com/integrations/native/opentelemetry#property-mapping\n */\n/**\n * Serialize a root span's input/output for the trace-level attributes.\n * Returns undefined (attribute omitted) for absent values and for values that\n * cannot be JSON-serialized, so a bad payload never fails the span export.\n */\nfunction serializeTraceIo(value: unknown): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value);\n } catch {\n return undefined;\n }\n}\n\nfunction mapMastraToLangfuseAttributes(\n attributes: Record<string, any>,\n span: AnyExportedSpan,\n environment?: string,\n release?: string,\n): void {\n if (attributes['gen_ai.usage.reasoning_tokens'] !== undefined) {\n attributes['gen_ai.usage.reasoning.output_tokens'] = attributes['gen_ai.usage.reasoning_tokens'];\n delete attributes['gen_ai.usage.reasoning_tokens'];\n }\n\n // A run attached under an existing span (tracingOptions.nestUnderParent) does\n // not own the trace. Langfuse applies trace-level attributes from any span,\n // so none of its spans may write them: they would replace the outer run's.\n // The skipped values stay on the observation as mastra.metadata.*.\n const nested = span.nestedUnderParent === true;\n\n // Environment and release: set directly since onStart() is not called\n if (environment) {\n attributes['langfuse.environment'] = environment;\n }\n if (release) {\n attributes['langfuse.release'] = release;\n }\n\n // mastra.metadata.langfuse holds a user-supplied object with two kinds of keys:\n // - the reserved `prompt` key, used for prompt linking, and\n // - arbitrary custom keys the user wants as top-level, filterable trace metadata.\n // Langfuse only allows filtering/grouping traces by top-level metadata\n // (langfuse.trace.metadata.*), so forward every non-prompt key there. These\n // attributes may be set on any span in the trace and land on the trace record.\n // @see https://langfuse.com/integrations/native/opentelemetry#property-mapping\n const langfuseMetadata = attributes['mastra.metadata.langfuse'];\n if (langfuseMetadata) {\n try {\n const parsed = typeof langfuseMetadata === 'string' ? JSON.parse(langfuseMetadata) : langfuseMetadata;\n if (parsed && typeof parsed === 'object') {\n // Prompt linking: mastra.metadata.langfuse.prompt → langfuse.observation.prompt.name / version\n if (parsed.prompt) {\n if (parsed.prompt.name !== undefined) {\n attributes['langfuse.observation.prompt.name'] = parsed.prompt.name;\n }\n if (parsed.prompt.version !== undefined) {\n attributes['langfuse.observation.prompt.version'] = parsed.prompt.version;\n }\n }\n\n // Custom keys: mastra.metadata.langfuse.<key> → langfuse.trace.metadata.<key>\n // Reserved identity keys (agentId/agentName/workflowId/workflowName) are set\n // by the root-span block below, which runs after this loop and takes precedence.\n for (const [key, value] of Object.entries(parsed)) {\n if (nested || key === 'prompt' || value === null || value === undefined) {\n continue;\n }\n const traceKey = `langfuse.trace.metadata.${key}`;\n // Don't overwrite a trace.metadata key already mapped from another attribute.\n if (attributes[traceKey] === undefined) {\n // Langfuse maps langfuse.trace.metadata.* as string attributes, so\n // serialize non-strings with JSON. Langfuse Cloud restores numbers,\n // booleans, and objects to their original types on ingestion.\n attributes[traceKey] = typeof value === 'string' ? value : JSON.stringify(value);\n }\n }\n }\n } catch {\n // best effort — invalid JSON is silently ignored\n }\n if (!nested) {\n delete attributes['mastra.metadata.langfuse'];\n }\n }\n\n // TTFT: mastra.completion_start_time → langfuse.observation.completion_start_time\n if (attributes['mastra.completion_start_time']) {\n attributes['langfuse.observation.completion_start_time'] = attributes['mastra.completion_start_time'];\n delete attributes['mastra.completion_start_time'];\n }\n\n // Exact provider-reported cost takes precedence over Langfuse's model-price inference.\n // Estimated costs remain unexported so Langfuse can apply its own pricing model.\n const costContext =\n span.type === SpanType.MODEL_GENERATION\n ? (span.attributes as ModelGenerationAttributes | undefined)?.costContext\n : undefined;\n if (\n costContext?.costMetadata?.source === 'provider_reported' &&\n costContext.costUnit === 'USD' &&\n typeof costContext.estimatedCost === 'number' &&\n Number.isFinite(costContext.estimatedCost) &&\n costContext.estimatedCost >= 0\n ) {\n attributes['langfuse.observation.cost_details'] = JSON.stringify({\n total: costContext.estimatedCost,\n });\n }\n\n // User ID: mastra.metadata.userId → user.id\n if (!nested && attributes['mastra.metadata.userId']) {\n attributes['user.id'] = attributes['mastra.metadata.userId'];\n delete attributes['mastra.metadata.userId'];\n }\n\n // OM keeps caller identity separate from its isolated execution thread. Read\n // the raw hint: conversion serializes malformed objects/arrays into strings.\n const omCallerThreadId = span.metadata?.[OM_CALLER_THREAD_ID];\n const sessionId =\n attributes['mastra.metadata.sessionId'] ??\n (typeof omCallerThreadId === 'string' && omCallerThreadId\n ? omCallerThreadId\n : attributes['mastra.metadata.threadId']);\n delete attributes[`${MASTRA_METADATA_PREFIX}${OM_CALLER_THREAD_ID}`];\n if (!nested && sessionId) {\n attributes['session.id'] = sessionId;\n delete attributes['mastra.metadata.sessionId'];\n delete attributes['mastra.metadata.threadId'];\n }\n\n // Tags: mastra.tags → langfuse.trace.tags\n if (attributes['mastra.tags']) {\n attributes['langfuse.trace.tags'] = attributes['mastra.tags'];\n delete attributes['mastra.tags'];\n }\n\n // Trace name: mastra.metadata.traceName → langfuse.trace.name\n if (!nested && attributes['mastra.metadata.traceName']) {\n attributes['langfuse.trace.name'] = attributes['mastra.metadata.traceName'];\n delete attributes['mastra.metadata.traceName'];\n }\n\n // Version: mastra.metadata.version → langfuse.version\n if (attributes['mastra.metadata.version']) {\n attributes['langfuse.version'] = attributes['mastra.metadata.version'];\n delete attributes['mastra.metadata.version'];\n }\n\n // Root-span trace identity: scope each Langfuse trace to the entity that\n // started it (agent or workflow). This makes the Langfuse trace name match\n // the agent/workflow id and exposes the same identity as trace metadata, so\n // users can scope Langfuse evaluators per agent via trace name or metadata\n // filters. User-provided traceName (set via mastra.metadata.traceName) takes\n // precedence and is preserved.\n if (span.isRootSpan && !nested) {\n // Trace input/output: mirror the root span's input/output onto the trace.\n // Without this, Langfuse traces have empty trace-level input/output (the\n // span data only reaches the root OBSERVATION), which breaks LLM-as-a-judge\n // evaluators mapped to Trace input/output and leaves the trace view without\n // the top-level request/response. Serialization failures (circular refs,\n // bigint) skip the attribute rather than failing the span export.\n // A resumed run (after a suspend such as tool-call approval) continues the\n // original trace: core marks it with parentSpanId === resumedFromSpanId.\n // Its input is the resume payload, not the user's message, and Langfuse\n // applies trace-level attributes from any span (last writer wins), so it\n // must not replace the trace input the original run already set. Its\n // output IS the turn's final reply, so trace output is still written.\n const resumedContinuation =\n span.parentSpanId !== undefined && span.parentSpanId === span.metadata?.resumedFromSpanId;\n const traceInput = resumedContinuation ? undefined : serializeTraceIo(span.input);\n if (traceInput !== undefined) {\n attributes['langfuse.trace.input'] = traceInput;\n }\n const traceOutput = serializeTraceIo(span.output);\n if (traceOutput !== undefined) {\n attributes['langfuse.trace.output'] = traceOutput;\n }\n if (span.type === SpanType.AGENT_RUN) {\n if (!attributes['langfuse.trace.name'] && (span.entityName || span.entityId)) {\n attributes['langfuse.trace.name'] = span.entityName ?? span.entityId;\n }\n if (span.entityId) {\n attributes['langfuse.trace.metadata.agentId'] = span.entityId;\n }\n if (span.entityName) {\n attributes['langfuse.trace.metadata.agentName'] = span.entityName;\n }\n } else if (span.type === SpanType.WORKFLOW_RUN) {\n if (!attributes['langfuse.trace.name'] && (span.entityName || span.entityId)) {\n attributes['langfuse.trace.name'] = span.entityName ?? span.entityId;\n }\n if (span.entityId) {\n attributes['langfuse.trace.metadata.workflowId'] = span.entityId;\n }\n if (span.entityName) {\n attributes['langfuse.trace.metadata.workflowName'] = span.entityName;\n }\n }\n\n // Root-span metadata: forward the remaining mastra.metadata.* keys (runId,\n // resourceId, user-supplied keys) to langfuse.trace.metadata.* so they are\n // filterable trace metadata; Langfuse nests unmapped attributes under\n // metadata.attributes. Dedicated keys, explicit metadata.langfuse.* values,\n // and the identity keys above take precedence. Root only: Langfuse applies\n // langfuse.trace.* from any span, so a child span could overwrite the trace.\n for (const [key, value] of Object.entries(attributes)) {\n if (!key.startsWith(MASTRA_METADATA_PREFIX)) {\n continue;\n }\n const metadataKey = key.slice(MASTRA_METADATA_PREFIX.length);\n if (DEDICATED_METADATA_KEYS.has(metadataKey) || value === null || value === undefined) {\n continue;\n }\n const traceKey = `langfuse.trace.metadata.${metadataKey}`;\n if (attributes[traceKey] !== undefined) {\n continue;\n }\n const serialized = serializeTraceIo(value);\n if (serialized !== undefined) {\n attributes[traceKey] = serialized;\n }\n }\n }\n\n // Observation metadata: map semantic attributes to langfuse.observation.metadata.*\n // so they become top-level filterable keys on each observation in Langfuse.\n // @see https://langfuse.com/integrations/native/opentelemetry#how-metadata-mapping-works\n if (attributes['gen_ai.agent.id']) {\n attributes['langfuse.observation.metadata.agentId'] = attributes['gen_ai.agent.id'];\n }\n if (attributes['gen_ai.agent.name']) {\n attributes['langfuse.observation.metadata.agentName'] = attributes['gen_ai.agent.name'];\n }\n if (attributes['mastra.span.type']) {\n attributes['langfuse.observation.metadata.spanType'] = attributes['mastra.span.type'];\n }\n if (attributes['gen_ai.operation.name']) {\n attributes['langfuse.observation.metadata.operationName'] = attributes['gen_ai.operation.name'];\n }\n\n // Span-type attributes (mastra.<span_type>.<snake_key>, e.g.\n // mastra.model_step.step_index) are promoted the same way, as camelCase keys,\n // so scalar span attributes are filterable in observation-level evaluator\n // rules without a per-key mapping. Input/output have their own mapping below.\n // Attributes whose raw value is an object or array (entryMetadata,\n // truthyIndexes, ...) are already JSON strings here and make no useful\n // filter, so they stay in the nested attributes only. Values are sent as strings.\n if (span.type) {\n const spanAttrPrefix = `mastra.${String(span.type).toLowerCase()}.`;\n const rawAttributes = (span.attributes ?? {}) as Record<string, unknown>;\n for (const [key, value] of Object.entries(attributes)) {\n if (!key.startsWith(spanAttrPrefix) || value === null || value === undefined) {\n continue;\n }\n const name = snakeToCamel(key.slice(spanAttrPrefix.length));\n if (name === 'input' || name === 'output') {\n continue;\n }\n const raw = rawAttributes[name];\n if (typeof raw === 'object' && raw !== null && !(raw instanceof Date)) {\n continue;\n }\n const metadataKey = `langfuse.observation.metadata.${name}`;\n if (attributes[metadataKey] === undefined) {\n attributes[metadataKey] = typeof value === 'string' ? value : JSON.stringify(value);\n }\n }\n }\n\n // Input/Output: mastra.*.input/output → langfuse.observation.input/output\n // For gen_ai spans, Langfuse reads gen_ai.input.messages natively.\n // For non-gen_ai spans, we map the first mastra.*.input/output we find.\n if (!attributes['gen_ai.input.messages'] && !attributes['gen_ai.tool.call.arguments']) {\n for (const key of Object.keys(attributes)) {\n if (key.startsWith('mastra.') && key.endsWith('.input')) {\n attributes['langfuse.observation.input'] = attributes[key];\n delete attributes[key];\n break;\n }\n }\n }\n if (!attributes['gen_ai.output.messages'] && !attributes['gen_ai.tool.call.result']) {\n for (const key of Object.keys(attributes)) {\n if (key.startsWith('mastra.') && key.endsWith('.output')) {\n attributes['langfuse.observation.output'] = attributes[key];\n delete attributes[key];\n break;\n }\n }\n }\n}\n\nfunction snakeToCamel(name: string): string {\n return name.replace(/_([a-z0-9])/g, (_, c: string) => c.toUpperCase());\n}\n\n/**\n * Remove trailing \"/\" characters procedurally. Avoids polynomial\n * backtracking that a greedy regex like `/\\/+$/` can exhibit when the\n * input is attacker-controlled.\n */\nfunction stripTrailingSlashes(s: string): string {\n let end = s.length;\n while (end > 0 && s.charCodeAt(end - 1) === 47 /* \"/\" */) {\n end--;\n }\n return end === s.length ? s : s.slice(0, end);\n}\n","/**\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 agent = new Agent({\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangfusePrompt({ name: 'my-prompt', version: 1 }),\n * ),\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 /** @deprecated Langfuse v5 only supports linking by name + version. This field is ignored. */\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 (required for Langfuse v5)\n * - `version` - Prompt version (required for Langfuse v5)\n *\n * All fields are deeply merged with any existing metadata.\n *\n * @param prompt - Prompt fields for linking (`name` and `version` required)\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 *\n * // Link a generation to a Langfuse prompt by name and version\n * const tracingOptions = buildTracingOptions(\n * withLangfusePrompt({ name: 'customer-support', version: 1 }),\n * );\n *\n * // Or directly in agent config\n * const agent = new Agent({\n * name: 'support-agent',\n * instructions: 'You are a helpful assistant',\n * model: openai('gpt-4o'),\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangfusePrompt({ name: 'my-prompt', version: 1 }),\n * ),\n * },\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"],"mappings":";;;;;;;;;;;;;;;AAwBA,MAAM,aAAa;AACnB,MAAM,yBAAyB;;AAE/B,MAAM,sBAAsB;AAC5B,MAAM,0CAA0B,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,MAAa,4BAA4B;AAyBzC,IAAa,mBAAb,cAAsCA,sBAAAA,aAAa;CACjD,OAAO;CACP;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,YAAY,SAAiC,CAAC,GAAG;EAC/C,MAAM,MAAM;EAEZ,MAAM,YAAY,OAAO,aAAa,QAAQ,IAAI;EAClD,MAAM,YAAY,OAAO,aAAa,QAAQ,IAAI;EAClD,MAAM,UAAU,qBAAqB,OAAO,WAAW,QAAQ,IAAI,qBAAA,4BAA8C;EACjH,KAAK,YAAY,OAAO,YAAY;EACpC,KAAK,sBAAsB,OAAO;EAElC,IAAI,CAAC,aAAa,CAAC,WAAW;GAC5B,MAAM,kBAAkB,OAAO,YAC3B,gBACA,QAAQ,IAAI,sBACV,aACA;GACN,MAAM,kBAAkB,OAAO,YAC3B,gBACA,QAAQ,IAAI,sBACV,aACA;GACN,KAAK,YACH,GAAG,WAAW,4CAA4C,gBAAgB,eAAe,gBAAgB,iGAE3G;GACA;EACF;EAEA,KAAK,aAAa,IAAIC,eAAAA,sBAAsB;GAC1C;GACA;GACA;GACA,mBAAmB,OAAO;GAC1B,aAAa,OAAO;GACpB,SAAS,OAAO;GAChB,YAAY,KAAK,YAAY,cAAc;GAC3C,SAAS,OAAO;GAChB,eAAe,OAAO;GAGtB,wBAAwB;EAC1B,CAAC;EAED,KAAK,UAAU,IAAIC,iBAAAA,eAAe;GAChC;GACA;GACA;GACA,mBAAmB,OAAO;EAC5B,CAAC;EAED,KAAK,eAAe,OAAO,eAAe,QAAQ,IAAI;EACtD,KAAK,WAAW,OAAO,WAAW,QAAQ,IAAI;CAChD;CAEA,KAAK,SAA8B;EACjC,KAAK,iBAAiB,IAAIC,sBAAAA,cAAc;GACtC,aAAa;GACb,aAAa,QAAQ,QAAQ;GAC7B,QAAQ,EAAE,oBAAoB,KAAK,oBAAoB;GACvD,QAAQ;EACV,CAAC;CACH;CAEA,MAAgB,oBAAoB,OAAoC;EACtE,IAAI,MAAM,SAASC,2BAAAA,iBAAiB,YAAY;EAChD,IAAI,CAAC,KAAK,YAAY;EAEtB,MAAM,KAAK,WAAW,MAAM,YAAY;CAC1C;CAEA,MAAc,WAAW,MAAsC;EAC7D,IAAI,CAAC,KAAK,gBAER,KAAK,iBAAiB,IAAID,sBAAAA,cAAc;GACtC,aAAa;GACb,aAAa;GACb,QAAQ,EAAE,oBAAoB,KAAK,oBAAoB;GACvD,QAAQ;EACV,CAAC;EAGH,IAAI;GACF,MAAM,WAAW,MAAM,KAAK,eAAe,YAAY,IAAI;GAM3D,8BAA8B,SAAS,YAAY,MAAM,KAAK,cAAc,KAAK,QAAQ;GAEzF,KAAK,WAAY,MAAM,QAAQ;EACjC,SAAS,OAAO;GACd,KAAK,OAAO,MAAM,GAAG,WAAW,yBAAyB,KAAK,GAAG,IAAI,KAAK;EAC5E;CACF;;;;;CAMA,IAAI,SAAqC;EACvC,OAAO,KAAK;CACd;;;;;CAMA,YAAoB,MAQX;EACP,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,EAAE,IAAI,SAAS,QAAQ,MAAM,OAAO,SAAS,aAAa;EAChE,IAAI;GACF,KAAK,QAAQ,MAAM,OAAO;IACxB;IACA;IACA,GAAI,SAAS,EAAE,eAAe,OAAO,IAAI,CAAC;IAC1C;IACA;IACA,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;IAC7B,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;IAC/B,GAAI,KAAK,eAAe,EAAE,aAAa,KAAK,aAAa,IAAI,CAAC;IAC9D,UAAU;GACZ,CAAC;EACH,SAAS,OAAO;GACd,KAAK,OAAO,MAAM,GAAG,WAAW,0BAA0B;IACxD;IACA;IACA;IACA;GACF,CAAC;EACH;CACF;CAEA,MAAM,aAAa,OAAkC;EACnD,MAAM,EAAE,UAAU;EAClB,IAAI,CAAC,MAAM,SAAS;EACpB,KAAK,YAAY;GACf,IAAI,MAAM;GACV,SAAS,MAAM;GACf,QAAQ,MAAM;GACd,MAAM,MAAM,cAAc,MAAM;GAChC,OAAO,MAAM;GACb,SAAS,MAAM;GACf,UAAU,MAAM;EAClB,CAAC;CACH;;;;;;CAOA,MAAM,gBAAgB,EACpB,SACA,QACA,OACA,QACA,YACA,YAQgB;EAChB,KAAK,YAAY;GACf,IAAI,GAAG,QAAQ,GAAG,UAAU,GAAG,GAAG;GAClC;GACA;GACA,MAAM;GACN,OAAO;GACP,SAAS;GACT;EACF,CAAC;CACH;CAEA,MAAM,QAAuB;EAC3B,MAAM,QAAQ,IAAI,CAAC,KAAK,YAAY,WAAW,GAAG,KAAK,SAAS,MAAM,CAAC,CAAC;CAC1E;CAEA,MAAM,WAA0B;EAC9B,MAAM,QAAQ,IAAI,CAAC,KAAK,YAAY,SAAS,GAAG,KAAK,SAAS,SAAS,CAAC,CAAC;CAC3E;AACF;;;;;;;;;;;;;;;;AAiBA,SAAS,iBAAiB,OAAoC;CAC5D,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;CAChC,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI;EACF,OAAO,KAAK,UAAU,KAAK;CAC7B,QAAQ;EACN;CACF;AACF;AAEA,SAAS,8BACP,YACA,MACA,aACA,SACM;CACN,IAAI,WAAW,qCAAqC,KAAA,GAAW;EAC7D,WAAW,0CAA0C,WAAW;EAChE,OAAO,WAAW;CACpB;CAMA,MAAM,SAAS,KAAK,sBAAsB;CAG1C,IAAI,aACF,WAAW,0BAA0B;CAEvC,IAAI,SACF,WAAW,sBAAsB;CAUnC,MAAM,mBAAmB,WAAW;CACpC,IAAI,kBAAkB;EACpB,IAAI;GACF,MAAM,SAAS,OAAO,qBAAqB,WAAW,KAAK,MAAM,gBAAgB,IAAI;GACrF,IAAI,UAAU,OAAO,WAAW,UAAU;IAExC,IAAI,OAAO,QAAQ;KACjB,IAAI,OAAO,OAAO,SAAS,KAAA,GACzB,WAAW,sCAAsC,OAAO,OAAO;KAEjE,IAAI,OAAO,OAAO,YAAY,KAAA,GAC5B,WAAW,yCAAyC,OAAO,OAAO;IAEtE;IAKA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;KACjD,IAAI,UAAU,QAAQ,YAAY,UAAU,QAAQ,UAAU,KAAA,GAC5D;KAEF,MAAM,WAAW,2BAA2B;KAE5C,IAAI,WAAW,cAAc,KAAA,GAI3B,WAAW,YAAY,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,KAAK;IAEnF;GACF;EACF,QAAQ,CAER;EACA,IAAI,CAAC,QACH,OAAO,WAAW;CAEtB;CAGA,IAAI,WAAW,iCAAiC;EAC9C,WAAW,gDAAgD,WAAW;EACtE,OAAO,WAAW;CACpB;CAIA,MAAM,cACJ,KAAK,SAASE,2BAAAA,SAAS,mBAClB,KAAK,YAAsD,cAC5D,KAAA;CACN,IACE,aAAa,cAAc,WAAW,uBACtC,YAAY,aAAa,SACzB,OAAO,YAAY,kBAAkB,YACrC,OAAO,SAAS,YAAY,aAAa,KACzC,YAAY,iBAAiB,GAE7B,WAAW,uCAAuC,KAAK,UAAU,EAC/D,OAAO,YAAY,cACrB,CAAC;CAIH,IAAI,CAAC,UAAU,WAAW,2BAA2B;EACnD,WAAW,aAAa,WAAW;EACnC,OAAO,WAAW;CACpB;CAIA,MAAM,mBAAmB,KAAK,WAAW;CACzC,MAAM,YACJ,WAAW,iCACV,OAAO,qBAAqB,YAAY,mBACrC,mBACA,WAAW;CACjB,OAAO,WAAW,GAAG,yBAAyB;CAC9C,IAAI,CAAC,UAAU,WAAW;EACxB,WAAW,gBAAgB;EAC3B,OAAO,WAAW;EAClB,OAAO,WAAW;CACpB;CAGA,IAAI,WAAW,gBAAgB;EAC7B,WAAW,yBAAyB,WAAW;EAC/C,OAAO,WAAW;CACpB;CAGA,IAAI,CAAC,UAAU,WAAW,8BAA8B;EACtD,WAAW,yBAAyB,WAAW;EAC/C,OAAO,WAAW;CACpB;CAGA,IAAI,WAAW,4BAA4B;EACzC,WAAW,sBAAsB,WAAW;EAC5C,OAAO,WAAW;CACpB;CAQA,IAAI,KAAK,cAAc,CAAC,QAAQ;EAe9B,MAAM,aADJ,KAAK,iBAAiB,KAAA,KAAa,KAAK,iBAAiB,KAAK,UAAU,oBACjC,KAAA,IAAY,iBAAiB,KAAK,KAAK;EAChF,IAAI,eAAe,KAAA,GACjB,WAAW,0BAA0B;EAEvC,MAAM,cAAc,iBAAiB,KAAK,MAAM;EAChD,IAAI,gBAAgB,KAAA,GAClB,WAAW,2BAA2B;EAExC,IAAI,KAAK,SAASA,2BAAAA,SAAS,WAAW;GACpC,IAAI,CAAC,WAAW,2BAA2B,KAAK,cAAc,KAAK,WACjE,WAAW,yBAAyB,KAAK,cAAc,KAAK;GAE9D,IAAI,KAAK,UACP,WAAW,qCAAqC,KAAK;GAEvD,IAAI,KAAK,YACP,WAAW,uCAAuC,KAAK;EAE3D,OAAO,IAAI,KAAK,SAASA,2BAAAA,SAAS,cAAc;GAC9C,IAAI,CAAC,WAAW,2BAA2B,KAAK,cAAc,KAAK,WACjE,WAAW,yBAAyB,KAAK,cAAc,KAAK;GAE9D,IAAI,KAAK,UACP,WAAW,wCAAwC,KAAK;GAE1D,IAAI,KAAK,YACP,WAAW,0CAA0C,KAAK;EAE9D;EAQA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,GAAG;GACrD,IAAI,CAAC,IAAI,WAAW,sBAAsB,GACxC;GAEF,MAAM,cAAc,IAAI,MAAM,EAA6B;GAC3D,IAAI,wBAAwB,IAAI,WAAW,KAAK,UAAU,QAAQ,UAAU,KAAA,GAC1E;GAEF,MAAM,WAAW,2BAA2B;GAC5C,IAAI,WAAW,cAAc,KAAA,GAC3B;GAEF,MAAM,aAAa,iBAAiB,KAAK;GACzC,IAAI,eAAe,KAAA,GACjB,WAAW,YAAY;EAE3B;CACF;CAKA,IAAI,WAAW,oBACb,WAAW,2CAA2C,WAAW;CAEnE,IAAI,WAAW,sBACb,WAAW,6CAA6C,WAAW;CAErE,IAAI,WAAW,qBACb,WAAW,4CAA4C,WAAW;CAEpE,IAAI,WAAW,0BACb,WAAW,iDAAiD,WAAW;CAUzE,IAAI,KAAK,MAAM;EACb,MAAM,iBAAiB,UAAU,OAAO,KAAK,IAAI,CAAC,CAAC,YAAY,EAAE;EACjE,MAAM,gBAAiB,KAAK,cAAc,CAAC;EAC3C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,GAAG;GACrD,IAAI,CAAC,IAAI,WAAW,cAAc,KAAK,UAAU,QAAQ,UAAU,KAAA,GACjE;GAEF,MAAM,OAAO,aAAa,IAAI,MAAM,eAAe,MAAM,CAAC;GAC1D,IAAI,SAAS,WAAW,SAAS,UAC/B;GAEF,MAAM,MAAM,cAAc;GAC1B,IAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,EAAE,eAAe,OAC9D;GAEF,MAAM,cAAc,iCAAiC;GACrD,IAAI,WAAW,iBAAiB,KAAA,GAC9B,WAAW,eAAe,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,KAAK;EAEtF;CACF;CAKA,IAAI,CAAC,WAAW,4BAA4B,CAAC,WAAW,+BACjD;OAAA,MAAM,OAAO,OAAO,KAAK,UAAU,GACtC,IAAI,IAAI,WAAW,SAAS,KAAK,IAAI,SAAS,QAAQ,GAAG;GACvD,WAAW,gCAAgC,WAAW;GACtD,OAAO,WAAW;GAClB;EACF;;CAGJ,IAAI,CAAC,WAAW,6BAA6B,CAAC,WAAW,4BAClD;OAAA,MAAM,OAAO,OAAO,KAAK,UAAU,GACtC,IAAI,IAAI,WAAW,SAAS,KAAK,IAAI,SAAS,SAAS,GAAG;GACxD,WAAW,iCAAiC,WAAW;GACvD,OAAO,WAAW;GAClB;EACF;;AAGN;AAEA,SAAS,aAAa,MAAsB;CAC1C,OAAO,KAAK,QAAQ,iBAAiB,GAAG,MAAc,EAAE,YAAY,CAAC;AACvE;;;;;;AAOA,SAAS,qBAAqB,GAAmB;CAC/C,IAAI,MAAM,EAAE;CACZ,OAAO,MAAM,KAAK,EAAE,WAAW,MAAM,CAAC,MAAM,IAC1C;CAEF,OAAO,QAAQ,EAAE,SAAS,IAAI,EAAE,MAAM,GAAG,GAAG;AAC9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACngBA,SAAgB,mBAAmB,QAAoD;CACrF,QAAO,UAAS;EACd,GAAG;EACH,UAAU;GACR,GAAG,KAAK;GACR,UAAU;IACR,GAAI,KAAK,UAAU;IACnB,QAAQ;KACN,GAAI,OAAO,SAAS,KAAA,KAAa,EAAE,MAAM,OAAO,KAAK;KACrD,GAAI,OAAO,YAAY,KAAA,KAAa,EAAE,SAAS,OAAO,QAAQ;KAC9D,GAAI,OAAO,OAAO,KAAA,KAAa,EAAE,IAAI,OAAO,GAAG;IACjD;GACF;EACF;CACF;AACF"}
1
+ {"version":3,"file":"index.cjs","names":["BaseExporter","LangfuseSpanProcessor","LangfuseClient","SpanConverter","TracingEventType","SpanType"],"sources":["../src/tracing.ts","../src/helpers.ts"],"sourcesContent":["/**\n * Langfuse Exporter for Mastra Observability\n *\n * Sends observability data to Langfuse using the official @langfuse/otel span processor\n * and @langfuse/client for non-tracing features (scoring, prompt management, evaluations).\n *\n * @see https://langfuse.com/docs/observability/sdk/typescript/overview\n */\n\nimport { LangfuseClient } from '@langfuse/client';\nimport { LangfuseSpanProcessor } from '@langfuse/otel';\nimport type {\n TracingEvent,\n AnyExportedSpan,\n InitExporterOptions,\n ModelGenerationAttributes,\n ScoreEvent,\n} from '@mastra/core/observability';\nimport { SpanType, TracingEventType } from '@mastra/core/observability';\nimport { BaseExporter } from '@mastra/observability';\nimport type { BaseExporterConfig } from '@mastra/observability';\nimport { SpanConverter } from '@mastra/otel-exporter';\nimport type { OtelExporterConfig } from '@mastra/otel-exporter';\n\nconst LOG_PREFIX = '[LangfuseExporter]';\nconst MASTRA_METADATA_PREFIX = 'mastra.metadata.';\n/** Metadata keys mapped to dedicated Langfuse fields; never forwarded as trace metadata. */\nconst OM_CALLER_THREAD_ID = '__mastraObservationalMemoryCallerThreadId';\nconst DEDICATED_METADATA_KEYS = new Set([\n 'userId',\n 'sessionId',\n 'threadId',\n 'traceName',\n 'version',\n 'langfuse',\n OM_CALLER_THREAD_ID,\n]);\n\nexport const LANGFUSE_DEFAULT_BASE_URL = 'https://cloud.langfuse.com';\n\nexport interface LangfuseExporterConfig extends BaseExporterConfig {\n /** Langfuse public key */\n publicKey?: string;\n /** Langfuse secret key */\n secretKey?: string;\n /** Langfuse host URL (defaults to https://cloud.langfuse.com) */\n baseUrl?: string;\n /** Additional headers sent with requests to Langfuse */\n additionalHeaders?: Record<string, string>;\n /** Enable realtime mode - flushes after each event for immediate visibility */\n realtime?: boolean;\n /** Maximum number of spans per OTEL export batch */\n flushAt?: number;\n /** Maximum time in seconds before pending spans are exported */\n flushInterval?: number;\n /** Langfuse environment tag for traces */\n environment?: string;\n /** Langfuse release tag for traces */\n release?: string;\n /** Override OpenTelemetry resource attributes, including service.version. */\n resourceAttributes?: OtelExporterConfig['resourceAttributes'];\n}\n\nexport class LangfuseExporter extends BaseExporter {\n name = 'langfuse';\n #processor: LangfuseSpanProcessor | undefined;\n #client: LangfuseClient | undefined;\n #spanConverter: SpanConverter | undefined;\n #realtime: boolean;\n #environment: string | undefined;\n #release: string | undefined;\n #resourceAttributes: OtelExporterConfig['resourceAttributes'];\n\n constructor(config: LangfuseExporterConfig = {}) {\n super(config);\n\n const publicKey = config.publicKey ?? process.env.LANGFUSE_PUBLIC_KEY;\n const secretKey = config.secretKey ?? process.env.LANGFUSE_SECRET_KEY;\n const baseUrl = stripTrailingSlashes(config.baseUrl ?? process.env.LANGFUSE_BASE_URL ?? LANGFUSE_DEFAULT_BASE_URL);\n this.#realtime = config.realtime ?? false;\n this.#resourceAttributes = config.resourceAttributes;\n\n if (!publicKey || !secretKey) {\n const publicKeySource = config.publicKey\n ? 'from config'\n : process.env.LANGFUSE_PUBLIC_KEY\n ? 'from env'\n : 'missing';\n const secretKeySource = config.secretKey\n ? 'from config'\n : process.env.LANGFUSE_SECRET_KEY\n ? 'from env'\n : 'missing';\n this.setDisabled(\n `${LOG_PREFIX} Missing required credentials (publicKey: ${publicKeySource}, secretKey: ${secretKeySource}). ` +\n `Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY environment variables or pass them in config.`,\n );\n return;\n }\n\n this.#processor = new LangfuseSpanProcessor({\n publicKey,\n secretKey,\n baseUrl,\n additionalHeaders: config.additionalHeaders,\n environment: config.environment,\n release: config.release,\n exportMode: this.#realtime ? 'immediate' : 'batched',\n flushAt: config.flushAt,\n flushInterval: config.flushInterval,\n // Export all spans — the default filter only passes spans with gen_ai.* attributes\n // or known LLM instrumentation scopes, but Mastra spans use mastra.* attributes.\n shouldExportSpan: () => true,\n });\n\n this.#client = new LangfuseClient({\n publicKey,\n secretKey,\n baseUrl,\n additionalHeaders: config.additionalHeaders,\n });\n\n this.#environment = config.environment ?? process.env.LANGFUSE_TRACING_ENVIRONMENT;\n this.#release = config.release ?? process.env.LANGFUSE_RELEASE;\n }\n\n init(options: InitExporterOptions) {\n this.#spanConverter = new SpanConverter({\n packageName: '@mastra/langfuse',\n serviceName: options.config?.serviceName,\n config: { resourceAttributes: this.#resourceAttributes },\n format: 'GenAI_v1_38_0',\n });\n }\n\n protected async _exportTracingEvent(event: TracingEvent): Promise<void> {\n if (event.type !== TracingEventType.SPAN_ENDED) return;\n if (!this.#processor) return;\n\n await this.exportSpan(event.exportedSpan);\n }\n\n private async exportSpan(span: AnyExportedSpan): Promise<void> {\n if (!this.#spanConverter) {\n // Fallback if init() was not called (e.g., standalone usage without Mastra)\n this.#spanConverter = new SpanConverter({\n packageName: '@mastra/langfuse',\n serviceName: 'mastra-service',\n config: { resourceAttributes: this.#resourceAttributes },\n format: 'GenAI_v1_38_0',\n });\n }\n\n try {\n const otelSpan = await this.#spanConverter.convertSpan(span);\n\n // Map mastra.* attributes to langfuse.* namespace so that Langfuse's OTLP\n // endpoint reads them correctly. SpanConverter produces mastra.* attributes,\n // but Langfuse only reads langfuse.* attributes for prompt linking, TTFT, etc.\n // @see https://langfuse.com/integrations/native/opentelemetry#property-mapping\n mapMastraToLangfuseAttributes(otelSpan.attributes, span, this.#environment, this.#release);\n\n this.#processor!.onEnd(otelSpan);\n } catch (error) {\n this.logger.error(`${LOG_PREFIX} Failed to export span ${span.id}:`, error);\n }\n }\n\n /**\n * The LangfuseClient instance for advanced Langfuse features.\n * Use this for prompt management, evaluations, datasets, and direct API access.\n */\n get client(): LangfuseClient | undefined {\n return this.#client;\n }\n\n /**\n * Submit a score to Langfuse. Used by both the new `onScoreEvent` path and the\n * deprecated `addScoreToTrace` wrapper.\n */\n private submitScore(args: {\n id: string;\n traceId: string;\n spanId?: string;\n name: string;\n value: number;\n comment?: string;\n metadata?: Record<string, unknown>;\n }): void {\n if (!this.#client) return;\n\n const { id, traceId, spanId, name, value, comment, metadata } = args;\n try {\n this.#client.score.create({\n id,\n traceId,\n ...(spanId ? { observationId: spanId } : {}),\n name,\n value,\n ...(comment ? { comment } : {}),\n ...(metadata ? { metadata } : {}),\n ...(this.#environment ? { environment: this.#environment } : {}),\n dataType: 'NUMERIC' as const,\n });\n } catch (error) {\n this.logger.error(`${LOG_PREFIX} Error submitting score`, {\n error,\n traceId,\n spanId,\n name,\n });\n }\n }\n\n async onScoreEvent(event: ScoreEvent): Promise<void> {\n const { score } = event;\n if (!score.traceId) return;\n this.submitScore({\n id: score.scoreId,\n traceId: score.traceId,\n spanId: score.spanId,\n name: score.scorerName ?? score.scorerId,\n value: score.score,\n comment: score.reason,\n metadata: score.metadata,\n });\n }\n\n /**\n * @deprecated Use the observability score event pipeline (`mastra.observability.addScore`)\n * instead. This method is preserved for backwards compatibility and forwards to the same\n * underlying client call as `onScoreEvent`.\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 this.submitScore({\n id: `${traceId}-${spanId || ''}-${scorerName}`,\n traceId,\n spanId,\n name: scorerName,\n value: score,\n comment: reason,\n metadata,\n });\n }\n\n async flush(): Promise<void> {\n await Promise.all([this.#processor?.forceFlush(), this.#client?.flush()]);\n }\n\n async shutdown(): Promise<void> {\n await Promise.all([this.#processor?.shutdown(), this.#client?.shutdown()]);\n }\n}\n\n/**\n * Maps Mastra-specific OTel attributes to the langfuse.* namespace that\n * Langfuse's OTLP endpoint reads for prompt linking, TTFT, and other features.\n *\n * SpanConverter produces attributes like mastra.metadata.*, mastra.completion_start_time, etc.\n * Langfuse's OTLP server only reads langfuse.observation.prompt.name, langfuse.observation.completion_start_time, etc.\n *\n * This function mutates the attributes object in place.\n * @see https://langfuse.com/integrations/native/opentelemetry#property-mapping\n */\n/**\n * Serialize a root span's input/output for the trace-level attributes.\n * Returns undefined (attribute omitted) for absent values and for values that\n * cannot be JSON-serialized, so a bad payload never fails the span export.\n */\nfunction serializeTraceIo(value: unknown): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value);\n } catch {\n return undefined;\n }\n}\n\nfunction mapMastraToLangfuseAttributes(\n attributes: Record<string, any>,\n span: AnyExportedSpan,\n environment?: string,\n release?: string,\n): void {\n if (attributes['gen_ai.usage.reasoning_tokens'] !== undefined) {\n attributes['gen_ai.usage.reasoning.output_tokens'] = attributes['gen_ai.usage.reasoning_tokens'];\n delete attributes['gen_ai.usage.reasoning_tokens'];\n }\n\n // A run attached under an existing span (tracingOptions.nestUnderParent) does\n // not own the trace. Langfuse applies trace-level attributes from any span,\n // so none of its spans may write them: they would replace the outer run's.\n // The skipped values stay on the observation as mastra.metadata.*.\n const nested = span.nestedUnderParent === true;\n\n // Environment and release: set directly since onStart() is not called\n if (environment) {\n attributes['langfuse.environment'] = environment;\n }\n if (release) {\n attributes['langfuse.release'] = release;\n }\n\n // mastra.metadata.langfuse holds a user-supplied object with two kinds of keys:\n // - the reserved `prompt` key, used for prompt linking, and\n // - arbitrary custom keys the user wants as top-level, filterable trace metadata.\n // Langfuse only allows filtering/grouping traces by top-level metadata\n // (langfuse.trace.metadata.*), so forward every non-prompt key there. These\n // attributes may be set on any span in the trace and land on the trace record.\n // @see https://langfuse.com/integrations/native/opentelemetry#property-mapping\n const langfuseMetadata = attributes['mastra.metadata.langfuse'];\n if (langfuseMetadata) {\n try {\n const parsed = typeof langfuseMetadata === 'string' ? JSON.parse(langfuseMetadata) : langfuseMetadata;\n if (parsed && typeof parsed === 'object') {\n // Prompt linking: mastra.metadata.langfuse.prompt → langfuse.observation.prompt.name / version\n if (parsed.prompt) {\n if (parsed.prompt.name !== undefined) {\n attributes['langfuse.observation.prompt.name'] = parsed.prompt.name;\n }\n if (parsed.prompt.version !== undefined) {\n attributes['langfuse.observation.prompt.version'] = parsed.prompt.version;\n }\n }\n\n // Custom keys: mastra.metadata.langfuse.<key> → langfuse.trace.metadata.<key>\n // Reserved identity keys (agentId/agentName/workflowId/workflowName) are set\n // by the root-span block below, which runs after this loop and takes precedence.\n for (const [key, value] of Object.entries(parsed)) {\n if (nested || key === 'prompt' || value === null || value === undefined) {\n continue;\n }\n const traceKey = `langfuse.trace.metadata.${key}`;\n // Don't overwrite a trace.metadata key already mapped from another attribute.\n if (attributes[traceKey] === undefined) {\n // Langfuse maps langfuse.trace.metadata.* as string attributes, so\n // serialize non-strings with JSON. Langfuse Cloud restores numbers,\n // booleans, and objects to their original types on ingestion.\n attributes[traceKey] = typeof value === 'string' ? value : JSON.stringify(value);\n }\n }\n }\n } catch {\n // best effort — invalid JSON is silently ignored\n }\n if (!nested) {\n delete attributes['mastra.metadata.langfuse'];\n }\n }\n\n // TTFT: mastra.completion_start_time → langfuse.observation.completion_start_time\n if (attributes['mastra.completion_start_time']) {\n attributes['langfuse.observation.completion_start_time'] = attributes['mastra.completion_start_time'];\n delete attributes['mastra.completion_start_time'];\n }\n\n // Exact provider-reported cost takes precedence over Langfuse's model-price inference.\n // Estimated costs remain unexported so Langfuse can apply its own pricing model.\n const costContext =\n span.type === SpanType.MODEL_GENERATION\n ? (span.attributes as ModelGenerationAttributes | undefined)?.costContext\n : undefined;\n if (\n costContext?.costMetadata?.source === 'provider_reported' &&\n costContext.costUnit === 'USD' &&\n typeof costContext.estimatedCost === 'number' &&\n Number.isFinite(costContext.estimatedCost) &&\n costContext.estimatedCost >= 0\n ) {\n attributes['langfuse.observation.cost_details'] = JSON.stringify({\n total: costContext.estimatedCost,\n });\n }\n\n // User ID: mastra.metadata.userId → user.id\n if (!nested && attributes['mastra.metadata.userId']) {\n attributes['user.id'] = attributes['mastra.metadata.userId'];\n delete attributes['mastra.metadata.userId'];\n }\n\n // OM keeps caller identity separate from its isolated execution thread. Read\n // the raw hint: conversion serializes malformed objects/arrays into strings.\n const omCallerThreadId = span.metadata?.[OM_CALLER_THREAD_ID];\n const sessionId =\n attributes['mastra.metadata.sessionId'] ??\n (typeof omCallerThreadId === 'string' && omCallerThreadId\n ? omCallerThreadId\n : attributes['mastra.metadata.threadId']);\n delete attributes[`${MASTRA_METADATA_PREFIX}${OM_CALLER_THREAD_ID}`];\n if (!nested && sessionId) {\n attributes['session.id'] = sessionId;\n delete attributes['mastra.metadata.sessionId'];\n delete attributes['mastra.metadata.threadId'];\n }\n\n // Tags: mastra.tags → langfuse.trace.tags\n if (attributes['mastra.tags']) {\n attributes['langfuse.trace.tags'] = attributes['mastra.tags'];\n delete attributes['mastra.tags'];\n }\n\n // Trace name: mastra.metadata.traceName → langfuse.trace.name\n if (!nested && attributes['mastra.metadata.traceName']) {\n attributes['langfuse.trace.name'] = attributes['mastra.metadata.traceName'];\n delete attributes['mastra.metadata.traceName'];\n }\n\n // Version: mastra.metadata.version → langfuse.version\n if (attributes['mastra.metadata.version']) {\n attributes['langfuse.version'] = attributes['mastra.metadata.version'];\n delete attributes['mastra.metadata.version'];\n }\n\n // Root-span trace identity: scope each Langfuse trace to the entity that\n // started it (agent or workflow). This makes the Langfuse trace name match\n // the agent/workflow id and exposes the same identity as trace metadata, so\n // users can scope Langfuse evaluators per agent via trace name or metadata\n // filters. User-provided traceName (set via mastra.metadata.traceName) takes\n // precedence and is preserved.\n if (span.isRootSpan && !nested) {\n // Trace input/output: mirror the root span's input/output onto the trace.\n // Without this, Langfuse traces have empty trace-level input/output (the\n // span data only reaches the root OBSERVATION), which breaks LLM-as-a-judge\n // evaluators mapped to Trace input/output and leaves the trace view without\n // the top-level request/response. Serialization failures (circular refs,\n // bigint) skip the attribute rather than failing the span export.\n // A resumed run (after a suspend such as tool-call approval) continues the\n // original trace: core marks it with parentSpanId === resumedFromSpanId.\n // Its input is the resume payload, not the user's message, and Langfuse\n // applies trace-level attributes from any span (last writer wins), so it\n // must not replace the trace input the original run already set. Its\n // output IS the turn's final reply, so trace output is still written.\n const resumedContinuation =\n span.parentSpanId !== undefined && span.parentSpanId === span.metadata?.resumedFromSpanId;\n const traceInput = resumedContinuation ? undefined : serializeTraceIo(span.input);\n if (traceInput !== undefined) {\n attributes['langfuse.trace.input'] = traceInput;\n }\n const traceOutput = serializeTraceIo(span.output);\n if (traceOutput !== undefined) {\n attributes['langfuse.trace.output'] = traceOutput;\n }\n if (span.type === SpanType.AGENT_RUN) {\n if (!attributes['langfuse.trace.name'] && (span.entityName || span.entityId)) {\n attributes['langfuse.trace.name'] = span.entityName ?? span.entityId;\n }\n if (span.entityId) {\n attributes['langfuse.trace.metadata.agentId'] = span.entityId;\n }\n if (span.entityName) {\n attributes['langfuse.trace.metadata.agentName'] = span.entityName;\n }\n } else if (span.type === SpanType.WORKFLOW_RUN) {\n if (!attributes['langfuse.trace.name'] && (span.entityName || span.entityId)) {\n attributes['langfuse.trace.name'] = span.entityName ?? span.entityId;\n }\n if (span.entityId) {\n attributes['langfuse.trace.metadata.workflowId'] = span.entityId;\n }\n if (span.entityName) {\n attributes['langfuse.trace.metadata.workflowName'] = span.entityName;\n }\n }\n\n // Root-span metadata: forward the remaining mastra.metadata.* keys (runId,\n // resourceId, user-supplied keys) to langfuse.trace.metadata.* so they are\n // filterable trace metadata; Langfuse nests unmapped attributes under\n // metadata.attributes. Dedicated keys, explicit metadata.langfuse.* values,\n // and the identity keys above take precedence. Root only: Langfuse applies\n // langfuse.trace.* from any span, so a child span could overwrite the trace.\n for (const [key, value] of Object.entries(attributes)) {\n if (!key.startsWith(MASTRA_METADATA_PREFIX)) {\n continue;\n }\n const metadataKey = key.slice(MASTRA_METADATA_PREFIX.length);\n if (DEDICATED_METADATA_KEYS.has(metadataKey) || value === null || value === undefined) {\n continue;\n }\n const traceKey = `langfuse.trace.metadata.${metadataKey}`;\n if (attributes[traceKey] !== undefined) {\n continue;\n }\n const serialized = serializeTraceIo(value);\n if (serialized !== undefined) {\n attributes[traceKey] = serialized;\n }\n }\n }\n\n // Observation metadata: map semantic attributes to langfuse.observation.metadata.*\n // so they become top-level filterable keys on each observation in Langfuse.\n // @see https://langfuse.com/integrations/native/opentelemetry#how-metadata-mapping-works\n if (attributes['gen_ai.agent.id']) {\n attributes['langfuse.observation.metadata.agentId'] = attributes['gen_ai.agent.id'];\n }\n if (attributes['gen_ai.agent.name']) {\n attributes['langfuse.observation.metadata.agentName'] = attributes['gen_ai.agent.name'];\n }\n if (attributes['mastra.span.type']) {\n attributes['langfuse.observation.metadata.spanType'] = attributes['mastra.span.type'];\n }\n if (attributes['gen_ai.operation.name']) {\n attributes['langfuse.observation.metadata.operationName'] = attributes['gen_ai.operation.name'];\n }\n\n // Span-type attributes (mastra.<span_type>.<snake_key>, e.g.\n // mastra.model_step.step_index) are promoted the same way, as camelCase keys,\n // so scalar span attributes are filterable in observation-level evaluator\n // rules without a per-key mapping. Input/output have their own mapping below.\n // Attributes whose raw value is an object or array (entryMetadata,\n // truthyIndexes, ...) are already JSON strings here and make no useful\n // filter, so they stay in the nested attributes only. Values are sent as strings.\n if (span.type) {\n const spanAttrPrefix = `mastra.${String(span.type).toLowerCase()}.`;\n const rawAttributes = (span.attributes ?? {}) as Record<string, unknown>;\n for (const [key, value] of Object.entries(attributes)) {\n if (!key.startsWith(spanAttrPrefix) || value === null || value === undefined) {\n continue;\n }\n const name = snakeToCamel(key.slice(spanAttrPrefix.length));\n if (name === 'input' || name === 'output') {\n continue;\n }\n const raw = rawAttributes[name];\n if (typeof raw === 'object' && raw !== null && !(raw instanceof Date)) {\n continue;\n }\n const metadataKey = `langfuse.observation.metadata.${name}`;\n if (attributes[metadataKey] === undefined) {\n attributes[metadataKey] = typeof value === 'string' ? value : JSON.stringify(value);\n }\n }\n }\n\n // Input/Output: mastra.*.input/output → langfuse.observation.input/output\n // For gen_ai spans, Langfuse reads gen_ai.input.messages natively.\n // For non-gen_ai spans, we map the first mastra.*.input/output we find.\n if (!attributes['gen_ai.input.messages'] && !attributes['gen_ai.tool.call.arguments']) {\n for (const key of Object.keys(attributes)) {\n if (key.startsWith('mastra.') && key.endsWith('.input')) {\n attributes['langfuse.observation.input'] = attributes[key];\n delete attributes[key];\n break;\n }\n }\n }\n if (!attributes['gen_ai.output.messages'] && !attributes['gen_ai.tool.call.result']) {\n for (const key of Object.keys(attributes)) {\n if (key.startsWith('mastra.') && key.endsWith('.output')) {\n attributes['langfuse.observation.output'] = attributes[key];\n delete attributes[key];\n break;\n }\n }\n }\n}\n\nfunction snakeToCamel(name: string): string {\n return name.replace(/_([a-z0-9])/g, (_, c: string) => c.toUpperCase());\n}\n\n/**\n * Remove trailing \"/\" characters procedurally. Avoids polynomial\n * backtracking that a greedy regex like `/\\/+$/` can exhibit when the\n * input is attacker-controlled.\n */\nfunction stripTrailingSlashes(s: string): string {\n let end = s.length;\n while (end > 0 && s.charCodeAt(end - 1) === 47 /* \"/\" */) {\n end--;\n }\n return end === s.length ? s : s.slice(0, end);\n}\n","/**\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 agent = new Agent({\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangfusePrompt({ name: 'my-prompt', version: 1 }),\n * ),\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 /** @deprecated Langfuse v5 only supports linking by name + version. This field is ignored. */\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 (required for Langfuse v5)\n * - `version` - Prompt version (required for Langfuse v5)\n *\n * All fields are deeply merged with any existing metadata.\n *\n * @param prompt - Prompt fields for linking (`name` and `version` required)\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 *\n * // Link a generation to a Langfuse prompt by name and version\n * const tracingOptions = buildTracingOptions(\n * withLangfusePrompt({ name: 'customer-support', version: 1 }),\n * );\n *\n * // Or directly in agent config\n * const agent = new Agent({\n * name: 'support-agent',\n * instructions: 'You are a helpful assistant',\n * model: openai('gpt-4o'),\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangfusePrompt({ name: 'my-prompt', version: 1 }),\n * ),\n * },\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"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { LangfuseSpanProcessor } from "@langfuse/otel";
3
3
  import { SpanType, TracingEventType } from "@mastra/core/observability";
4
4
  import { BaseExporter } from "@mastra/observability";
5
5
  import { SpanConverter } from "@mastra/otel-exporter";
6
- //#region src/tracing.ts
6
+ // src/tracing.ts
7
7
  /**
8
8
  * Langfuse Exporter for Mastra Observability
9
9
  *
@@ -317,8 +317,7 @@ function stripTrailingSlashes(s) {
317
317
  while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
318
318
  return end === s.length ? s : s.slice(0, end);
319
319
  }
320
- //#endregion
321
- //#region src/helpers.ts
320
+ // src/helpers.ts
322
321
  /**
323
322
  * Adds Langfuse prompt metadata to the tracing options
324
323
  * to enable Langfuse Prompt Tracing.
@@ -371,7 +370,6 @@ function withLangfusePrompt(prompt) {
371
370
  }
372
371
  });
373
372
  }
374
- //#endregion
375
373
  export { LANGFUSE_DEFAULT_BASE_URL, LangfuseExporter, withLangfusePrompt };
376
374
 
377
375
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/tracing.ts","../src/helpers.ts"],"sourcesContent":["/**\n * Langfuse Exporter for Mastra Observability\n *\n * Sends observability data to Langfuse using the official @langfuse/otel span processor\n * and @langfuse/client for non-tracing features (scoring, prompt management, evaluations).\n *\n * @see https://langfuse.com/docs/observability/sdk/typescript/overview\n */\n\nimport { LangfuseClient } from '@langfuse/client';\nimport { LangfuseSpanProcessor } from '@langfuse/otel';\nimport type {\n TracingEvent,\n AnyExportedSpan,\n InitExporterOptions,\n ModelGenerationAttributes,\n ScoreEvent,\n} from '@mastra/core/observability';\nimport { SpanType, TracingEventType } from '@mastra/core/observability';\nimport { BaseExporter } from '@mastra/observability';\nimport type { BaseExporterConfig } from '@mastra/observability';\nimport { SpanConverter } from '@mastra/otel-exporter';\nimport type { OtelExporterConfig } from '@mastra/otel-exporter';\n\nconst LOG_PREFIX = '[LangfuseExporter]';\nconst MASTRA_METADATA_PREFIX = 'mastra.metadata.';\n/** Metadata keys mapped to dedicated Langfuse fields; never forwarded as trace metadata. */\nconst OM_CALLER_THREAD_ID = '__mastraObservationalMemoryCallerThreadId';\nconst DEDICATED_METADATA_KEYS = new Set([\n 'userId',\n 'sessionId',\n 'threadId',\n 'traceName',\n 'version',\n 'langfuse',\n OM_CALLER_THREAD_ID,\n]);\n\nexport const LANGFUSE_DEFAULT_BASE_URL = 'https://cloud.langfuse.com';\n\nexport interface LangfuseExporterConfig extends BaseExporterConfig {\n /** Langfuse public key */\n publicKey?: string;\n /** Langfuse secret key */\n secretKey?: string;\n /** Langfuse host URL (defaults to https://cloud.langfuse.com) */\n baseUrl?: string;\n /** Additional headers sent with requests to Langfuse */\n additionalHeaders?: Record<string, string>;\n /** Enable realtime mode - flushes after each event for immediate visibility */\n realtime?: boolean;\n /** Maximum number of spans per OTEL export batch */\n flushAt?: number;\n /** Maximum time in seconds before pending spans are exported */\n flushInterval?: number;\n /** Langfuse environment tag for traces */\n environment?: string;\n /** Langfuse release tag for traces */\n release?: string;\n /** Override OpenTelemetry resource attributes, including service.version. */\n resourceAttributes?: OtelExporterConfig['resourceAttributes'];\n}\n\nexport class LangfuseExporter extends BaseExporter {\n name = 'langfuse';\n #processor: LangfuseSpanProcessor | undefined;\n #client: LangfuseClient | undefined;\n #spanConverter: SpanConverter | undefined;\n #realtime: boolean;\n #environment: string | undefined;\n #release: string | undefined;\n #resourceAttributes: OtelExporterConfig['resourceAttributes'];\n\n constructor(config: LangfuseExporterConfig = {}) {\n super(config);\n\n const publicKey = config.publicKey ?? process.env.LANGFUSE_PUBLIC_KEY;\n const secretKey = config.secretKey ?? process.env.LANGFUSE_SECRET_KEY;\n const baseUrl = stripTrailingSlashes(config.baseUrl ?? process.env.LANGFUSE_BASE_URL ?? LANGFUSE_DEFAULT_BASE_URL);\n this.#realtime = config.realtime ?? false;\n this.#resourceAttributes = config.resourceAttributes;\n\n if (!publicKey || !secretKey) {\n const publicKeySource = config.publicKey\n ? 'from config'\n : process.env.LANGFUSE_PUBLIC_KEY\n ? 'from env'\n : 'missing';\n const secretKeySource = config.secretKey\n ? 'from config'\n : process.env.LANGFUSE_SECRET_KEY\n ? 'from env'\n : 'missing';\n this.setDisabled(\n `${LOG_PREFIX} Missing required credentials (publicKey: ${publicKeySource}, secretKey: ${secretKeySource}). ` +\n `Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY environment variables or pass them in config.`,\n );\n return;\n }\n\n this.#processor = new LangfuseSpanProcessor({\n publicKey,\n secretKey,\n baseUrl,\n additionalHeaders: config.additionalHeaders,\n environment: config.environment,\n release: config.release,\n exportMode: this.#realtime ? 'immediate' : 'batched',\n flushAt: config.flushAt,\n flushInterval: config.flushInterval,\n // Export all spans — the default filter only passes spans with gen_ai.* attributes\n // or known LLM instrumentation scopes, but Mastra spans use mastra.* attributes.\n shouldExportSpan: () => true,\n });\n\n this.#client = new LangfuseClient({\n publicKey,\n secretKey,\n baseUrl,\n additionalHeaders: config.additionalHeaders,\n });\n\n this.#environment = config.environment ?? process.env.LANGFUSE_TRACING_ENVIRONMENT;\n this.#release = config.release ?? process.env.LANGFUSE_RELEASE;\n }\n\n init(options: InitExporterOptions) {\n this.#spanConverter = new SpanConverter({\n packageName: '@mastra/langfuse',\n serviceName: options.config?.serviceName,\n config: { resourceAttributes: this.#resourceAttributes },\n format: 'GenAI_v1_38_0',\n });\n }\n\n protected async _exportTracingEvent(event: TracingEvent): Promise<void> {\n if (event.type !== TracingEventType.SPAN_ENDED) return;\n if (!this.#processor) return;\n\n await this.exportSpan(event.exportedSpan);\n }\n\n private async exportSpan(span: AnyExportedSpan): Promise<void> {\n if (!this.#spanConverter) {\n // Fallback if init() was not called (e.g., standalone usage without Mastra)\n this.#spanConverter = new SpanConverter({\n packageName: '@mastra/langfuse',\n serviceName: 'mastra-service',\n config: { resourceAttributes: this.#resourceAttributes },\n format: 'GenAI_v1_38_0',\n });\n }\n\n try {\n const otelSpan = await this.#spanConverter.convertSpan(span);\n\n // Map mastra.* attributes to langfuse.* namespace so that Langfuse's OTLP\n // endpoint reads them correctly. SpanConverter produces mastra.* attributes,\n // but Langfuse only reads langfuse.* attributes for prompt linking, TTFT, etc.\n // @see https://langfuse.com/integrations/native/opentelemetry#property-mapping\n mapMastraToLangfuseAttributes(otelSpan.attributes, span, this.#environment, this.#release);\n\n this.#processor!.onEnd(otelSpan);\n } catch (error) {\n this.logger.error(`${LOG_PREFIX} Failed to export span ${span.id}:`, error);\n }\n }\n\n /**\n * The LangfuseClient instance for advanced Langfuse features.\n * Use this for prompt management, evaluations, datasets, and direct API access.\n */\n get client(): LangfuseClient | undefined {\n return this.#client;\n }\n\n /**\n * Submit a score to Langfuse. Used by both the new `onScoreEvent` path and the\n * deprecated `addScoreToTrace` wrapper.\n */\n private submitScore(args: {\n id: string;\n traceId: string;\n spanId?: string;\n name: string;\n value: number;\n comment?: string;\n metadata?: Record<string, unknown>;\n }): void {\n if (!this.#client) return;\n\n const { id, traceId, spanId, name, value, comment, metadata } = args;\n try {\n this.#client.score.create({\n id,\n traceId,\n ...(spanId ? { observationId: spanId } : {}),\n name,\n value,\n ...(comment ? { comment } : {}),\n ...(metadata ? { metadata } : {}),\n ...(this.#environment ? { environment: this.#environment } : {}),\n dataType: 'NUMERIC' as const,\n });\n } catch (error) {\n this.logger.error(`${LOG_PREFIX} Error submitting score`, {\n error,\n traceId,\n spanId,\n name,\n });\n }\n }\n\n async onScoreEvent(event: ScoreEvent): Promise<void> {\n const { score } = event;\n if (!score.traceId) return;\n this.submitScore({\n id: score.scoreId,\n traceId: score.traceId,\n spanId: score.spanId,\n name: score.scorerName ?? score.scorerId,\n value: score.score,\n comment: score.reason,\n metadata: score.metadata,\n });\n }\n\n /**\n * @deprecated Use the observability score event pipeline (`mastra.observability.addScore`)\n * instead. This method is preserved for backwards compatibility and forwards to the same\n * underlying client call as `onScoreEvent`.\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 this.submitScore({\n id: `${traceId}-${spanId || ''}-${scorerName}`,\n traceId,\n spanId,\n name: scorerName,\n value: score,\n comment: reason,\n metadata,\n });\n }\n\n async flush(): Promise<void> {\n await Promise.all([this.#processor?.forceFlush(), this.#client?.flush()]);\n }\n\n async shutdown(): Promise<void> {\n await Promise.all([this.#processor?.shutdown(), this.#client?.shutdown()]);\n }\n}\n\n/**\n * Maps Mastra-specific OTel attributes to the langfuse.* namespace that\n * Langfuse's OTLP endpoint reads for prompt linking, TTFT, and other features.\n *\n * SpanConverter produces attributes like mastra.metadata.*, mastra.completion_start_time, etc.\n * Langfuse's OTLP server only reads langfuse.observation.prompt.name, langfuse.observation.completion_start_time, etc.\n *\n * This function mutates the attributes object in place.\n * @see https://langfuse.com/integrations/native/opentelemetry#property-mapping\n */\n/**\n * Serialize a root span's input/output for the trace-level attributes.\n * Returns undefined (attribute omitted) for absent values and for values that\n * cannot be JSON-serialized, so a bad payload never fails the span export.\n */\nfunction serializeTraceIo(value: unknown): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value);\n } catch {\n return undefined;\n }\n}\n\nfunction mapMastraToLangfuseAttributes(\n attributes: Record<string, any>,\n span: AnyExportedSpan,\n environment?: string,\n release?: string,\n): void {\n if (attributes['gen_ai.usage.reasoning_tokens'] !== undefined) {\n attributes['gen_ai.usage.reasoning.output_tokens'] = attributes['gen_ai.usage.reasoning_tokens'];\n delete attributes['gen_ai.usage.reasoning_tokens'];\n }\n\n // A run attached under an existing span (tracingOptions.nestUnderParent) does\n // not own the trace. Langfuse applies trace-level attributes from any span,\n // so none of its spans may write them: they would replace the outer run's.\n // The skipped values stay on the observation as mastra.metadata.*.\n const nested = span.nestedUnderParent === true;\n\n // Environment and release: set directly since onStart() is not called\n if (environment) {\n attributes['langfuse.environment'] = environment;\n }\n if (release) {\n attributes['langfuse.release'] = release;\n }\n\n // mastra.metadata.langfuse holds a user-supplied object with two kinds of keys:\n // - the reserved `prompt` key, used for prompt linking, and\n // - arbitrary custom keys the user wants as top-level, filterable trace metadata.\n // Langfuse only allows filtering/grouping traces by top-level metadata\n // (langfuse.trace.metadata.*), so forward every non-prompt key there. These\n // attributes may be set on any span in the trace and land on the trace record.\n // @see https://langfuse.com/integrations/native/opentelemetry#property-mapping\n const langfuseMetadata = attributes['mastra.metadata.langfuse'];\n if (langfuseMetadata) {\n try {\n const parsed = typeof langfuseMetadata === 'string' ? JSON.parse(langfuseMetadata) : langfuseMetadata;\n if (parsed && typeof parsed === 'object') {\n // Prompt linking: mastra.metadata.langfuse.prompt → langfuse.observation.prompt.name / version\n if (parsed.prompt) {\n if (parsed.prompt.name !== undefined) {\n attributes['langfuse.observation.prompt.name'] = parsed.prompt.name;\n }\n if (parsed.prompt.version !== undefined) {\n attributes['langfuse.observation.prompt.version'] = parsed.prompt.version;\n }\n }\n\n // Custom keys: mastra.metadata.langfuse.<key> → langfuse.trace.metadata.<key>\n // Reserved identity keys (agentId/agentName/workflowId/workflowName) are set\n // by the root-span block below, which runs after this loop and takes precedence.\n for (const [key, value] of Object.entries(parsed)) {\n if (nested || key === 'prompt' || value === null || value === undefined) {\n continue;\n }\n const traceKey = `langfuse.trace.metadata.${key}`;\n // Don't overwrite a trace.metadata key already mapped from another attribute.\n if (attributes[traceKey] === undefined) {\n // Langfuse maps langfuse.trace.metadata.* as string attributes, so\n // serialize non-strings with JSON. Langfuse Cloud restores numbers,\n // booleans, and objects to their original types on ingestion.\n attributes[traceKey] = typeof value === 'string' ? value : JSON.stringify(value);\n }\n }\n }\n } catch {\n // best effort — invalid JSON is silently ignored\n }\n if (!nested) {\n delete attributes['mastra.metadata.langfuse'];\n }\n }\n\n // TTFT: mastra.completion_start_time → langfuse.observation.completion_start_time\n if (attributes['mastra.completion_start_time']) {\n attributes['langfuse.observation.completion_start_time'] = attributes['mastra.completion_start_time'];\n delete attributes['mastra.completion_start_time'];\n }\n\n // Exact provider-reported cost takes precedence over Langfuse's model-price inference.\n // Estimated costs remain unexported so Langfuse can apply its own pricing model.\n const costContext =\n span.type === SpanType.MODEL_GENERATION\n ? (span.attributes as ModelGenerationAttributes | undefined)?.costContext\n : undefined;\n if (\n costContext?.costMetadata?.source === 'provider_reported' &&\n costContext.costUnit === 'USD' &&\n typeof costContext.estimatedCost === 'number' &&\n Number.isFinite(costContext.estimatedCost) &&\n costContext.estimatedCost >= 0\n ) {\n attributes['langfuse.observation.cost_details'] = JSON.stringify({\n total: costContext.estimatedCost,\n });\n }\n\n // User ID: mastra.metadata.userId → user.id\n if (!nested && attributes['mastra.metadata.userId']) {\n attributes['user.id'] = attributes['mastra.metadata.userId'];\n delete attributes['mastra.metadata.userId'];\n }\n\n // OM keeps caller identity separate from its isolated execution thread. Read\n // the raw hint: conversion serializes malformed objects/arrays into strings.\n const omCallerThreadId = span.metadata?.[OM_CALLER_THREAD_ID];\n const sessionId =\n attributes['mastra.metadata.sessionId'] ??\n (typeof omCallerThreadId === 'string' && omCallerThreadId\n ? omCallerThreadId\n : attributes['mastra.metadata.threadId']);\n delete attributes[`${MASTRA_METADATA_PREFIX}${OM_CALLER_THREAD_ID}`];\n if (!nested && sessionId) {\n attributes['session.id'] = sessionId;\n delete attributes['mastra.metadata.sessionId'];\n delete attributes['mastra.metadata.threadId'];\n }\n\n // Tags: mastra.tags → langfuse.trace.tags\n if (attributes['mastra.tags']) {\n attributes['langfuse.trace.tags'] = attributes['mastra.tags'];\n delete attributes['mastra.tags'];\n }\n\n // Trace name: mastra.metadata.traceName → langfuse.trace.name\n if (!nested && attributes['mastra.metadata.traceName']) {\n attributes['langfuse.trace.name'] = attributes['mastra.metadata.traceName'];\n delete attributes['mastra.metadata.traceName'];\n }\n\n // Version: mastra.metadata.version → langfuse.version\n if (attributes['mastra.metadata.version']) {\n attributes['langfuse.version'] = attributes['mastra.metadata.version'];\n delete attributes['mastra.metadata.version'];\n }\n\n // Root-span trace identity: scope each Langfuse trace to the entity that\n // started it (agent or workflow). This makes the Langfuse trace name match\n // the agent/workflow id and exposes the same identity as trace metadata, so\n // users can scope Langfuse evaluators per agent via trace name or metadata\n // filters. User-provided traceName (set via mastra.metadata.traceName) takes\n // precedence and is preserved.\n if (span.isRootSpan && !nested) {\n // Trace input/output: mirror the root span's input/output onto the trace.\n // Without this, Langfuse traces have empty trace-level input/output (the\n // span data only reaches the root OBSERVATION), which breaks LLM-as-a-judge\n // evaluators mapped to Trace input/output and leaves the trace view without\n // the top-level request/response. Serialization failures (circular refs,\n // bigint) skip the attribute rather than failing the span export.\n // A resumed run (after a suspend such as tool-call approval) continues the\n // original trace: core marks it with parentSpanId === resumedFromSpanId.\n // Its input is the resume payload, not the user's message, and Langfuse\n // applies trace-level attributes from any span (last writer wins), so it\n // must not replace the trace input the original run already set. Its\n // output IS the turn's final reply, so trace output is still written.\n const resumedContinuation =\n span.parentSpanId !== undefined && span.parentSpanId === span.metadata?.resumedFromSpanId;\n const traceInput = resumedContinuation ? undefined : serializeTraceIo(span.input);\n if (traceInput !== undefined) {\n attributes['langfuse.trace.input'] = traceInput;\n }\n const traceOutput = serializeTraceIo(span.output);\n if (traceOutput !== undefined) {\n attributes['langfuse.trace.output'] = traceOutput;\n }\n if (span.type === SpanType.AGENT_RUN) {\n if (!attributes['langfuse.trace.name'] && (span.entityName || span.entityId)) {\n attributes['langfuse.trace.name'] = span.entityName ?? span.entityId;\n }\n if (span.entityId) {\n attributes['langfuse.trace.metadata.agentId'] = span.entityId;\n }\n if (span.entityName) {\n attributes['langfuse.trace.metadata.agentName'] = span.entityName;\n }\n } else if (span.type === SpanType.WORKFLOW_RUN) {\n if (!attributes['langfuse.trace.name'] && (span.entityName || span.entityId)) {\n attributes['langfuse.trace.name'] = span.entityName ?? span.entityId;\n }\n if (span.entityId) {\n attributes['langfuse.trace.metadata.workflowId'] = span.entityId;\n }\n if (span.entityName) {\n attributes['langfuse.trace.metadata.workflowName'] = span.entityName;\n }\n }\n\n // Root-span metadata: forward the remaining mastra.metadata.* keys (runId,\n // resourceId, user-supplied keys) to langfuse.trace.metadata.* so they are\n // filterable trace metadata; Langfuse nests unmapped attributes under\n // metadata.attributes. Dedicated keys, explicit metadata.langfuse.* values,\n // and the identity keys above take precedence. Root only: Langfuse applies\n // langfuse.trace.* from any span, so a child span could overwrite the trace.\n for (const [key, value] of Object.entries(attributes)) {\n if (!key.startsWith(MASTRA_METADATA_PREFIX)) {\n continue;\n }\n const metadataKey = key.slice(MASTRA_METADATA_PREFIX.length);\n if (DEDICATED_METADATA_KEYS.has(metadataKey) || value === null || value === undefined) {\n continue;\n }\n const traceKey = `langfuse.trace.metadata.${metadataKey}`;\n if (attributes[traceKey] !== undefined) {\n continue;\n }\n const serialized = serializeTraceIo(value);\n if (serialized !== undefined) {\n attributes[traceKey] = serialized;\n }\n }\n }\n\n // Observation metadata: map semantic attributes to langfuse.observation.metadata.*\n // so they become top-level filterable keys on each observation in Langfuse.\n // @see https://langfuse.com/integrations/native/opentelemetry#how-metadata-mapping-works\n if (attributes['gen_ai.agent.id']) {\n attributes['langfuse.observation.metadata.agentId'] = attributes['gen_ai.agent.id'];\n }\n if (attributes['gen_ai.agent.name']) {\n attributes['langfuse.observation.metadata.agentName'] = attributes['gen_ai.agent.name'];\n }\n if (attributes['mastra.span.type']) {\n attributes['langfuse.observation.metadata.spanType'] = attributes['mastra.span.type'];\n }\n if (attributes['gen_ai.operation.name']) {\n attributes['langfuse.observation.metadata.operationName'] = attributes['gen_ai.operation.name'];\n }\n\n // Span-type attributes (mastra.<span_type>.<snake_key>, e.g.\n // mastra.model_step.step_index) are promoted the same way, as camelCase keys,\n // so scalar span attributes are filterable in observation-level evaluator\n // rules without a per-key mapping. Input/output have their own mapping below.\n // Attributes whose raw value is an object or array (entryMetadata,\n // truthyIndexes, ...) are already JSON strings here and make no useful\n // filter, so they stay in the nested attributes only. Values are sent as strings.\n if (span.type) {\n const spanAttrPrefix = `mastra.${String(span.type).toLowerCase()}.`;\n const rawAttributes = (span.attributes ?? {}) as Record<string, unknown>;\n for (const [key, value] of Object.entries(attributes)) {\n if (!key.startsWith(spanAttrPrefix) || value === null || value === undefined) {\n continue;\n }\n const name = snakeToCamel(key.slice(spanAttrPrefix.length));\n if (name === 'input' || name === 'output') {\n continue;\n }\n const raw = rawAttributes[name];\n if (typeof raw === 'object' && raw !== null && !(raw instanceof Date)) {\n continue;\n }\n const metadataKey = `langfuse.observation.metadata.${name}`;\n if (attributes[metadataKey] === undefined) {\n attributes[metadataKey] = typeof value === 'string' ? value : JSON.stringify(value);\n }\n }\n }\n\n // Input/Output: mastra.*.input/output → langfuse.observation.input/output\n // For gen_ai spans, Langfuse reads gen_ai.input.messages natively.\n // For non-gen_ai spans, we map the first mastra.*.input/output we find.\n if (!attributes['gen_ai.input.messages'] && !attributes['gen_ai.tool.call.arguments']) {\n for (const key of Object.keys(attributes)) {\n if (key.startsWith('mastra.') && key.endsWith('.input')) {\n attributes['langfuse.observation.input'] = attributes[key];\n delete attributes[key];\n break;\n }\n }\n }\n if (!attributes['gen_ai.output.messages'] && !attributes['gen_ai.tool.call.result']) {\n for (const key of Object.keys(attributes)) {\n if (key.startsWith('mastra.') && key.endsWith('.output')) {\n attributes['langfuse.observation.output'] = attributes[key];\n delete attributes[key];\n break;\n }\n }\n }\n}\n\nfunction snakeToCamel(name: string): string {\n return name.replace(/_([a-z0-9])/g, (_, c: string) => c.toUpperCase());\n}\n\n/**\n * Remove trailing \"/\" characters procedurally. Avoids polynomial\n * backtracking that a greedy regex like `/\\/+$/` can exhibit when the\n * input is attacker-controlled.\n */\nfunction stripTrailingSlashes(s: string): string {\n let end = s.length;\n while (end > 0 && s.charCodeAt(end - 1) === 47 /* \"/\" */) {\n end--;\n }\n return end === s.length ? s : s.slice(0, end);\n}\n","/**\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 agent = new Agent({\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangfusePrompt({ name: 'my-prompt', version: 1 }),\n * ),\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 /** @deprecated Langfuse v5 only supports linking by name + version. This field is ignored. */\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 (required for Langfuse v5)\n * - `version` - Prompt version (required for Langfuse v5)\n *\n * All fields are deeply merged with any existing metadata.\n *\n * @param prompt - Prompt fields for linking (`name` and `version` required)\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 *\n * // Link a generation to a Langfuse prompt by name and version\n * const tracingOptions = buildTracingOptions(\n * withLangfusePrompt({ name: 'customer-support', version: 1 }),\n * );\n *\n * // Or directly in agent config\n * const agent = new Agent({\n * name: 'support-agent',\n * instructions: 'You are a helpful assistant',\n * model: openai('gpt-4o'),\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangfusePrompt({ name: 'my-prompt', version: 1 }),\n * ),\n * },\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"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/tracing.ts","../src/helpers.ts"],"sourcesContent":["/**\n * Langfuse Exporter for Mastra Observability\n *\n * Sends observability data to Langfuse using the official @langfuse/otel span processor\n * and @langfuse/client for non-tracing features (scoring, prompt management, evaluations).\n *\n * @see https://langfuse.com/docs/observability/sdk/typescript/overview\n */\n\nimport { LangfuseClient } from '@langfuse/client';\nimport { LangfuseSpanProcessor } from '@langfuse/otel';\nimport type {\n TracingEvent,\n AnyExportedSpan,\n InitExporterOptions,\n ModelGenerationAttributes,\n ScoreEvent,\n} from '@mastra/core/observability';\nimport { SpanType, TracingEventType } from '@mastra/core/observability';\nimport { BaseExporter } from '@mastra/observability';\nimport type { BaseExporterConfig } from '@mastra/observability';\nimport { SpanConverter } from '@mastra/otel-exporter';\nimport type { OtelExporterConfig } from '@mastra/otel-exporter';\n\nconst LOG_PREFIX = '[LangfuseExporter]';\nconst MASTRA_METADATA_PREFIX = 'mastra.metadata.';\n/** Metadata keys mapped to dedicated Langfuse fields; never forwarded as trace metadata. */\nconst OM_CALLER_THREAD_ID = '__mastraObservationalMemoryCallerThreadId';\nconst DEDICATED_METADATA_KEYS = new Set([\n 'userId',\n 'sessionId',\n 'threadId',\n 'traceName',\n 'version',\n 'langfuse',\n OM_CALLER_THREAD_ID,\n]);\n\nexport const LANGFUSE_DEFAULT_BASE_URL = 'https://cloud.langfuse.com';\n\nexport interface LangfuseExporterConfig extends BaseExporterConfig {\n /** Langfuse public key */\n publicKey?: string;\n /** Langfuse secret key */\n secretKey?: string;\n /** Langfuse host URL (defaults to https://cloud.langfuse.com) */\n baseUrl?: string;\n /** Additional headers sent with requests to Langfuse */\n additionalHeaders?: Record<string, string>;\n /** Enable realtime mode - flushes after each event for immediate visibility */\n realtime?: boolean;\n /** Maximum number of spans per OTEL export batch */\n flushAt?: number;\n /** Maximum time in seconds before pending spans are exported */\n flushInterval?: number;\n /** Langfuse environment tag for traces */\n environment?: string;\n /** Langfuse release tag for traces */\n release?: string;\n /** Override OpenTelemetry resource attributes, including service.version. */\n resourceAttributes?: OtelExporterConfig['resourceAttributes'];\n}\n\nexport class LangfuseExporter extends BaseExporter {\n name = 'langfuse';\n #processor: LangfuseSpanProcessor | undefined;\n #client: LangfuseClient | undefined;\n #spanConverter: SpanConverter | undefined;\n #realtime: boolean;\n #environment: string | undefined;\n #release: string | undefined;\n #resourceAttributes: OtelExporterConfig['resourceAttributes'];\n\n constructor(config: LangfuseExporterConfig = {}) {\n super(config);\n\n const publicKey = config.publicKey ?? process.env.LANGFUSE_PUBLIC_KEY;\n const secretKey = config.secretKey ?? process.env.LANGFUSE_SECRET_KEY;\n const baseUrl = stripTrailingSlashes(config.baseUrl ?? process.env.LANGFUSE_BASE_URL ?? LANGFUSE_DEFAULT_BASE_URL);\n this.#realtime = config.realtime ?? false;\n this.#resourceAttributes = config.resourceAttributes;\n\n if (!publicKey || !secretKey) {\n const publicKeySource = config.publicKey\n ? 'from config'\n : process.env.LANGFUSE_PUBLIC_KEY\n ? 'from env'\n : 'missing';\n const secretKeySource = config.secretKey\n ? 'from config'\n : process.env.LANGFUSE_SECRET_KEY\n ? 'from env'\n : 'missing';\n this.setDisabled(\n `${LOG_PREFIX} Missing required credentials (publicKey: ${publicKeySource}, secretKey: ${secretKeySource}). ` +\n `Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY environment variables or pass them in config.`,\n );\n return;\n }\n\n this.#processor = new LangfuseSpanProcessor({\n publicKey,\n secretKey,\n baseUrl,\n additionalHeaders: config.additionalHeaders,\n environment: config.environment,\n release: config.release,\n exportMode: this.#realtime ? 'immediate' : 'batched',\n flushAt: config.flushAt,\n flushInterval: config.flushInterval,\n // Export all spans — the default filter only passes spans with gen_ai.* attributes\n // or known LLM instrumentation scopes, but Mastra spans use mastra.* attributes.\n shouldExportSpan: () => true,\n });\n\n this.#client = new LangfuseClient({\n publicKey,\n secretKey,\n baseUrl,\n additionalHeaders: config.additionalHeaders,\n });\n\n this.#environment = config.environment ?? process.env.LANGFUSE_TRACING_ENVIRONMENT;\n this.#release = config.release ?? process.env.LANGFUSE_RELEASE;\n }\n\n init(options: InitExporterOptions) {\n this.#spanConverter = new SpanConverter({\n packageName: '@mastra/langfuse',\n serviceName: options.config?.serviceName,\n config: { resourceAttributes: this.#resourceAttributes },\n format: 'GenAI_v1_38_0',\n });\n }\n\n protected async _exportTracingEvent(event: TracingEvent): Promise<void> {\n if (event.type !== TracingEventType.SPAN_ENDED) return;\n if (!this.#processor) return;\n\n await this.exportSpan(event.exportedSpan);\n }\n\n private async exportSpan(span: AnyExportedSpan): Promise<void> {\n if (!this.#spanConverter) {\n // Fallback if init() was not called (e.g., standalone usage without Mastra)\n this.#spanConverter = new SpanConverter({\n packageName: '@mastra/langfuse',\n serviceName: 'mastra-service',\n config: { resourceAttributes: this.#resourceAttributes },\n format: 'GenAI_v1_38_0',\n });\n }\n\n try {\n const otelSpan = await this.#spanConverter.convertSpan(span);\n\n // Map mastra.* attributes to langfuse.* namespace so that Langfuse's OTLP\n // endpoint reads them correctly. SpanConverter produces mastra.* attributes,\n // but Langfuse only reads langfuse.* attributes for prompt linking, TTFT, etc.\n // @see https://langfuse.com/integrations/native/opentelemetry#property-mapping\n mapMastraToLangfuseAttributes(otelSpan.attributes, span, this.#environment, this.#release);\n\n this.#processor!.onEnd(otelSpan);\n } catch (error) {\n this.logger.error(`${LOG_PREFIX} Failed to export span ${span.id}:`, error);\n }\n }\n\n /**\n * The LangfuseClient instance for advanced Langfuse features.\n * Use this for prompt management, evaluations, datasets, and direct API access.\n */\n get client(): LangfuseClient | undefined {\n return this.#client;\n }\n\n /**\n * Submit a score to Langfuse. Used by both the new `onScoreEvent` path and the\n * deprecated `addScoreToTrace` wrapper.\n */\n private submitScore(args: {\n id: string;\n traceId: string;\n spanId?: string;\n name: string;\n value: number;\n comment?: string;\n metadata?: Record<string, unknown>;\n }): void {\n if (!this.#client) return;\n\n const { id, traceId, spanId, name, value, comment, metadata } = args;\n try {\n this.#client.score.create({\n id,\n traceId,\n ...(spanId ? { observationId: spanId } : {}),\n name,\n value,\n ...(comment ? { comment } : {}),\n ...(metadata ? { metadata } : {}),\n ...(this.#environment ? { environment: this.#environment } : {}),\n dataType: 'NUMERIC' as const,\n });\n } catch (error) {\n this.logger.error(`${LOG_PREFIX} Error submitting score`, {\n error,\n traceId,\n spanId,\n name,\n });\n }\n }\n\n async onScoreEvent(event: ScoreEvent): Promise<void> {\n const { score } = event;\n if (!score.traceId) return;\n this.submitScore({\n id: score.scoreId,\n traceId: score.traceId,\n spanId: score.spanId,\n name: score.scorerName ?? score.scorerId,\n value: score.score,\n comment: score.reason,\n metadata: score.metadata,\n });\n }\n\n /**\n * @deprecated Use the observability score event pipeline (`mastra.observability.addScore`)\n * instead. This method is preserved for backwards compatibility and forwards to the same\n * underlying client call as `onScoreEvent`.\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 this.submitScore({\n id: `${traceId}-${spanId || ''}-${scorerName}`,\n traceId,\n spanId,\n name: scorerName,\n value: score,\n comment: reason,\n metadata,\n });\n }\n\n async flush(): Promise<void> {\n await Promise.all([this.#processor?.forceFlush(), this.#client?.flush()]);\n }\n\n async shutdown(): Promise<void> {\n await Promise.all([this.#processor?.shutdown(), this.#client?.shutdown()]);\n }\n}\n\n/**\n * Maps Mastra-specific OTel attributes to the langfuse.* namespace that\n * Langfuse's OTLP endpoint reads for prompt linking, TTFT, and other features.\n *\n * SpanConverter produces attributes like mastra.metadata.*, mastra.completion_start_time, etc.\n * Langfuse's OTLP server only reads langfuse.observation.prompt.name, langfuse.observation.completion_start_time, etc.\n *\n * This function mutates the attributes object in place.\n * @see https://langfuse.com/integrations/native/opentelemetry#property-mapping\n */\n/**\n * Serialize a root span's input/output for the trace-level attributes.\n * Returns undefined (attribute omitted) for absent values and for values that\n * cannot be JSON-serialized, so a bad payload never fails the span export.\n */\nfunction serializeTraceIo(value: unknown): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value);\n } catch {\n return undefined;\n }\n}\n\nfunction mapMastraToLangfuseAttributes(\n attributes: Record<string, any>,\n span: AnyExportedSpan,\n environment?: string,\n release?: string,\n): void {\n if (attributes['gen_ai.usage.reasoning_tokens'] !== undefined) {\n attributes['gen_ai.usage.reasoning.output_tokens'] = attributes['gen_ai.usage.reasoning_tokens'];\n delete attributes['gen_ai.usage.reasoning_tokens'];\n }\n\n // A run attached under an existing span (tracingOptions.nestUnderParent) does\n // not own the trace. Langfuse applies trace-level attributes from any span,\n // so none of its spans may write them: they would replace the outer run's.\n // The skipped values stay on the observation as mastra.metadata.*.\n const nested = span.nestedUnderParent === true;\n\n // Environment and release: set directly since onStart() is not called\n if (environment) {\n attributes['langfuse.environment'] = environment;\n }\n if (release) {\n attributes['langfuse.release'] = release;\n }\n\n // mastra.metadata.langfuse holds a user-supplied object with two kinds of keys:\n // - the reserved `prompt` key, used for prompt linking, and\n // - arbitrary custom keys the user wants as top-level, filterable trace metadata.\n // Langfuse only allows filtering/grouping traces by top-level metadata\n // (langfuse.trace.metadata.*), so forward every non-prompt key there. These\n // attributes may be set on any span in the trace and land on the trace record.\n // @see https://langfuse.com/integrations/native/opentelemetry#property-mapping\n const langfuseMetadata = attributes['mastra.metadata.langfuse'];\n if (langfuseMetadata) {\n try {\n const parsed = typeof langfuseMetadata === 'string' ? JSON.parse(langfuseMetadata) : langfuseMetadata;\n if (parsed && typeof parsed === 'object') {\n // Prompt linking: mastra.metadata.langfuse.prompt → langfuse.observation.prompt.name / version\n if (parsed.prompt) {\n if (parsed.prompt.name !== undefined) {\n attributes['langfuse.observation.prompt.name'] = parsed.prompt.name;\n }\n if (parsed.prompt.version !== undefined) {\n attributes['langfuse.observation.prompt.version'] = parsed.prompt.version;\n }\n }\n\n // Custom keys: mastra.metadata.langfuse.<key> → langfuse.trace.metadata.<key>\n // Reserved identity keys (agentId/agentName/workflowId/workflowName) are set\n // by the root-span block below, which runs after this loop and takes precedence.\n for (const [key, value] of Object.entries(parsed)) {\n if (nested || key === 'prompt' || value === null || value === undefined) {\n continue;\n }\n const traceKey = `langfuse.trace.metadata.${key}`;\n // Don't overwrite a trace.metadata key already mapped from another attribute.\n if (attributes[traceKey] === undefined) {\n // Langfuse maps langfuse.trace.metadata.* as string attributes, so\n // serialize non-strings with JSON. Langfuse Cloud restores numbers,\n // booleans, and objects to their original types on ingestion.\n attributes[traceKey] = typeof value === 'string' ? value : JSON.stringify(value);\n }\n }\n }\n } catch {\n // best effort — invalid JSON is silently ignored\n }\n if (!nested) {\n delete attributes['mastra.metadata.langfuse'];\n }\n }\n\n // TTFT: mastra.completion_start_time → langfuse.observation.completion_start_time\n if (attributes['mastra.completion_start_time']) {\n attributes['langfuse.observation.completion_start_time'] = attributes['mastra.completion_start_time'];\n delete attributes['mastra.completion_start_time'];\n }\n\n // Exact provider-reported cost takes precedence over Langfuse's model-price inference.\n // Estimated costs remain unexported so Langfuse can apply its own pricing model.\n const costContext =\n span.type === SpanType.MODEL_GENERATION\n ? (span.attributes as ModelGenerationAttributes | undefined)?.costContext\n : undefined;\n if (\n costContext?.costMetadata?.source === 'provider_reported' &&\n costContext.costUnit === 'USD' &&\n typeof costContext.estimatedCost === 'number' &&\n Number.isFinite(costContext.estimatedCost) &&\n costContext.estimatedCost >= 0\n ) {\n attributes['langfuse.observation.cost_details'] = JSON.stringify({\n total: costContext.estimatedCost,\n });\n }\n\n // User ID: mastra.metadata.userId → user.id\n if (!nested && attributes['mastra.metadata.userId']) {\n attributes['user.id'] = attributes['mastra.metadata.userId'];\n delete attributes['mastra.metadata.userId'];\n }\n\n // OM keeps caller identity separate from its isolated execution thread. Read\n // the raw hint: conversion serializes malformed objects/arrays into strings.\n const omCallerThreadId = span.metadata?.[OM_CALLER_THREAD_ID];\n const sessionId =\n attributes['mastra.metadata.sessionId'] ??\n (typeof omCallerThreadId === 'string' && omCallerThreadId\n ? omCallerThreadId\n : attributes['mastra.metadata.threadId']);\n delete attributes[`${MASTRA_METADATA_PREFIX}${OM_CALLER_THREAD_ID}`];\n if (!nested && sessionId) {\n attributes['session.id'] = sessionId;\n delete attributes['mastra.metadata.sessionId'];\n delete attributes['mastra.metadata.threadId'];\n }\n\n // Tags: mastra.tags → langfuse.trace.tags\n if (attributes['mastra.tags']) {\n attributes['langfuse.trace.tags'] = attributes['mastra.tags'];\n delete attributes['mastra.tags'];\n }\n\n // Trace name: mastra.metadata.traceName → langfuse.trace.name\n if (!nested && attributes['mastra.metadata.traceName']) {\n attributes['langfuse.trace.name'] = attributes['mastra.metadata.traceName'];\n delete attributes['mastra.metadata.traceName'];\n }\n\n // Version: mastra.metadata.version → langfuse.version\n if (attributes['mastra.metadata.version']) {\n attributes['langfuse.version'] = attributes['mastra.metadata.version'];\n delete attributes['mastra.metadata.version'];\n }\n\n // Root-span trace identity: scope each Langfuse trace to the entity that\n // started it (agent or workflow). This makes the Langfuse trace name match\n // the agent/workflow id and exposes the same identity as trace metadata, so\n // users can scope Langfuse evaluators per agent via trace name or metadata\n // filters. User-provided traceName (set via mastra.metadata.traceName) takes\n // precedence and is preserved.\n if (span.isRootSpan && !nested) {\n // Trace input/output: mirror the root span's input/output onto the trace.\n // Without this, Langfuse traces have empty trace-level input/output (the\n // span data only reaches the root OBSERVATION), which breaks LLM-as-a-judge\n // evaluators mapped to Trace input/output and leaves the trace view without\n // the top-level request/response. Serialization failures (circular refs,\n // bigint) skip the attribute rather than failing the span export.\n // A resumed run (after a suspend such as tool-call approval) continues the\n // original trace: core marks it with parentSpanId === resumedFromSpanId.\n // Its input is the resume payload, not the user's message, and Langfuse\n // applies trace-level attributes from any span (last writer wins), so it\n // must not replace the trace input the original run already set. Its\n // output IS the turn's final reply, so trace output is still written.\n const resumedContinuation =\n span.parentSpanId !== undefined && span.parentSpanId === span.metadata?.resumedFromSpanId;\n const traceInput = resumedContinuation ? undefined : serializeTraceIo(span.input);\n if (traceInput !== undefined) {\n attributes['langfuse.trace.input'] = traceInput;\n }\n const traceOutput = serializeTraceIo(span.output);\n if (traceOutput !== undefined) {\n attributes['langfuse.trace.output'] = traceOutput;\n }\n if (span.type === SpanType.AGENT_RUN) {\n if (!attributes['langfuse.trace.name'] && (span.entityName || span.entityId)) {\n attributes['langfuse.trace.name'] = span.entityName ?? span.entityId;\n }\n if (span.entityId) {\n attributes['langfuse.trace.metadata.agentId'] = span.entityId;\n }\n if (span.entityName) {\n attributes['langfuse.trace.metadata.agentName'] = span.entityName;\n }\n } else if (span.type === SpanType.WORKFLOW_RUN) {\n if (!attributes['langfuse.trace.name'] && (span.entityName || span.entityId)) {\n attributes['langfuse.trace.name'] = span.entityName ?? span.entityId;\n }\n if (span.entityId) {\n attributes['langfuse.trace.metadata.workflowId'] = span.entityId;\n }\n if (span.entityName) {\n attributes['langfuse.trace.metadata.workflowName'] = span.entityName;\n }\n }\n\n // Root-span metadata: forward the remaining mastra.metadata.* keys (runId,\n // resourceId, user-supplied keys) to langfuse.trace.metadata.* so they are\n // filterable trace metadata; Langfuse nests unmapped attributes under\n // metadata.attributes. Dedicated keys, explicit metadata.langfuse.* values,\n // and the identity keys above take precedence. Root only: Langfuse applies\n // langfuse.trace.* from any span, so a child span could overwrite the trace.\n for (const [key, value] of Object.entries(attributes)) {\n if (!key.startsWith(MASTRA_METADATA_PREFIX)) {\n continue;\n }\n const metadataKey = key.slice(MASTRA_METADATA_PREFIX.length);\n if (DEDICATED_METADATA_KEYS.has(metadataKey) || value === null || value === undefined) {\n continue;\n }\n const traceKey = `langfuse.trace.metadata.${metadataKey}`;\n if (attributes[traceKey] !== undefined) {\n continue;\n }\n const serialized = serializeTraceIo(value);\n if (serialized !== undefined) {\n attributes[traceKey] = serialized;\n }\n }\n }\n\n // Observation metadata: map semantic attributes to langfuse.observation.metadata.*\n // so they become top-level filterable keys on each observation in Langfuse.\n // @see https://langfuse.com/integrations/native/opentelemetry#how-metadata-mapping-works\n if (attributes['gen_ai.agent.id']) {\n attributes['langfuse.observation.metadata.agentId'] = attributes['gen_ai.agent.id'];\n }\n if (attributes['gen_ai.agent.name']) {\n attributes['langfuse.observation.metadata.agentName'] = attributes['gen_ai.agent.name'];\n }\n if (attributes['mastra.span.type']) {\n attributes['langfuse.observation.metadata.spanType'] = attributes['mastra.span.type'];\n }\n if (attributes['gen_ai.operation.name']) {\n attributes['langfuse.observation.metadata.operationName'] = attributes['gen_ai.operation.name'];\n }\n\n // Span-type attributes (mastra.<span_type>.<snake_key>, e.g.\n // mastra.model_step.step_index) are promoted the same way, as camelCase keys,\n // so scalar span attributes are filterable in observation-level evaluator\n // rules without a per-key mapping. Input/output have their own mapping below.\n // Attributes whose raw value is an object or array (entryMetadata,\n // truthyIndexes, ...) are already JSON strings here and make no useful\n // filter, so they stay in the nested attributes only. Values are sent as strings.\n if (span.type) {\n const spanAttrPrefix = `mastra.${String(span.type).toLowerCase()}.`;\n const rawAttributes = (span.attributes ?? {}) as Record<string, unknown>;\n for (const [key, value] of Object.entries(attributes)) {\n if (!key.startsWith(spanAttrPrefix) || value === null || value === undefined) {\n continue;\n }\n const name = snakeToCamel(key.slice(spanAttrPrefix.length));\n if (name === 'input' || name === 'output') {\n continue;\n }\n const raw = rawAttributes[name];\n if (typeof raw === 'object' && raw !== null && !(raw instanceof Date)) {\n continue;\n }\n const metadataKey = `langfuse.observation.metadata.${name}`;\n if (attributes[metadataKey] === undefined) {\n attributes[metadataKey] = typeof value === 'string' ? value : JSON.stringify(value);\n }\n }\n }\n\n // Input/Output: mastra.*.input/output → langfuse.observation.input/output\n // For gen_ai spans, Langfuse reads gen_ai.input.messages natively.\n // For non-gen_ai spans, we map the first mastra.*.input/output we find.\n if (!attributes['gen_ai.input.messages'] && !attributes['gen_ai.tool.call.arguments']) {\n for (const key of Object.keys(attributes)) {\n if (key.startsWith('mastra.') && key.endsWith('.input')) {\n attributes['langfuse.observation.input'] = attributes[key];\n delete attributes[key];\n break;\n }\n }\n }\n if (!attributes['gen_ai.output.messages'] && !attributes['gen_ai.tool.call.result']) {\n for (const key of Object.keys(attributes)) {\n if (key.startsWith('mastra.') && key.endsWith('.output')) {\n attributes['langfuse.observation.output'] = attributes[key];\n delete attributes[key];\n break;\n }\n }\n }\n}\n\nfunction snakeToCamel(name: string): string {\n return name.replace(/_([a-z0-9])/g, (_, c: string) => c.toUpperCase());\n}\n\n/**\n * Remove trailing \"/\" characters procedurally. Avoids polynomial\n * backtracking that a greedy regex like `/\\/+$/` can exhibit when the\n * input is attacker-controlled.\n */\nfunction stripTrailingSlashes(s: string): string {\n let end = s.length;\n while (end > 0 && s.charCodeAt(end - 1) === 47 /* \"/\" */) {\n end--;\n }\n return end === s.length ? s : s.slice(0, end);\n}\n","/**\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 agent = new Agent({\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangfusePrompt({ name: 'my-prompt', version: 1 }),\n * ),\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 /** @deprecated Langfuse v5 only supports linking by name + version. This field is ignored. */\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 (required for Langfuse v5)\n * - `version` - Prompt version (required for Langfuse v5)\n *\n * All fields are deeply merged with any existing metadata.\n *\n * @param prompt - Prompt fields for linking (`name` and `version` required)\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 *\n * // Link a generation to a Langfuse prompt by name and version\n * const tracingOptions = buildTracingOptions(\n * withLangfusePrompt({ name: 'customer-support', version: 1 }),\n * );\n *\n * // Or directly in agent config\n * const agent = new Agent({\n * name: 'support-agent',\n * instructions: 'You are a helpful assistant',\n * model: openai('gpt-4o'),\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangfusePrompt({ name: 'my-prompt', version: 1 }),\n * ),\n * },\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"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/langfuse",
3
- "version": "1.6.3-alpha.1",
3
+ "version": "1.6.3-alpha.2",
4
4
  "description": "Langfuse observability provider for Mastra - uses official Langfuse v5 SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,23 +23,23 @@
23
23
  },
24
24
  "license": "Apache-2.0",
25
25
  "dependencies": {
26
- "@langfuse/client": "^5.9.1",
27
- "@langfuse/otel": "^5.9.1",
28
- "@mastra/observability": "1.21.1-alpha.1",
29
- "@mastra/otel-exporter": "1.4.10-alpha.1"
26
+ "@langfuse/client": "^5.14.0",
27
+ "@langfuse/otel": "^5.14.0",
28
+ "@mastra/observability": "1.21.1-alpha.2",
29
+ "@mastra/otel-exporter": "1.4.10-alpha.2"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@opentelemetry/api": "^1.9.1",
33
- "@opentelemetry/sdk-trace-base": "^2.10.0",
34
- "@types/node": "22.20.5",
33
+ "@opentelemetry/sdk-trace-base": "^2.12.0",
34
+ "@types/node": "22.19.15",
35
35
  "@vitest/coverage-v8": "4.1.12",
36
36
  "@vitest/ui": "4.1.12",
37
37
  "tsdown": "0.23.1",
38
38
  "typescript": "^7.0.2",
39
39
  "vitest": "4.1.12",
40
40
  "@internal/lint": "0.0.145",
41
- "@mastra/core": "1.80.0-alpha.1",
42
- "@internal/types-builder": "0.0.120"
41
+ "@internal/types-builder": "0.0.120",
42
+ "@mastra/core": "1.80.0-alpha.4"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@mastra/core": ">=1.16.0-0 <2.0.0-0",