@langfuse/otel 4.0.0-beta.0 → 4.0.0-beta.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/README.md +24 -0
- package/dist/index.cjs +145 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +297 -1
- package/dist/index.d.ts +297 -1
- package/dist/index.mjs +147 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# @langfuse/otel
|
|
4
|
+
|
|
5
|
+
This is the OTEL package of the Langfuse JS SDK containing the `LangfuseSpanProcessor` to export emitted spans to Langfuse with masking, filtering, and media handling.
|
|
6
|
+
|
|
7
|
+
## Packages
|
|
8
|
+
|
|
9
|
+
| Package | NPM | Description | Environments |
|
|
10
|
+
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | ------------ |
|
|
11
|
+
| [@langfuse/client](./packages/client) | [](https://www.npmjs.com/package/@langfuse/client) | Langfuse API client for universal JavaScript environments | Universal JS |
|
|
12
|
+
| [@langfuse/tracing](./packages/tracing) | [](https://www.npmjs.com/package/@langfuse/tracing) | Langfuse instrumentation methods based on OpenTelemetry | Node.js 20+ |
|
|
13
|
+
| [@langfuse/otel](./packages/otel) | [](https://www.npmjs.com/package/@langfuse/otel) | Langfuse OpenTelemetry export helpers | Node.js 20+ |
|
|
14
|
+
| [@langfuse/openai](./packages/openai) | [](https://www.npmjs.com/package/@langfuse/openai) | Langfuse integration for OpenAI SDK | Universal JS |
|
|
15
|
+
| [@langfuse/langchain](./packages/langchain) | [](https://www.npmjs.com/package/@langfuse/langchain) | Langfuse integration for LangChain | Universal JS |
|
|
16
|
+
|
|
17
|
+
## Documentation
|
|
18
|
+
|
|
19
|
+
- Docs: https://langfuse.com/docs/sdk/typescript
|
|
20
|
+
- Reference: https://js.reference.langfuse.com
|
|
21
|
+
|
|
22
|
+
## License
|
|
23
|
+
|
|
24
|
+
[MIT](LICENSE)
|
package/dist/index.cjs
CHANGED
|
@@ -71,6 +71,20 @@ function getSha256HashFromBytes(data) {
|
|
|
71
71
|
// src/media.ts
|
|
72
72
|
var import_core2 = require("@langfuse/core");
|
|
73
73
|
var LangfuseMedia = class {
|
|
74
|
+
/**
|
|
75
|
+
* Creates a new LangfuseMedia instance.
|
|
76
|
+
*
|
|
77
|
+
* @param params - Media parameters specifying the source and content
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```typescript
|
|
81
|
+
* // Create from base64 data URI
|
|
82
|
+
* const media = new LangfuseMedia({
|
|
83
|
+
* source: "base64_data_uri",
|
|
84
|
+
* base64DataUri: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
74
88
|
constructor(params) {
|
|
75
89
|
const { source } = params;
|
|
76
90
|
this._source = source;
|
|
@@ -85,6 +99,13 @@ var LangfuseMedia = class {
|
|
|
85
99
|
this._contentType = params.contentType;
|
|
86
100
|
}
|
|
87
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Parses a base64 data URI to extract content bytes and type.
|
|
104
|
+
*
|
|
105
|
+
* @param data - The base64 data URI string
|
|
106
|
+
* @returns Tuple of [contentBytes, contentType] or [undefined, undefined] on error
|
|
107
|
+
* @private
|
|
108
|
+
*/
|
|
88
109
|
parseBase64DataUri(data) {
|
|
89
110
|
try {
|
|
90
111
|
if (!data || typeof data !== "string") {
|
|
@@ -114,15 +135,42 @@ var LangfuseMedia = class {
|
|
|
114
135
|
return [void 0, void 0];
|
|
115
136
|
}
|
|
116
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Gets a unique identifier for this media based on its content hash.
|
|
140
|
+
*
|
|
141
|
+
* The ID is derived from the first 22 characters of the URL-safe base64-encoded
|
|
142
|
+
* SHA-256 hash of the content.
|
|
143
|
+
*
|
|
144
|
+
* @returns The unique media ID, or null if hash generation failed
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* const media = new LangfuseMedia({...});
|
|
149
|
+
* console.log(media.id); // "A1B2C3D4E5F6G7H8I9J0K1"
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
117
152
|
get id() {
|
|
118
153
|
if (!this.contentSha256Hash) return null;
|
|
119
154
|
const urlSafeContentHash = this.contentSha256Hash.replaceAll("+", "-").replaceAll("/", "_");
|
|
120
155
|
return urlSafeContentHash.slice(0, 22);
|
|
121
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Gets the length of the media content in bytes.
|
|
159
|
+
*
|
|
160
|
+
* @returns The content length in bytes, or undefined if no content is available
|
|
161
|
+
*/
|
|
122
162
|
get contentLength() {
|
|
123
163
|
var _a2;
|
|
124
164
|
return (_a2 = this._contentBytes) == null ? void 0 : _a2.length;
|
|
125
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Gets the SHA-256 hash of the media content.
|
|
168
|
+
*
|
|
169
|
+
* The hash is used for content integrity verification and generating unique media IDs.
|
|
170
|
+
* Returns undefined if crypto is not available or hash generation fails.
|
|
171
|
+
*
|
|
172
|
+
* @returns The base64-encoded SHA-256 hash, or undefined if unavailable
|
|
173
|
+
*/
|
|
126
174
|
get contentSha256Hash() {
|
|
127
175
|
if (!this._contentBytes || !isCryptoAvailable) {
|
|
128
176
|
return void 0;
|
|
@@ -137,14 +185,47 @@ var LangfuseMedia = class {
|
|
|
137
185
|
return void 0;
|
|
138
186
|
}
|
|
139
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Gets the media reference tag for embedding in trace data.
|
|
190
|
+
*
|
|
191
|
+
* The tag format is: `@@@langfuseMedia:type=<contentType>|id=<mediaId>|source=<source>@@@`
|
|
192
|
+
* This tag can be embedded in trace attributes and will be replaced with actual
|
|
193
|
+
* media content when the trace is viewed in Langfuse.
|
|
194
|
+
*
|
|
195
|
+
* @returns The media reference tag, or null if required data is missing
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```typescript
|
|
199
|
+
* const media = new LangfuseMedia({...});
|
|
200
|
+
* console.log(media.tag);
|
|
201
|
+
* // "@@@langfuseMedia:type=image/png|id=A1B2C3D4E5F6G7H8I9J0K1|source=base64_data_uri@@@"
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
140
204
|
get tag() {
|
|
141
205
|
if (!this._contentType || !this._source || !this.id) return null;
|
|
142
206
|
return `@@@langfuseMedia:type=${this._contentType}|id=${this.id}|source=${this._source}@@@`;
|
|
143
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Gets the media content as a base64 data URI.
|
|
210
|
+
*
|
|
211
|
+
* @returns The complete data URI string, or null if no content is available
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```typescript
|
|
215
|
+
* const media = new LangfuseMedia({...});
|
|
216
|
+
* console.log(media.base64DataUri);
|
|
217
|
+
* // "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB..."
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
144
220
|
get base64DataUri() {
|
|
145
221
|
if (!this._contentBytes) return null;
|
|
146
222
|
return `data:${this._contentType};base64,${Buffer.from(this._contentBytes).toString("base64")}`;
|
|
147
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Serializes the media to JSON (returns the base64 data URI).
|
|
226
|
+
*
|
|
227
|
+
* @returns The base64 data URI, or null if no content is available
|
|
228
|
+
*/
|
|
148
229
|
toJSON() {
|
|
149
230
|
return this.base64DataUri;
|
|
150
231
|
}
|
|
@@ -152,6 +233,32 @@ var LangfuseMedia = class {
|
|
|
152
233
|
|
|
153
234
|
// src/span-processor.ts
|
|
154
235
|
var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcessor {
|
|
236
|
+
/**
|
|
237
|
+
* Creates a new LangfuseSpanProcessor instance.
|
|
238
|
+
*
|
|
239
|
+
* @param params - Configuration parameters for the processor
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```typescript
|
|
243
|
+
* const processor = new LangfuseSpanProcessor({
|
|
244
|
+
* publicKey: 'pk_...',
|
|
245
|
+
* secretKey: 'sk_...',
|
|
246
|
+
* environment: 'staging',
|
|
247
|
+
* flushAt: 10,
|
|
248
|
+
* flushInterval: 2,
|
|
249
|
+
* mask: ({ data }) => {
|
|
250
|
+
* // Custom masking logic
|
|
251
|
+
* return typeof data === 'string'
|
|
252
|
+
* ? data.replace(/secret_\w+/g, 'secret_***')
|
|
253
|
+
* : data;
|
|
254
|
+
* },
|
|
255
|
+
* shouldExportSpan: ({ otelSpan }) => {
|
|
256
|
+
* // Only export spans from specific services
|
|
257
|
+
* return otelSpan.name.startsWith('my-service');
|
|
258
|
+
* }
|
|
259
|
+
* });
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
155
262
|
constructor(params) {
|
|
156
263
|
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
157
264
|
const logger = (0, import_core3.getGlobalLogger)();
|
|
@@ -173,8 +280,8 @@ var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcess
|
|
|
173
280
|
}
|
|
174
281
|
const flushAt = (_f = params == null ? void 0 : params.flushAt) != null ? _f : (0, import_core3.getEnv)("LANGFUSE_FLUSH_AT");
|
|
175
282
|
const flushIntervalSeconds = (_g = params == null ? void 0 : params.flushInterval) != null ? _g : (0, import_core3.getEnv)("LANGFUSE_FLUSH_INTERVAL");
|
|
176
|
-
const authHeaderValue =
|
|
177
|
-
|
|
283
|
+
const authHeaderValue = (0, import_core3.uint8ArrayToBase64)(
|
|
284
|
+
new TextEncoder().encode(`${publicKey}:${secretKey}`)
|
|
178
285
|
);
|
|
179
286
|
const timeoutSeconds = (_i = params == null ? void 0 : params.timeout) != null ? _i : Number((_h = (0, import_core3.getEnv)("LANGFUSE_TIMEOUT")) != null ? _h : 5);
|
|
180
287
|
const exporter = (_j = params == null ? void 0 : params.exporter) != null ? _j : new import_exporter_trace_otlp_http.OTLPTraceExporter({
|
|
@@ -228,6 +335,14 @@ var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcess
|
|
|
228
335
|
get logger() {
|
|
229
336
|
return (0, import_core3.getGlobalLogger)();
|
|
230
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* Called when a span is started. Adds environment and release attributes to the span.
|
|
340
|
+
*
|
|
341
|
+
* @param span - The span that was started
|
|
342
|
+
* @param parentContext - The parent context
|
|
343
|
+
*
|
|
344
|
+
* @override
|
|
345
|
+
*/
|
|
231
346
|
onStart(span, parentContext) {
|
|
232
347
|
span.setAttributes({
|
|
233
348
|
[import_core3.LangfuseOtelSpanAttributes.ENVIRONMENT]: this.environment,
|
|
@@ -235,6 +350,20 @@ var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcess
|
|
|
235
350
|
});
|
|
236
351
|
return super.onStart(span, parentContext);
|
|
237
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* Called when a span ends. Processes the span for export to Langfuse.
|
|
355
|
+
*
|
|
356
|
+
* This method:
|
|
357
|
+
* 1. Checks if the span should be exported using the shouldExportSpan function
|
|
358
|
+
* 2. Applies data masking to sensitive attributes
|
|
359
|
+
* 3. Handles media content extraction and upload
|
|
360
|
+
* 4. Logs span details in debug mode
|
|
361
|
+
* 5. Passes the span to the parent processor for export
|
|
362
|
+
*
|
|
363
|
+
* @param span - The span that ended
|
|
364
|
+
*
|
|
365
|
+
* @override
|
|
366
|
+
*/
|
|
238
367
|
onEnd(span) {
|
|
239
368
|
var _a2, _b;
|
|
240
369
|
if (this.shouldExportSpan) {
|
|
@@ -280,10 +409,24 @@ ${JSON.stringify(
|
|
|
280
409
|
);
|
|
281
410
|
});
|
|
282
411
|
}
|
|
412
|
+
/**
|
|
413
|
+
* Forces an immediate flush of all pending spans and media uploads.
|
|
414
|
+
*
|
|
415
|
+
* @returns Promise that resolves when all pending operations are complete
|
|
416
|
+
*
|
|
417
|
+
* @override
|
|
418
|
+
*/
|
|
283
419
|
async forceFlush() {
|
|
284
420
|
await this.flush();
|
|
285
421
|
return super.forceFlush();
|
|
286
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* Gracefully shuts down the processor, ensuring all pending operations are completed.
|
|
425
|
+
*
|
|
426
|
+
* @returns Promise that resolves when shutdown is complete
|
|
427
|
+
*
|
|
428
|
+
* @override
|
|
429
|
+
*/
|
|
287
430
|
async shutdown() {
|
|
288
431
|
await this.flush();
|
|
289
432
|
return super.shutdown();
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/span-processor.ts","../src/hash.ts","../src/media.ts"],"sourcesContent":["export * from \"./span-processor.js\";\nexport * from \"./media.js\";\n","import {\n Logger,\n getGlobalLogger,\n generateUUID,\n LangfuseAPIClient,\n LANGFUSE_SDK_VERSION,\n LangfuseOtelSpanAttributes,\n getEnv,\n} from \"@langfuse/core\";\nimport { hrTimeToMilliseconds } from \"@opentelemetry/core\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport {\n Span,\n BatchSpanProcessor,\n SpanExporter,\n ReadableSpan,\n} from \"@opentelemetry/sdk-trace-base\";\n\nimport { isCryptoAvailable } from \"./hash.js\";\nimport { LangfuseMedia } from \"./media.js\";\n\nexport type MaskFunction = (params: { data: any }) => any;\nexport type ShouldExportSpan = (params: { otelSpan: ReadableSpan }) => boolean;\n\nexport interface LangfuseSpanProcessorParams {\n exporter?: SpanExporter;\n publicKey?: string;\n secretKey?: string;\n baseUrl?: string;\n flushAt?: number;\n flushInterval?: number;\n mask?: MaskFunction;\n shouldExportSpan?: ShouldExportSpan;\n environment?: string;\n release?: string;\n timeout?: number;\n additionalHeaders?: Record<string, string>;\n}\n\nexport class LangfuseSpanProcessor extends BatchSpanProcessor {\n private pendingMediaUploads: Record<string, Promise<any>> = {};\n\n private publicKey?: string;\n private baseUrl?: string;\n private environment?: string;\n private release?: string;\n private mask?: MaskFunction;\n private shouldExportSpan?: ShouldExportSpan;\n private apiClient: LangfuseAPIClient;\n\n constructor(params?: LangfuseSpanProcessorParams) {\n const logger = getGlobalLogger();\n\n const publicKey = params?.publicKey ?? getEnv(\"LANGFUSE_PUBLIC_KEY\");\n const secretKey = params?.secretKey ?? getEnv(\"LANGFUSE_SECRET_KEY\");\n const baseUrl =\n params?.baseUrl ??\n getEnv(\"LANGFUSE_BASE_URL\") ??\n getEnv(\"LANGFUSE_BASEURL\") ?? // legacy v2\n \"https://cloud.langfuse.com\";\n\n if (!params?.exporter && !publicKey) {\n logger.warn(\n \"No exporter configured and no public key provided in constructor or as LANGFUSE_PUBLIC_KEY env var. Span exports will fail.\",\n );\n }\n if (!params?.exporter && !secretKey) {\n logger.warn(\n \"No exporter configured and no secret key provided in constructor or as LANGFUSE_SECRET_KEY env var. Span exports will fail.\",\n );\n }\n const flushAt = params?.flushAt ?? getEnv(\"LANGFUSE_FLUSH_AT\");\n const flushIntervalSeconds =\n params?.flushInterval ?? getEnv(\"LANGFUSE_FLUSH_INTERVAL\");\n\n const authHeaderValue = Buffer.from(`${publicKey}:${secretKey}`).toString(\n \"base64\",\n );\n const timeoutSeconds =\n params?.timeout ?? Number(getEnv(\"LANGFUSE_TIMEOUT\") ?? 5);\n\n const exporter =\n params?.exporter ??\n new OTLPTraceExporter({\n url: `${baseUrl}/api/public/otel/v1/traces`,\n headers: {\n Authorization: `Basic ${authHeaderValue}`,\n x_langfuse_sdk_name: \"javascript\",\n x_langfuse_sdk_version: LANGFUSE_SDK_VERSION,\n x_langfuse_public_key: publicKey ?? \"<missing>\",\n ...params?.additionalHeaders,\n },\n timeoutMillis: timeoutSeconds * 1_000,\n });\n\n super(exporter, {\n maxExportBatchSize: flushAt ? Number(flushAt) : undefined,\n scheduledDelayMillis: flushIntervalSeconds\n ? Number(flushIntervalSeconds) * 1_000\n : undefined,\n });\n\n this.publicKey = publicKey;\n this.baseUrl = baseUrl;\n this.environment =\n params?.environment ?? getEnv(\"LANGFUSE_TRACING_ENVIRONMENT\");\n this.release = params?.release ?? getEnv(\"LANGFUSE_RELEASE\");\n this.mask = params?.mask;\n this.shouldExportSpan = params?.shouldExportSpan;\n this.apiClient = new LangfuseAPIClient({\n baseUrl: this.baseUrl,\n username: this.publicKey,\n password: secretKey,\n xLangfusePublicKey: this.publicKey,\n xLangfuseSdkVersion: LANGFUSE_SDK_VERSION,\n xLangfuseSdkName: \"javascript\",\n environment: \"\", // noop as baseUrl is set\n headers: params?.additionalHeaders,\n });\n\n logger.debug(\"Initialized LangfuseSpanProcessor with params:\", {\n publicKey,\n baseUrl,\n environment: this.environment,\n release: this.release,\n timeoutSeconds,\n flushAt,\n flushIntervalSeconds,\n });\n\n // Warn if crypto is not available\n if (!isCryptoAvailable) {\n logger.warn(\n \"[Langfuse] Crypto module not available in this runtime. Media upload functionality will be disabled. \" +\n \"Spans will still be processed normally, but any media content in base64 data URIs will not be uploaded to Langfuse.\",\n );\n }\n }\n\n private get logger(): Logger {\n return getGlobalLogger();\n }\n\n public onStart(span: Span, parentContext: any): void {\n span.setAttributes({\n [LangfuseOtelSpanAttributes.ENVIRONMENT]: this.environment,\n [LangfuseOtelSpanAttributes.RELEASE]: this.release,\n });\n\n return super.onStart(span, parentContext);\n }\n\n public onEnd(span: ReadableSpan): void {\n if (this.shouldExportSpan) {\n try {\n if (this.shouldExportSpan({ otelSpan: span }) === false) return;\n } catch (err) {\n this.logger.error(\n \"ShouldExportSpan failed with error. Excluding span. Error: \",\n err,\n );\n\n return;\n }\n }\n\n this.applyMaskInPlace(span);\n this.handleMediaInPlace(span);\n\n this.logger.debug(\n `Processed span:\\n${JSON.stringify(\n {\n name: span.name,\n traceId: span.spanContext().traceId,\n spanId: span.spanContext().spanId,\n parentSpanId: span.parentSpanContext?.spanId ?? null,\n attributes: span.attributes,\n startTime: new Date(hrTimeToMilliseconds(span.startTime)),\n endTime: new Date(hrTimeToMilliseconds(span.endTime)),\n durationMs: hrTimeToMilliseconds(span.duration),\n kind: span.kind,\n status: span.status,\n resource: span.resource.attributes,\n instrumentationScope: span.instrumentationScope,\n },\n null,\n 2,\n )}`,\n );\n\n super.onEnd(span);\n }\n\n private async flush(): Promise<void> {\n await Promise.all(Object.values(this.pendingMediaUploads)).catch((e) => {\n this.logger.error(\n e instanceof Error ? e.message : \"Unhandled media upload error\",\n );\n });\n }\n\n public async forceFlush(): Promise<void> {\n await this.flush();\n\n return super.forceFlush();\n }\n\n public async shutdown(): Promise<void> {\n await this.flush();\n\n return super.shutdown();\n }\n\n private handleMediaInPlace(span: ReadableSpan): void {\n // Skip media handling if crypto is not available\n if (!isCryptoAvailable) {\n this.logger.debug(\n \"[Langfuse] Crypto not available, skipping media processing\",\n );\n return;\n }\n\n const mediaAttributes = [\n LangfuseOtelSpanAttributes.OBSERVATION_INPUT,\n LangfuseOtelSpanAttributes.TRACE_INPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,\n LangfuseOtelSpanAttributes.TRACE_OUTPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_METADATA,\n LangfuseOtelSpanAttributes.TRACE_METADATA,\n ];\n\n for (const mediaAttribute of mediaAttributes) {\n const mediaRelevantAttributeKeys = Object.keys(span.attributes).filter(\n (attributeName) => attributeName.startsWith(mediaAttribute),\n );\n\n for (const key of mediaRelevantAttributeKeys) {\n const value = span.attributes[key];\n\n if (typeof value !== \"string\") {\n this.logger.warn(\n `Span attribute ${mediaAttribute} is not a stringified object. Skipping media handling.`,\n );\n\n continue;\n }\n\n // Find media base64 data URI\n let mediaReplacedValue = value;\n const regex = /data:[^;]+;base64,[A-Za-z0-9+/]+=*/g;\n const foundMedia = [...new Set(value.match(regex) ?? [])];\n\n if (foundMedia.length === 0) continue;\n\n for (const mediaDataUri of foundMedia) {\n // For each media, create media tag and initiate upload\n const media = new LangfuseMedia({\n base64DataUri: mediaDataUri,\n source: \"base64_data_uri\",\n });\n\n if (!media.tag) {\n this.logger.warn(\n \"Failed to create Langfuse media tag. Skipping media item.\",\n );\n\n continue;\n }\n\n const uploadPromise: Promise<void> = this.processMediaItem({\n media,\n traceId: span.spanContext().traceId,\n observationId: span.spanContext().spanId,\n field: mediaAttribute.includes(\"input\")\n ? \"input\"\n : mediaAttribute.includes(\"output\")\n ? \"output\"\n : \"metadata\", // todo: make more robust\n });\n\n const promiseId = generateUUID();\n this.pendingMediaUploads[promiseId] = uploadPromise;\n\n uploadPromise.finally(() => {\n delete this.pendingMediaUploads[promiseId];\n });\n\n // Replace original attribute with media escaped attribute\n mediaReplacedValue = mediaReplacedValue.replaceAll(\n mediaDataUri,\n media.tag,\n );\n }\n\n span.attributes[key] = mediaReplacedValue;\n }\n }\n }\n\n private applyMaskInPlace(span: ReadableSpan): void {\n const maskCandidates = [\n LangfuseOtelSpanAttributes.OBSERVATION_INPUT,\n LangfuseOtelSpanAttributes.TRACE_INPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,\n LangfuseOtelSpanAttributes.TRACE_OUTPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_METADATA,\n LangfuseOtelSpanAttributes.TRACE_METADATA,\n ];\n\n for (const maskCandidate of maskCandidates) {\n if (maskCandidate in span.attributes) {\n span.attributes[maskCandidate] = this.applyMask(\n span.attributes[maskCandidate],\n );\n }\n }\n }\n\n private applyMask<T>(data: T): T | string {\n if (!this.mask) return data;\n\n try {\n return this.mask({ data });\n } catch (err) {\n this.logger.warn(\n `Applying mask function failed due to error, fully masking property. Error: ${err}`,\n );\n\n return \"<fully masked due to failed mask function>\";\n }\n }\n\n private async processMediaItem({\n media,\n traceId,\n observationId,\n field,\n }: {\n media: LangfuseMedia;\n traceId: string;\n observationId?: string;\n field: string;\n }): Promise<void> {\n try {\n if (\n !media.contentLength ||\n !media._contentType ||\n !media.contentSha256Hash ||\n !media._contentBytes\n ) {\n return;\n }\n\n const { uploadUrl, mediaId } = await this.apiClient.media.getUploadUrl({\n contentLength: media.contentLength,\n traceId,\n observationId,\n field,\n contentType: media._contentType,\n sha256Hash: media.contentSha256Hash,\n });\n\n if (!uploadUrl) {\n this.logger.debug(\n `Media status: Media with ID ${media.id} already uploaded. Skipping duplicate upload.`,\n );\n\n return;\n }\n\n if (media.id !== mediaId) {\n this.logger.error(\n `Media integrity error: Media ID mismatch between SDK (${media.id}) and Server (${mediaId}). Upload cancelled. Please check media ID generation logic.`,\n );\n\n return;\n }\n\n this.logger.debug(`Uploading media ${mediaId}...`);\n\n const startTime = Date.now();\n\n const uploadResponse = await this.uploadMediaWithBackoff({\n uploadUrl,\n contentBytes: media._contentBytes,\n contentType: media._contentType,\n contentSha256Hash: media.contentSha256Hash,\n maxRetries: 3,\n baseDelay: 1000,\n });\n\n if (!uploadResponse) {\n throw Error(\"Media upload process failed\");\n }\n\n await this.apiClient.media.patch(mediaId, {\n uploadedAt: new Date().toISOString(),\n uploadHttpStatus: uploadResponse.status,\n uploadHttpError: await uploadResponse.text(),\n uploadTimeMs: Date.now() - startTime,\n });\n\n this.logger.debug(`Media upload status reported for ${mediaId}`);\n } catch (err) {\n this.logger.error(`Error processing media item: ${err}`);\n }\n }\n\n private async uploadMediaWithBackoff(params: {\n uploadUrl: string;\n contentType: string;\n contentSha256Hash: string;\n contentBytes: Buffer;\n maxRetries: number;\n baseDelay: number;\n }) {\n const {\n uploadUrl,\n contentType,\n contentSha256Hash,\n contentBytes,\n maxRetries,\n baseDelay,\n } = params;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const uploadResponse = await fetch(uploadUrl, {\n method: \"PUT\",\n body: contentBytes,\n headers: {\n \"Content-Type\": contentType,\n \"x-amz-checksum-sha256\": contentSha256Hash,\n \"x-ms-blob-type\": \"BlockBlob\",\n },\n });\n\n if (\n attempt < maxRetries &&\n uploadResponse.status !== 200 &&\n uploadResponse.status !== 201\n ) {\n throw new Error(`Upload failed with status ${uploadResponse.status}`);\n }\n\n return uploadResponse;\n } catch (e) {\n if (attempt === maxRetries) {\n throw e;\n }\n\n const delay = baseDelay * Math.pow(2, attempt);\n const jitter = Math.random() * 1000;\n\n await new Promise((resolve) => setTimeout(resolve, delay + jitter));\n }\n }\n }\n}\n","import { getGlobalLogger, uint8ArrayToBase64 } from \"@langfuse/core\";\n\n// Cross-platform hash utilities with graceful fallbacks\nlet cryptoModule: any;\nlet isCryptoAvailable = false;\n\ntry {\n if (typeof (globalThis as any).Deno !== \"undefined\") {\n // Deno\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n cryptoModule = require(\"node:crypto\");\n isCryptoAvailable = true;\n } else if (typeof process !== \"undefined\" && process.versions?.node) {\n // Node\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n cryptoModule = require(\"crypto\");\n isCryptoAvailable = true;\n } else if (typeof crypto !== \"undefined\") {\n // Edge runtime (Cloudflare Workers, Vercel Cloud Function)\n cryptoModule = crypto;\n isCryptoAvailable = true;\n }\n} catch (error) {\n getGlobalLogger().warn(\n \"Crypto module not available. Media handling will be disabled.\",\n error,\n );\n\n isCryptoAvailable = false;\n}\n\nexport { isCryptoAvailable };\n\nfunction sha256(data: Uint8Array): Uint8Array {\n if (!isCryptoAvailable || !cryptoModule) {\n throw new Error(\"Crypto module not available\");\n }\n\n return cryptoModule.createHash(\"sha256\").update(data).digest();\n}\n\nexport function getSha256HashFromBytes(data: Uint8Array): string {\n if (!isCryptoAvailable) {\n throw new Error(\"Crypto module not available\");\n }\n\n const hash = sha256(data);\n\n return uint8ArrayToBase64(hash);\n}\n","import { getGlobalLogger, type MediaContentType } from \"@langfuse/core\";\n\nimport { getSha256HashFromBytes, isCryptoAvailable } from \"./hash.js\";\n\nexport type LangfuseMediaParams =\n | { source: \"base64_data_uri\"; base64DataUri: string }\n | {\n source: \"bytes\";\n contentBytes: Buffer;\n contentType: MediaContentType;\n };\n\n/**\n * A class for wrapping media objects for upload to Langfuse.\n *\n * This class handles the preparation and formatting of media content for Langfuse,\n * supporting both base64 data URIs and raw content bytes.\n */\nclass LangfuseMedia {\n _contentBytes?: Buffer;\n _contentType?: MediaContentType;\n _source?: string;\n\n constructor(params: LangfuseMediaParams) {\n const { source } = params;\n\n this._source = source;\n\n if (source === \"base64_data_uri\") {\n const [contentBytesParsed, contentTypeParsed] = this.parseBase64DataUri(\n params.base64DataUri,\n );\n this._contentBytes = contentBytesParsed;\n this._contentType = contentTypeParsed;\n } else {\n this._contentBytes = params.contentBytes;\n this._contentType = params.contentType;\n }\n }\n\n private parseBase64DataUri(\n data: string,\n ): [Buffer | undefined, MediaContentType | undefined] {\n try {\n if (!data || typeof data !== \"string\") {\n throw new Error(\"Data URI is not a string\");\n }\n\n if (!data.startsWith(\"data:\")) {\n throw new Error(\"Data URI does not start with 'data:'\");\n }\n\n const [header, actualData] = data.slice(5).split(\",\", 2);\n if (!header || !actualData) {\n throw new Error(\"Invalid URI\");\n }\n\n const headerParts = header.split(\";\");\n if (!headerParts.includes(\"base64\")) {\n throw new Error(\"Data is not base64 encoded\");\n }\n\n const contentType = headerParts[0];\n if (!contentType) {\n throw new Error(\"Content type is empty\");\n }\n\n return [\n Buffer.from(actualData, \"base64\"),\n contentType as MediaContentType,\n ];\n } catch (error) {\n getGlobalLogger().error(\"Error parsing base64 data URI\", error);\n return [undefined, undefined];\n }\n }\n\n get id(): string | null {\n if (!this.contentSha256Hash) return null;\n\n const urlSafeContentHash = this.contentSha256Hash\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\");\n\n return urlSafeContentHash.slice(0, 22);\n }\n\n get contentLength(): number | undefined {\n return this._contentBytes?.length;\n }\n\n get contentSha256Hash(): string | undefined {\n if (!this._contentBytes || !isCryptoAvailable) {\n return undefined;\n }\n\n try {\n return getSha256HashFromBytes(this._contentBytes);\n } catch (error) {\n getGlobalLogger().warn(\n \"[Langfuse] Failed to generate SHA-256 hash for media content:\",\n error,\n );\n\n return undefined;\n }\n }\n\n get tag(): string | null {\n if (!this._contentType || !this._source || !this.id) return null;\n\n return `@@@langfuseMedia:type=${this._contentType}|id=${this.id}|source=${this._source}@@@`;\n }\n\n get base64DataUri(): string | null {\n if (!this._contentBytes) return null;\n\n return `data:${this._contentType};base64,${Buffer.from(this._contentBytes).toString(\"base64\")}`;\n }\n\n toJSON(): string | null {\n return this.base64DataUri;\n }\n}\n\nexport { LangfuseMedia, type MediaContentType };\n"],"mappings":"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","names":["import_core","import_core","_a","_a"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/span-processor.ts","../src/hash.ts","../src/media.ts"],"sourcesContent":["export * from \"./span-processor.js\";\nexport * from \"./media.js\";\n","import {\n Logger,\n getGlobalLogger,\n generateUUID,\n LangfuseAPIClient,\n LANGFUSE_SDK_VERSION,\n LangfuseOtelSpanAttributes,\n getEnv,\n uint8ArrayToBase64,\n} from \"@langfuse/core\";\nimport { hrTimeToMilliseconds } from \"@opentelemetry/core\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport {\n Span,\n BatchSpanProcessor,\n SpanExporter,\n ReadableSpan,\n} from \"@opentelemetry/sdk-trace-base\";\n\nimport { isCryptoAvailable } from \"./hash.js\";\nimport { LangfuseMedia } from \"./media.js\";\n\n/**\n * Function type for masking sensitive data in spans before export.\n *\n * @param params - Object containing the data to be masked\n * @param params.data - The data that should be masked\n * @returns The masked data (can be of any type)\n *\n * @example\n * ```typescript\n * const maskFunction: MaskFunction = ({ data }) => {\n * if (typeof data === 'string') {\n * return data.replace(/password=\\w+/g, 'password=***');\n * }\n * return data;\n * };\n * ```\n *\n * @public\n */\nexport type MaskFunction = (params: { data: any }) => any;\n\n/**\n * Function type for determining whether a span should be exported to Langfuse.\n *\n * @param params - Object containing the span to evaluate\n * @param params.otelSpan - The OpenTelemetry span to evaluate\n * @returns `true` if the span should be exported, `false` otherwise\n *\n * @example\n * ```typescript\n * const shouldExportSpan: ShouldExportSpan = ({ otelSpan }) => {\n * // Only export spans that took longer than 100ms\n * return otelSpan.duration[0] * 1000 + otelSpan.duration[1] / 1000000 > 100;\n * };\n * ```\n *\n * @public\n */\nexport type ShouldExportSpan = (params: { otelSpan: ReadableSpan }) => boolean;\n\n/**\n * Configuration parameters for the LangfuseSpanProcessor.\n *\n * @public\n */\nexport interface LangfuseSpanProcessorParams {\n /**\n * Custom OpenTelemetry span exporter. If not provided, a default OTLP exporter will be used.\n */\n exporter?: SpanExporter;\n\n /**\n * Langfuse public API key. Can also be set via LANGFUSE_PUBLIC_KEY environment variable.\n */\n publicKey?: string;\n\n /**\n * Langfuse secret API key. Can also be set via LANGFUSE_SECRET_KEY environment variable.\n */\n secretKey?: string;\n\n /**\n * Langfuse instance base URL. Can also be set via LANGFUSE_BASE_URL environment variable.\n * @defaultValue \"https://cloud.langfuse.com\"\n */\n baseUrl?: string;\n\n /**\n * Number of spans to batch before flushing. Can also be set via LANGFUSE_FLUSH_AT environment variable.\n */\n flushAt?: number;\n\n /**\n * Flush interval in seconds. Can also be set via LANGFUSE_FLUSH_INTERVAL environment variable.\n */\n flushInterval?: number;\n\n /**\n * Function to mask sensitive data in spans before export.\n */\n mask?: MaskFunction;\n\n /**\n * Function to determine whether a span should be exported to Langfuse.\n */\n shouldExportSpan?: ShouldExportSpan;\n\n /**\n * Environment identifier for the traces. Can also be set via LANGFUSE_TRACING_ENVIRONMENT environment variable.\n */\n environment?: string;\n\n /**\n * Release identifier for the traces. Can also be set via LANGFUSE_RELEASE environment variable.\n */\n release?: string;\n\n /**\n * Request timeout in seconds. Can also be set via LANGFUSE_TIMEOUT environment variable.\n * @defaultValue 5\n */\n timeout?: number;\n\n /**\n * Additional HTTP headers to include with requests.\n */\n additionalHeaders?: Record<string, string>;\n}\n\n/**\n * OpenTelemetry span processor for sending spans to Langfuse.\n *\n * This processor extends the standard BatchSpanProcessor to provide:\n * - Automatic batching and flushing of spans to Langfuse\n * - Media content extraction and upload from base64 data URIs\n * - Data masking capabilities for sensitive information\n * - Conditional span export based on custom logic\n * - Environment and release tagging\n *\n * @example\n * ```typescript\n * import { NodeSDK } from '@opentelemetry/sdk-node';\n * import { LangfuseSpanProcessor } from '@langfuse/otel';\n *\n * const sdk = new NodeSDK({\n * spanProcessors: [\n * new LangfuseSpanProcessor({\n * publicKey: 'pk_...',\n * secretKey: 'sk_...',\n * baseUrl: 'https://cloud.langfuse.com',\n * environment: 'production',\n * mask: ({ data }) => {\n * // Mask sensitive data\n * return data.replace(/api_key=\\w+/g, 'api_key=***');\n * }\n * })\n * ]\n * });\n *\n * sdk.start();\n * ```\n *\n * @public\n */\nexport class LangfuseSpanProcessor extends BatchSpanProcessor {\n private pendingMediaUploads: Record<string, Promise<any>> = {};\n\n private publicKey?: string;\n private baseUrl?: string;\n private environment?: string;\n private release?: string;\n private mask?: MaskFunction;\n private shouldExportSpan?: ShouldExportSpan;\n private apiClient: LangfuseAPIClient;\n\n /**\n * Creates a new LangfuseSpanProcessor instance.\n *\n * @param params - Configuration parameters for the processor\n *\n * @example\n * ```typescript\n * const processor = new LangfuseSpanProcessor({\n * publicKey: 'pk_...',\n * secretKey: 'sk_...',\n * environment: 'staging',\n * flushAt: 10,\n * flushInterval: 2,\n * mask: ({ data }) => {\n * // Custom masking logic\n * return typeof data === 'string'\n * ? data.replace(/secret_\\w+/g, 'secret_***')\n * : data;\n * },\n * shouldExportSpan: ({ otelSpan }) => {\n * // Only export spans from specific services\n * return otelSpan.name.startsWith('my-service');\n * }\n * });\n * ```\n */\n constructor(params?: LangfuseSpanProcessorParams) {\n const logger = getGlobalLogger();\n\n const publicKey = params?.publicKey ?? getEnv(\"LANGFUSE_PUBLIC_KEY\");\n const secretKey = params?.secretKey ?? getEnv(\"LANGFUSE_SECRET_KEY\");\n const baseUrl =\n params?.baseUrl ??\n getEnv(\"LANGFUSE_BASE_URL\") ??\n getEnv(\"LANGFUSE_BASEURL\") ?? // legacy v2\n \"https://cloud.langfuse.com\";\n\n if (!params?.exporter && !publicKey) {\n logger.warn(\n \"No exporter configured and no public key provided in constructor or as LANGFUSE_PUBLIC_KEY env var. Span exports will fail.\",\n );\n }\n if (!params?.exporter && !secretKey) {\n logger.warn(\n \"No exporter configured and no secret key provided in constructor or as LANGFUSE_SECRET_KEY env var. Span exports will fail.\",\n );\n }\n const flushAt = params?.flushAt ?? getEnv(\"LANGFUSE_FLUSH_AT\");\n const flushIntervalSeconds =\n params?.flushInterval ?? getEnv(\"LANGFUSE_FLUSH_INTERVAL\");\n\n const authHeaderValue = uint8ArrayToBase64(\n new TextEncoder().encode(`${publicKey}:${secretKey}`),\n );\n const timeoutSeconds =\n params?.timeout ?? Number(getEnv(\"LANGFUSE_TIMEOUT\") ?? 5);\n\n const exporter =\n params?.exporter ??\n new OTLPTraceExporter({\n url: `${baseUrl}/api/public/otel/v1/traces`,\n headers: {\n Authorization: `Basic ${authHeaderValue}`,\n x_langfuse_sdk_name: \"javascript\",\n x_langfuse_sdk_version: LANGFUSE_SDK_VERSION,\n x_langfuse_public_key: publicKey ?? \"<missing>\",\n ...params?.additionalHeaders,\n },\n timeoutMillis: timeoutSeconds * 1_000,\n });\n\n super(exporter, {\n maxExportBatchSize: flushAt ? Number(flushAt) : undefined,\n scheduledDelayMillis: flushIntervalSeconds\n ? Number(flushIntervalSeconds) * 1_000\n : undefined,\n });\n\n this.publicKey = publicKey;\n this.baseUrl = baseUrl;\n this.environment =\n params?.environment ?? getEnv(\"LANGFUSE_TRACING_ENVIRONMENT\");\n this.release = params?.release ?? getEnv(\"LANGFUSE_RELEASE\");\n this.mask = params?.mask;\n this.shouldExportSpan = params?.shouldExportSpan;\n this.apiClient = new LangfuseAPIClient({\n baseUrl: this.baseUrl,\n username: this.publicKey,\n password: secretKey,\n xLangfusePublicKey: this.publicKey,\n xLangfuseSdkVersion: LANGFUSE_SDK_VERSION,\n xLangfuseSdkName: \"javascript\",\n environment: \"\", // noop as baseUrl is set\n headers: params?.additionalHeaders,\n });\n\n logger.debug(\"Initialized LangfuseSpanProcessor with params:\", {\n publicKey,\n baseUrl,\n environment: this.environment,\n release: this.release,\n timeoutSeconds,\n flushAt,\n flushIntervalSeconds,\n });\n\n // Warn if crypto is not available\n if (!isCryptoAvailable) {\n logger.warn(\n \"[Langfuse] Crypto module not available in this runtime. Media upload functionality will be disabled. \" +\n \"Spans will still be processed normally, but any media content in base64 data URIs will not be uploaded to Langfuse.\",\n );\n }\n }\n\n private get logger(): Logger {\n return getGlobalLogger();\n }\n\n /**\n * Called when a span is started. Adds environment and release attributes to the span.\n *\n * @param span - The span that was started\n * @param parentContext - The parent context\n *\n * @override\n */\n public onStart(span: Span, parentContext: any): void {\n span.setAttributes({\n [LangfuseOtelSpanAttributes.ENVIRONMENT]: this.environment,\n [LangfuseOtelSpanAttributes.RELEASE]: this.release,\n });\n\n return super.onStart(span, parentContext);\n }\n\n /**\n * Called when a span ends. Processes the span for export to Langfuse.\n *\n * This method:\n * 1. Checks if the span should be exported using the shouldExportSpan function\n * 2. Applies data masking to sensitive attributes\n * 3. Handles media content extraction and upload\n * 4. Logs span details in debug mode\n * 5. Passes the span to the parent processor for export\n *\n * @param span - The span that ended\n *\n * @override\n */\n public onEnd(span: ReadableSpan): void {\n if (this.shouldExportSpan) {\n try {\n if (this.shouldExportSpan({ otelSpan: span }) === false) return;\n } catch (err) {\n this.logger.error(\n \"ShouldExportSpan failed with error. Excluding span. Error: \",\n err,\n );\n\n return;\n }\n }\n\n this.applyMaskInPlace(span);\n this.handleMediaInPlace(span);\n\n this.logger.debug(\n `Processed span:\\n${JSON.stringify(\n {\n name: span.name,\n traceId: span.spanContext().traceId,\n spanId: span.spanContext().spanId,\n parentSpanId: span.parentSpanContext?.spanId ?? null,\n attributes: span.attributes,\n startTime: new Date(hrTimeToMilliseconds(span.startTime)),\n endTime: new Date(hrTimeToMilliseconds(span.endTime)),\n durationMs: hrTimeToMilliseconds(span.duration),\n kind: span.kind,\n status: span.status,\n resource: span.resource.attributes,\n instrumentationScope: span.instrumentationScope,\n },\n null,\n 2,\n )}`,\n );\n\n super.onEnd(span);\n }\n\n private async flush(): Promise<void> {\n await Promise.all(Object.values(this.pendingMediaUploads)).catch((e) => {\n this.logger.error(\n e instanceof Error ? e.message : \"Unhandled media upload error\",\n );\n });\n }\n\n /**\n * Forces an immediate flush of all pending spans and media uploads.\n *\n * @returns Promise that resolves when all pending operations are complete\n *\n * @override\n */\n public async forceFlush(): Promise<void> {\n await this.flush();\n\n return super.forceFlush();\n }\n\n /**\n * Gracefully shuts down the processor, ensuring all pending operations are completed.\n *\n * @returns Promise that resolves when shutdown is complete\n *\n * @override\n */\n public async shutdown(): Promise<void> {\n await this.flush();\n\n return super.shutdown();\n }\n\n private handleMediaInPlace(span: ReadableSpan): void {\n // Skip media handling if crypto is not available\n if (!isCryptoAvailable) {\n this.logger.debug(\n \"[Langfuse] Crypto not available, skipping media processing\",\n );\n return;\n }\n\n const mediaAttributes = [\n LangfuseOtelSpanAttributes.OBSERVATION_INPUT,\n LangfuseOtelSpanAttributes.TRACE_INPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,\n LangfuseOtelSpanAttributes.TRACE_OUTPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_METADATA,\n LangfuseOtelSpanAttributes.TRACE_METADATA,\n ];\n\n for (const mediaAttribute of mediaAttributes) {\n const mediaRelevantAttributeKeys = Object.keys(span.attributes).filter(\n (attributeName) => attributeName.startsWith(mediaAttribute),\n );\n\n for (const key of mediaRelevantAttributeKeys) {\n const value = span.attributes[key];\n\n if (typeof value !== \"string\") {\n this.logger.warn(\n `Span attribute ${mediaAttribute} is not a stringified object. Skipping media handling.`,\n );\n\n continue;\n }\n\n // Find media base64 data URI\n let mediaReplacedValue = value;\n const regex = /data:[^;]+;base64,[A-Za-z0-9+/]+=*/g;\n const foundMedia = [...new Set(value.match(regex) ?? [])];\n\n if (foundMedia.length === 0) continue;\n\n for (const mediaDataUri of foundMedia) {\n // For each media, create media tag and initiate upload\n const media = new LangfuseMedia({\n base64DataUri: mediaDataUri,\n source: \"base64_data_uri\",\n });\n\n if (!media.tag) {\n this.logger.warn(\n \"Failed to create Langfuse media tag. Skipping media item.\",\n );\n\n continue;\n }\n\n const uploadPromise: Promise<void> = this.processMediaItem({\n media,\n traceId: span.spanContext().traceId,\n observationId: span.spanContext().spanId,\n field: mediaAttribute.includes(\"input\")\n ? \"input\"\n : mediaAttribute.includes(\"output\")\n ? \"output\"\n : \"metadata\", // todo: make more robust\n });\n\n const promiseId = generateUUID();\n this.pendingMediaUploads[promiseId] = uploadPromise;\n\n uploadPromise.finally(() => {\n delete this.pendingMediaUploads[promiseId];\n });\n\n // Replace original attribute with media escaped attribute\n mediaReplacedValue = mediaReplacedValue.replaceAll(\n mediaDataUri,\n media.tag,\n );\n }\n\n span.attributes[key] = mediaReplacedValue;\n }\n }\n }\n\n private applyMaskInPlace(span: ReadableSpan): void {\n const maskCandidates = [\n LangfuseOtelSpanAttributes.OBSERVATION_INPUT,\n LangfuseOtelSpanAttributes.TRACE_INPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,\n LangfuseOtelSpanAttributes.TRACE_OUTPUT,\n LangfuseOtelSpanAttributes.OBSERVATION_METADATA,\n LangfuseOtelSpanAttributes.TRACE_METADATA,\n ];\n\n for (const maskCandidate of maskCandidates) {\n if (maskCandidate in span.attributes) {\n span.attributes[maskCandidate] = this.applyMask(\n span.attributes[maskCandidate],\n );\n }\n }\n }\n\n private applyMask<T>(data: T): T | string {\n if (!this.mask) return data;\n\n try {\n return this.mask({ data });\n } catch (err) {\n this.logger.warn(\n `Applying mask function failed due to error, fully masking property. Error: ${err}`,\n );\n\n return \"<fully masked due to failed mask function>\";\n }\n }\n\n private async processMediaItem({\n media,\n traceId,\n observationId,\n field,\n }: {\n media: LangfuseMedia;\n traceId: string;\n observationId?: string;\n field: string;\n }): Promise<void> {\n try {\n if (\n !media.contentLength ||\n !media._contentType ||\n !media.contentSha256Hash ||\n !media._contentBytes\n ) {\n return;\n }\n\n const { uploadUrl, mediaId } = await this.apiClient.media.getUploadUrl({\n contentLength: media.contentLength,\n traceId,\n observationId,\n field,\n contentType: media._contentType,\n sha256Hash: media.contentSha256Hash,\n });\n\n if (!uploadUrl) {\n this.logger.debug(\n `Media status: Media with ID ${media.id} already uploaded. Skipping duplicate upload.`,\n );\n\n return;\n }\n\n if (media.id !== mediaId) {\n this.logger.error(\n `Media integrity error: Media ID mismatch between SDK (${media.id}) and Server (${mediaId}). Upload cancelled. Please check media ID generation logic.`,\n );\n\n return;\n }\n\n this.logger.debug(`Uploading media ${mediaId}...`);\n\n const startTime = Date.now();\n\n const uploadResponse = await this.uploadMediaWithBackoff({\n uploadUrl,\n contentBytes: media._contentBytes,\n contentType: media._contentType,\n contentSha256Hash: media.contentSha256Hash,\n maxRetries: 3,\n baseDelay: 1000,\n });\n\n if (!uploadResponse) {\n throw Error(\"Media upload process failed\");\n }\n\n await this.apiClient.media.patch(mediaId, {\n uploadedAt: new Date().toISOString(),\n uploadHttpStatus: uploadResponse.status,\n uploadHttpError: await uploadResponse.text(),\n uploadTimeMs: Date.now() - startTime,\n });\n\n this.logger.debug(`Media upload status reported for ${mediaId}`);\n } catch (err) {\n this.logger.error(`Error processing media item: ${err}`);\n }\n }\n\n private async uploadMediaWithBackoff(params: {\n uploadUrl: string;\n contentType: string;\n contentSha256Hash: string;\n contentBytes: Buffer;\n maxRetries: number;\n baseDelay: number;\n }) {\n const {\n uploadUrl,\n contentType,\n contentSha256Hash,\n contentBytes,\n maxRetries,\n baseDelay,\n } = params;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const uploadResponse = await fetch(uploadUrl, {\n method: \"PUT\",\n body: contentBytes,\n headers: {\n \"Content-Type\": contentType,\n \"x-amz-checksum-sha256\": contentSha256Hash,\n \"x-ms-blob-type\": \"BlockBlob\",\n },\n });\n\n if (\n attempt < maxRetries &&\n uploadResponse.status !== 200 &&\n uploadResponse.status !== 201\n ) {\n throw new Error(`Upload failed with status ${uploadResponse.status}`);\n }\n\n return uploadResponse;\n } catch (e) {\n if (attempt === maxRetries) {\n throw e;\n }\n\n const delay = baseDelay * Math.pow(2, attempt);\n const jitter = Math.random() * 1000;\n\n await new Promise((resolve) => setTimeout(resolve, delay + jitter));\n }\n }\n }\n}\n","import { getGlobalLogger, uint8ArrayToBase64 } from \"@langfuse/core\";\n\n/**\n * Cross-platform hash utilities with graceful fallbacks.\n *\n * This module attempts to load crypto functionality from various JavaScript runtimes:\n * - Node.js (using the 'crypto' module)\n * - Deno (using 'node:crypto')\n * - Edge runtimes like Cloudflare Workers (using the Web Crypto API)\n *\n * If crypto is not available, functions will throw errors and isCryptoAvailable will be false.\n */\n\n// Cross-platform hash utilities with graceful fallbacks\nlet cryptoModule: any;\n\n/**\n * Indicates whether cryptographic functions are available in the current runtime.\n *\n * @example\n * ```typescript\n * import { isCryptoAvailable } from '@langfuse/otel';\n *\n * if (isCryptoAvailable) {\n * // Safe to use hash functions\n * const hash = getSha256HashFromBytes(data);\n * } else {\n * // Crypto not available, handle gracefully\n * console.warn('Crypto functions not available in this runtime');\n * }\n * ```\n *\n * @public\n */\nlet isCryptoAvailable = false;\n\ntry {\n if (typeof (globalThis as any).Deno !== \"undefined\") {\n // Deno\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n cryptoModule = require(\"node:crypto\");\n isCryptoAvailable = true;\n } else if (typeof process !== \"undefined\" && process.versions?.node) {\n // Node\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n cryptoModule = require(\"crypto\");\n isCryptoAvailable = true;\n } else if (typeof crypto !== \"undefined\") {\n // Edge runtime (Cloudflare Workers, Vercel Cloud Function)\n cryptoModule = crypto;\n isCryptoAvailable = true;\n }\n} catch (error) {\n getGlobalLogger().warn(\n \"Crypto module not available. Media handling will be disabled.\",\n error,\n );\n\n isCryptoAvailable = false;\n}\n\nexport { isCryptoAvailable };\n\n/**\n * Computes the SHA-256 hash of the provided data.\n *\n * @param data - The data to hash\n * @returns The SHA-256 hash as a Uint8Array\n * @throws Error if crypto module is not available\n * @private\n */\nfunction sha256(data: Uint8Array): Uint8Array {\n if (!isCryptoAvailable || !cryptoModule) {\n throw new Error(\"Crypto module not available\");\n }\n\n return cryptoModule.createHash(\"sha256\").update(data).digest();\n}\n\n/**\n * Generates a base64-encoded SHA-256 hash from the provided bytes.\n *\n * This function is used throughout the Langfuse OpenTelemetry integration\n * to generate content hashes for media files and ensure data integrity.\n *\n * @param data - The bytes to hash\n * @returns The base64-encoded SHA-256 hash\n * @throws Error if crypto module is not available\n *\n * @example\n * ```typescript\n * import { getSha256HashFromBytes } from '@langfuse/otel';\n *\n * const data = new TextEncoder().encode('Hello World');\n * const hash = getSha256HashFromBytes(data);\n * console.log(hash); // \"pZGm1Av0IEBKARczz7exkNYsZb8LzaMrV7J32a2fFG4=\"\n * ```\n *\n * @public\n */\nexport function getSha256HashFromBytes(data: Uint8Array): string {\n if (!isCryptoAvailable) {\n throw new Error(\"Crypto module not available\");\n }\n\n const hash = sha256(data);\n\n return uint8ArrayToBase64(hash);\n}\n","import { getGlobalLogger, type MediaContentType } from \"@langfuse/core\";\n\nimport { getSha256HashFromBytes, isCryptoAvailable } from \"./hash.js\";\n\n/**\n * Parameters for creating a LangfuseMedia instance.\n *\n * Supports two input formats:\n * - Base64 data URI (e.g., \"data:image/png;base64,...\")\n * - Raw bytes with explicit content type\n *\n * @public\n */\nexport type LangfuseMediaParams =\n | {\n /** Indicates the media is provided as a base64 data URI */\n source: \"base64_data_uri\";\n /** The complete base64 data URI string */\n base64DataUri: string;\n }\n | {\n /** Indicates the media is provided as raw bytes */\n source: \"bytes\";\n /** The raw content bytes */\n contentBytes: Buffer;\n /** The MIME type of the content */\n contentType: MediaContentType;\n };\n\n/**\n * A class for wrapping media objects for upload to Langfuse.\n *\n * This class handles the preparation and formatting of media content for Langfuse,\n * supporting both base64 data URIs and raw content bytes. It automatically:\n * - Parses base64 data URIs to extract content type and bytes\n * - Generates SHA-256 hashes for content integrity\n * - Creates unique media IDs based on content hash\n * - Formats media references for embedding in traces\n *\n * @example\n * ```typescript\n * // From base64 data URI\n * const media1 = new LangfuseMedia({\n * source: \"base64_data_uri\",\n * base64DataUri: \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==\"\n * });\n *\n * // From raw bytes\n * const media2 = new LangfuseMedia({\n * source: \"bytes\",\n * contentBytes: Buffer.from(\"Hello World\"),\n * contentType: \"text/plain\"\n * });\n *\n * console.log(media1.id); // Unique media ID\n * console.log(media1.tag); // Media reference tag\n * ```\n *\n * @public\n */\nclass LangfuseMedia {\n _contentBytes?: Buffer;\n _contentType?: MediaContentType;\n _source?: string;\n\n /**\n * Creates a new LangfuseMedia instance.\n *\n * @param params - Media parameters specifying the source and content\n *\n * @example\n * ```typescript\n * // Create from base64 data URI\n * const media = new LangfuseMedia({\n * source: \"base64_data_uri\",\n * base64DataUri: \"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ...\"\n * });\n * ```\n */\n constructor(params: LangfuseMediaParams) {\n const { source } = params;\n\n this._source = source;\n\n if (source === \"base64_data_uri\") {\n const [contentBytesParsed, contentTypeParsed] = this.parseBase64DataUri(\n params.base64DataUri,\n );\n this._contentBytes = contentBytesParsed;\n this._contentType = contentTypeParsed;\n } else {\n this._contentBytes = params.contentBytes;\n this._contentType = params.contentType;\n }\n }\n\n /**\n * Parses a base64 data URI to extract content bytes and type.\n *\n * @param data - The base64 data URI string\n * @returns Tuple of [contentBytes, contentType] or [undefined, undefined] on error\n * @private\n */\n private parseBase64DataUri(\n data: string,\n ): [Buffer | undefined, MediaContentType | undefined] {\n try {\n if (!data || typeof data !== \"string\") {\n throw new Error(\"Data URI is not a string\");\n }\n\n if (!data.startsWith(\"data:\")) {\n throw new Error(\"Data URI does not start with 'data:'\");\n }\n\n const [header, actualData] = data.slice(5).split(\",\", 2);\n if (!header || !actualData) {\n throw new Error(\"Invalid URI\");\n }\n\n const headerParts = header.split(\";\");\n if (!headerParts.includes(\"base64\")) {\n throw new Error(\"Data is not base64 encoded\");\n }\n\n const contentType = headerParts[0];\n if (!contentType) {\n throw new Error(\"Content type is empty\");\n }\n\n return [\n Buffer.from(actualData, \"base64\"),\n contentType as MediaContentType,\n ];\n } catch (error) {\n getGlobalLogger().error(\"Error parsing base64 data URI\", error);\n return [undefined, undefined];\n }\n }\n\n /**\n * Gets a unique identifier for this media based on its content hash.\n *\n * The ID is derived from the first 22 characters of the URL-safe base64-encoded\n * SHA-256 hash of the content.\n *\n * @returns The unique media ID, or null if hash generation failed\n *\n * @example\n * ```typescript\n * const media = new LangfuseMedia({...});\n * console.log(media.id); // \"A1B2C3D4E5F6G7H8I9J0K1\"\n * ```\n */\n get id(): string | null {\n if (!this.contentSha256Hash) return null;\n\n const urlSafeContentHash = this.contentSha256Hash\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\");\n\n return urlSafeContentHash.slice(0, 22);\n }\n\n /**\n * Gets the length of the media content in bytes.\n *\n * @returns The content length in bytes, or undefined if no content is available\n */\n get contentLength(): number | undefined {\n return this._contentBytes?.length;\n }\n\n /**\n * Gets the SHA-256 hash of the media content.\n *\n * The hash is used for content integrity verification and generating unique media IDs.\n * Returns undefined if crypto is not available or hash generation fails.\n *\n * @returns The base64-encoded SHA-256 hash, or undefined if unavailable\n */\n get contentSha256Hash(): string | undefined {\n if (!this._contentBytes || !isCryptoAvailable) {\n return undefined;\n }\n\n try {\n return getSha256HashFromBytes(this._contentBytes);\n } catch (error) {\n getGlobalLogger().warn(\n \"[Langfuse] Failed to generate SHA-256 hash for media content:\",\n error,\n );\n\n return undefined;\n }\n }\n\n /**\n * Gets the media reference tag for embedding in trace data.\n *\n * The tag format is: `@@@langfuseMedia:type=<contentType>|id=<mediaId>|source=<source>@@@`\n * This tag can be embedded in trace attributes and will be replaced with actual\n * media content when the trace is viewed in Langfuse.\n *\n * @returns The media reference tag, or null if required data is missing\n *\n * @example\n * ```typescript\n * const media = new LangfuseMedia({...});\n * console.log(media.tag);\n * // \"@@@langfuseMedia:type=image/png|id=A1B2C3D4E5F6G7H8I9J0K1|source=base64_data_uri@@@\"\n * ```\n */\n get tag(): string | null {\n if (!this._contentType || !this._source || !this.id) return null;\n\n return `@@@langfuseMedia:type=${this._contentType}|id=${this.id}|source=${this._source}@@@`;\n }\n\n /**\n * Gets the media content as a base64 data URI.\n *\n * @returns The complete data URI string, or null if no content is available\n *\n * @example\n * ```typescript\n * const media = new LangfuseMedia({...});\n * console.log(media.base64DataUri);\n * // \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB...\"\n * ```\n */\n get base64DataUri(): string | null {\n if (!this._contentBytes) return null;\n\n return `data:${this._contentType};base64,${Buffer.from(this._contentBytes).toString(\"base64\")}`;\n }\n\n /**\n * Serializes the media to JSON (returns the base64 data URI).\n *\n * @returns The base64 data URI, or null if no content is available\n */\n toJSON(): string | null {\n return this.base64DataUri;\n }\n}\n\nexport { LangfuseMedia, type MediaContentType };\n"],"mappings":"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","names":["import_core","import_core","_a","_a"]}
|