@langfuse/otel 4.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2023 Finto Technologies GmbH
2
+
3
+ Copyright (c) 2022 PostHog (part of Hiberly Inc)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/hash.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ declare let isCryptoAvailable: boolean;
2
+ export { isCryptoAvailable };
3
+ export declare function getSha256HashFromBytes(data: Uint8Array): string;
4
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAIA,QAAA,IAAI,iBAAiB,SAAQ,CAAC;AA2B9B,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAU7B,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAQ/D"}
package/dist/hash.js ADDED
@@ -0,0 +1,42 @@
1
+ var _a;
2
+ import { getGlobalLogger, uint8ArrayToBase64 } from "@langfuse/core";
3
+ // Cross-platform hash utilities with graceful fallbacks
4
+ let cryptoModule;
5
+ let isCryptoAvailable = false;
6
+ try {
7
+ if (typeof globalThis.Deno !== "undefined") {
8
+ // Deno
9
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
10
+ cryptoModule = require("node:crypto");
11
+ isCryptoAvailable = true;
12
+ }
13
+ else if (typeof process !== "undefined" && ((_a = process.versions) === null || _a === void 0 ? void 0 : _a.node)) {
14
+ // Node
15
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
16
+ cryptoModule = require("crypto");
17
+ isCryptoAvailable = true;
18
+ }
19
+ else if (typeof crypto !== "undefined") {
20
+ // Edge runtime (Cloudflare Workers, Vercel Cloud Function)
21
+ cryptoModule = crypto;
22
+ isCryptoAvailable = true;
23
+ }
24
+ }
25
+ catch (error) {
26
+ getGlobalLogger().warn("Crypto module not available. Media handling will be disabled.", error);
27
+ isCryptoAvailable = false;
28
+ }
29
+ export { isCryptoAvailable };
30
+ function sha256(data) {
31
+ if (!isCryptoAvailable || !cryptoModule) {
32
+ throw new Error("Crypto module not available");
33
+ }
34
+ return cryptoModule.createHash("sha256").update(data).digest();
35
+ }
36
+ export function getSha256HashFromBytes(data) {
37
+ if (!isCryptoAvailable) {
38
+ throw new Error("Crypto module not available");
39
+ }
40
+ const hash = sha256(data);
41
+ return uint8ArrayToBase64(hash);
42
+ }
package/dist/index.cjs ADDED
@@ -0,0 +1,477 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ LangfuseMedia: () => LangfuseMedia,
24
+ LangfuseSpanProcessor: () => LangfuseSpanProcessor
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/span-processor.ts
29
+ var import_core3 = require("@langfuse/core");
30
+ var import_core4 = require("@opentelemetry/core");
31
+ var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
32
+ var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
33
+
34
+ // src/hash.ts
35
+ var import_core = require("@langfuse/core");
36
+ var cryptoModule;
37
+ var isCryptoAvailable = false;
38
+ var _a;
39
+ try {
40
+ if (typeof globalThis.Deno !== "undefined") {
41
+ cryptoModule = require("crypto");
42
+ isCryptoAvailable = true;
43
+ } else if (typeof process !== "undefined" && ((_a = process.versions) == null ? void 0 : _a.node)) {
44
+ cryptoModule = require("crypto");
45
+ isCryptoAvailable = true;
46
+ } else if (typeof crypto !== "undefined") {
47
+ cryptoModule = crypto;
48
+ isCryptoAvailable = true;
49
+ }
50
+ } catch (error) {
51
+ (0, import_core.getGlobalLogger)().warn(
52
+ "Crypto module not available. Media handling will be disabled.",
53
+ error
54
+ );
55
+ isCryptoAvailable = false;
56
+ }
57
+ function sha256(data) {
58
+ if (!isCryptoAvailable || !cryptoModule) {
59
+ throw new Error("Crypto module not available");
60
+ }
61
+ return cryptoModule.createHash("sha256").update(data).digest();
62
+ }
63
+ function getSha256HashFromBytes(data) {
64
+ if (!isCryptoAvailable) {
65
+ throw new Error("Crypto module not available");
66
+ }
67
+ const hash = sha256(data);
68
+ return (0, import_core.uint8ArrayToBase64)(hash);
69
+ }
70
+
71
+ // src/media.ts
72
+ var import_core2 = require("@langfuse/core");
73
+ var LangfuseMedia = class {
74
+ constructor(params) {
75
+ const { source } = params;
76
+ this._source = source;
77
+ if (source === "base64_data_uri") {
78
+ const [contentBytesParsed, contentTypeParsed] = this.parseBase64DataUri(
79
+ params.base64DataUri
80
+ );
81
+ this._contentBytes = contentBytesParsed;
82
+ this._contentType = contentTypeParsed;
83
+ } else {
84
+ this._contentBytes = params.contentBytes;
85
+ this._contentType = params.contentType;
86
+ }
87
+ }
88
+ parseBase64DataUri(data) {
89
+ try {
90
+ if (!data || typeof data !== "string") {
91
+ throw new Error("Data URI is not a string");
92
+ }
93
+ if (!data.startsWith("data:")) {
94
+ throw new Error("Data URI does not start with 'data:'");
95
+ }
96
+ const [header, actualData] = data.slice(5).split(",", 2);
97
+ if (!header || !actualData) {
98
+ throw new Error("Invalid URI");
99
+ }
100
+ const headerParts = header.split(";");
101
+ if (!headerParts.includes("base64")) {
102
+ throw new Error("Data is not base64 encoded");
103
+ }
104
+ const contentType = headerParts[0];
105
+ if (!contentType) {
106
+ throw new Error("Content type is empty");
107
+ }
108
+ return [
109
+ Buffer.from(actualData, "base64"),
110
+ contentType
111
+ ];
112
+ } catch (error) {
113
+ (0, import_core2.getGlobalLogger)().error("Error parsing base64 data URI", error);
114
+ return [void 0, void 0];
115
+ }
116
+ }
117
+ get id() {
118
+ if (!this.contentSha256Hash) return null;
119
+ const urlSafeContentHash = this.contentSha256Hash.replaceAll("+", "-").replaceAll("/", "_");
120
+ return urlSafeContentHash.slice(0, 22);
121
+ }
122
+ get contentLength() {
123
+ var _a2;
124
+ return (_a2 = this._contentBytes) == null ? void 0 : _a2.length;
125
+ }
126
+ get contentSha256Hash() {
127
+ if (!this._contentBytes || !isCryptoAvailable) {
128
+ return void 0;
129
+ }
130
+ try {
131
+ return getSha256HashFromBytes(this._contentBytes);
132
+ } catch (error) {
133
+ (0, import_core2.getGlobalLogger)().warn(
134
+ "[Langfuse] Failed to generate SHA-256 hash for media content:",
135
+ error
136
+ );
137
+ return void 0;
138
+ }
139
+ }
140
+ get tag() {
141
+ if (!this._contentType || !this._source || !this.id) return null;
142
+ return `@@@langfuseMedia:type=${this._contentType}|id=${this.id}|source=${this._source}@@@`;
143
+ }
144
+ get base64DataUri() {
145
+ if (!this._contentBytes) return null;
146
+ return `data:${this._contentType};base64,${Buffer.from(this._contentBytes).toString("base64")}`;
147
+ }
148
+ toJSON() {
149
+ return this.base64DataUri;
150
+ }
151
+ };
152
+
153
+ // src/span-processor.ts
154
+ var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcessor {
155
+ constructor(params) {
156
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
157
+ const logger = (0, import_core3.getGlobalLogger)();
158
+ const publicKey = (_a2 = params.publicKey) != null ? _a2 : (0, import_core3.getEnv)("LANGFUSE_PUBLIC_KEY");
159
+ const secretKey = (_b = params.secretKey) != null ? _b : (0, import_core3.getEnv)("LANGFUSE_SECRET_KEY");
160
+ const baseUrl = (_e = (_d = (_c = params.baseUrl) != null ? _c : (0, import_core3.getEnv)("LANGFUSE_BASE_URL")) != null ? _d : (0, import_core3.getEnv)("LANGFUSE_BASEURL")) != null ? _e : (
161
+ // legacy v2
162
+ "https://cloud.langfuse.com"
163
+ );
164
+ if (!params.exporter && !publicKey) {
165
+ logger.warn(
166
+ "No exporter configured and no public key provided in constructor or as LANGFUSE_PUBLIC_KEY env var. Span exports will fail."
167
+ );
168
+ }
169
+ if (!params.exporter && !secretKey) {
170
+ logger.warn(
171
+ "No exporter configured and no secret key provided in constructor or as LANGFUSE_SECRET_KEY env var. Span exports will fail."
172
+ );
173
+ }
174
+ const flushAt = (_f = params.flushAt) != null ? _f : (0, import_core3.getEnv)("LANGFUSE_FLUSH_AT");
175
+ const flushIntervalSeconds = (_g = params.flushInterval) != null ? _g : (0, import_core3.getEnv)("LANGFUSE_FLUSH_INTERVAL");
176
+ const authHeaderValue = Buffer.from(`${publicKey}:${secretKey}`).toString(
177
+ "base64"
178
+ );
179
+ const timeoutSeconds = (_i = params.timeout) != null ? _i : Number((_h = (0, import_core3.getEnv)("LANGFUSE_TIMEOUT")) != null ? _h : 5);
180
+ const exporter = (_j = params.exporter) != null ? _j : new import_exporter_trace_otlp_http.OTLPTraceExporter({
181
+ url: `${baseUrl}/api/public/otel/v1/traces`,
182
+ headers: {
183
+ Authorization: `Basic ${authHeaderValue}`,
184
+ x_langfuse_sdk_name: "javascript",
185
+ x_langfuse_sdk_version: import_core3.LANGFUSE_SDK_VERSION,
186
+ x_langfuse_public_key: publicKey != null ? publicKey : "<missing>",
187
+ ...params.additionalHeaders
188
+ },
189
+ timeoutMillis: timeoutSeconds * 1e3
190
+ });
191
+ super(exporter, {
192
+ maxExportBatchSize: flushAt ? Number(flushAt) : void 0,
193
+ scheduledDelayMillis: flushIntervalSeconds ? Number(flushIntervalSeconds) * 1e3 : void 0
194
+ });
195
+ this.pendingMediaUploads = {};
196
+ this.publicKey = publicKey;
197
+ this.baseUrl = baseUrl;
198
+ this.environment = (_k = params.environment) != null ? _k : (0, import_core3.getEnv)("LANGFUSE_TRACING_ENVIRONMENT");
199
+ this.release = (_l = params.release) != null ? _l : (0, import_core3.getEnv)("LANGFUSE_RELEASE");
200
+ this.mask = params.mask;
201
+ this.shouldExportSpan = params.shouldExportSpan;
202
+ this.apiClient = new import_core3.LangfuseAPIClient({
203
+ baseUrl: this.baseUrl,
204
+ username: this.publicKey,
205
+ password: secretKey,
206
+ xLangfusePublicKey: this.publicKey,
207
+ xLangfuseSdkVersion: import_core3.LANGFUSE_SDK_VERSION,
208
+ xLangfuseSdkName: "javascript",
209
+ environment: "",
210
+ // noop as baseUrl is set
211
+ headers: params.additionalHeaders
212
+ });
213
+ logger.debug("Initialized LangfuseSpanProcessor with params:", {
214
+ publicKey,
215
+ baseUrl,
216
+ environment: this.environment,
217
+ release: this.release,
218
+ timeoutSeconds,
219
+ flushAt,
220
+ flushIntervalSeconds
221
+ });
222
+ if (!isCryptoAvailable) {
223
+ logger.warn(
224
+ "[Langfuse] Crypto module not available in this runtime. Media upload functionality will be disabled. Spans will still be processed normally, but any media content in base64 data URIs will not be uploaded to Langfuse."
225
+ );
226
+ }
227
+ }
228
+ get logger() {
229
+ return (0, import_core3.getGlobalLogger)();
230
+ }
231
+ onStart(span, parentContext) {
232
+ span.setAttributes({
233
+ [import_core3.LangfuseOtelSpanAttributes.ENVIRONMENT]: this.environment,
234
+ [import_core3.LangfuseOtelSpanAttributes.RELEASE]: this.release
235
+ });
236
+ return super.onStart(span, parentContext);
237
+ }
238
+ onEnd(span) {
239
+ var _a2, _b;
240
+ if (this.shouldExportSpan) {
241
+ try {
242
+ if (this.shouldExportSpan({ otelSpan: span }) === false) return;
243
+ } catch (err) {
244
+ this.logger.error(
245
+ "ShouldExportSpan failed with error. Excluding span. Error: ",
246
+ err
247
+ );
248
+ return;
249
+ }
250
+ }
251
+ this.applyMaskInPlace(span);
252
+ this.handleMediaInPlace(span);
253
+ this.logger.debug(
254
+ `Processed span:
255
+ ${JSON.stringify(
256
+ {
257
+ name: span.name,
258
+ traceId: span.spanContext().traceId,
259
+ spanId: span.spanContext().spanId,
260
+ parentSpanId: (_b = (_a2 = span.parentSpanContext) == null ? void 0 : _a2.spanId) != null ? _b : null,
261
+ attributes: span.attributes,
262
+ startTime: new Date((0, import_core4.hrTimeToMilliseconds)(span.startTime)),
263
+ endTime: new Date((0, import_core4.hrTimeToMilliseconds)(span.endTime)),
264
+ durationMs: (0, import_core4.hrTimeToMilliseconds)(span.duration),
265
+ kind: span.kind,
266
+ status: span.status,
267
+ resource: span.resource.attributes,
268
+ instrumentationScope: span.instrumentationScope
269
+ },
270
+ null,
271
+ 2
272
+ )}`
273
+ );
274
+ super.onEnd(span);
275
+ }
276
+ async flush() {
277
+ await Promise.all(Object.values(this.pendingMediaUploads)).catch((e) => {
278
+ this.logger.error(
279
+ e instanceof Error ? e.message : "Unhandled media upload error"
280
+ );
281
+ });
282
+ }
283
+ async forceFlush() {
284
+ await this.flush();
285
+ return super.forceFlush();
286
+ }
287
+ async shutdown() {
288
+ await this.flush();
289
+ return super.shutdown();
290
+ }
291
+ handleMediaInPlace(span) {
292
+ var _a2;
293
+ if (!isCryptoAvailable) {
294
+ this.logger.debug(
295
+ "[Langfuse] Crypto not available, skipping media processing"
296
+ );
297
+ return;
298
+ }
299
+ const mediaAttributes = [
300
+ import_core3.LangfuseOtelSpanAttributes.OBSERVATION_INPUT,
301
+ import_core3.LangfuseOtelSpanAttributes.TRACE_INPUT,
302
+ import_core3.LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,
303
+ import_core3.LangfuseOtelSpanAttributes.TRACE_OUTPUT,
304
+ import_core3.LangfuseOtelSpanAttributes.OBSERVATION_METADATA,
305
+ import_core3.LangfuseOtelSpanAttributes.TRACE_METADATA
306
+ ];
307
+ for (const mediaAttribute of mediaAttributes) {
308
+ const mediaRelevantAttributeKeys = Object.keys(span.attributes).filter(
309
+ (attributeName) => attributeName.startsWith(mediaAttribute)
310
+ );
311
+ for (const key of mediaRelevantAttributeKeys) {
312
+ const value = span.attributes[key];
313
+ if (typeof value !== "string") {
314
+ this.logger.warn(
315
+ `Span attribute ${mediaAttribute} is not a stringified object. Skipping media handling.`
316
+ );
317
+ continue;
318
+ }
319
+ let mediaReplacedValue = value;
320
+ const regex = /data:[^;]+;base64,[A-Za-z0-9+/]+=*/g;
321
+ const foundMedia = [...new Set((_a2 = value.match(regex)) != null ? _a2 : [])];
322
+ if (foundMedia.length === 0) continue;
323
+ for (const mediaDataUri of foundMedia) {
324
+ const media = new LangfuseMedia({
325
+ base64DataUri: mediaDataUri,
326
+ source: "base64_data_uri"
327
+ });
328
+ if (!media.tag) {
329
+ this.logger.warn(
330
+ "Failed to create Langfuse media tag. Skipping media item."
331
+ );
332
+ continue;
333
+ }
334
+ const uploadPromise = this.processMediaItem({
335
+ media,
336
+ traceId: span.spanContext().traceId,
337
+ observationId: span.spanContext().spanId,
338
+ field: mediaAttribute.includes("input") ? "input" : mediaAttribute.includes("output") ? "output" : "metadata"
339
+ // todo: make more robust
340
+ });
341
+ const promiseId = (0, import_core3.generateUUID)();
342
+ this.pendingMediaUploads[promiseId] = uploadPromise;
343
+ uploadPromise.finally(() => {
344
+ delete this.pendingMediaUploads[promiseId];
345
+ });
346
+ mediaReplacedValue = mediaReplacedValue.replaceAll(
347
+ mediaDataUri,
348
+ media.tag
349
+ );
350
+ }
351
+ span.attributes[key] = mediaReplacedValue;
352
+ }
353
+ }
354
+ }
355
+ applyMaskInPlace(span) {
356
+ const maskCandidates = [
357
+ import_core3.LangfuseOtelSpanAttributes.OBSERVATION_INPUT,
358
+ import_core3.LangfuseOtelSpanAttributes.TRACE_INPUT,
359
+ import_core3.LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,
360
+ import_core3.LangfuseOtelSpanAttributes.TRACE_OUTPUT,
361
+ import_core3.LangfuseOtelSpanAttributes.OBSERVATION_METADATA,
362
+ import_core3.LangfuseOtelSpanAttributes.TRACE_METADATA
363
+ ];
364
+ for (const maskCandidate of maskCandidates) {
365
+ if (maskCandidate in span.attributes) {
366
+ span.attributes[maskCandidate] = this.applyMask(
367
+ span.attributes[maskCandidate]
368
+ );
369
+ }
370
+ }
371
+ }
372
+ applyMask(data) {
373
+ if (!this.mask) return data;
374
+ try {
375
+ return this.mask({ data });
376
+ } catch (err) {
377
+ this.logger.warn(
378
+ `Applying mask function failed due to error, fully masking property. Error: ${err}`
379
+ );
380
+ return "<fully masked due to failed mask function>";
381
+ }
382
+ }
383
+ async processMediaItem({
384
+ media,
385
+ traceId,
386
+ observationId,
387
+ field
388
+ }) {
389
+ try {
390
+ if (!media.contentLength || !media._contentType || !media.contentSha256Hash || !media._contentBytes) {
391
+ return;
392
+ }
393
+ const { uploadUrl, mediaId } = await this.apiClient.media.getUploadUrl({
394
+ contentLength: media.contentLength,
395
+ traceId,
396
+ observationId,
397
+ field,
398
+ contentType: media._contentType,
399
+ sha256Hash: media.contentSha256Hash
400
+ });
401
+ if (!uploadUrl) {
402
+ this.logger.debug(
403
+ `Media status: Media with ID ${media.id} already uploaded. Skipping duplicate upload.`
404
+ );
405
+ return;
406
+ }
407
+ if (media.id !== mediaId) {
408
+ this.logger.error(
409
+ `Media integrity error: Media ID mismatch between SDK (${media.id}) and Server (${mediaId}). Upload cancelled. Please check media ID generation logic.`
410
+ );
411
+ return;
412
+ }
413
+ this.logger.debug(`Uploading media ${mediaId}...`);
414
+ const startTime = Date.now();
415
+ const uploadResponse = await this.uploadMediaWithBackoff({
416
+ uploadUrl,
417
+ contentBytes: media._contentBytes,
418
+ contentType: media._contentType,
419
+ contentSha256Hash: media.contentSha256Hash,
420
+ maxRetries: 3,
421
+ baseDelay: 1e3
422
+ });
423
+ if (!uploadResponse) {
424
+ throw Error("Media upload process failed");
425
+ }
426
+ await this.apiClient.media.patch(mediaId, {
427
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
428
+ uploadHttpStatus: uploadResponse.status,
429
+ uploadHttpError: await uploadResponse.text(),
430
+ uploadTimeMs: Date.now() - startTime
431
+ });
432
+ this.logger.debug(`Media upload status reported for ${mediaId}`);
433
+ } catch (err) {
434
+ this.logger.error(`Error processing media item: ${err}`);
435
+ }
436
+ }
437
+ async uploadMediaWithBackoff(params) {
438
+ const {
439
+ uploadUrl,
440
+ contentType,
441
+ contentSha256Hash,
442
+ contentBytes,
443
+ maxRetries,
444
+ baseDelay
445
+ } = params;
446
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
447
+ try {
448
+ const uploadResponse = await fetch(uploadUrl, {
449
+ method: "PUT",
450
+ body: contentBytes,
451
+ headers: {
452
+ "Content-Type": contentType,
453
+ "x-amz-checksum-sha256": contentSha256Hash,
454
+ "x-ms-blob-type": "BlockBlob"
455
+ }
456
+ });
457
+ if (attempt < maxRetries && uploadResponse.status !== 200 && uploadResponse.status !== 201) {
458
+ throw new Error(`Upload failed with status ${uploadResponse.status}`);
459
+ }
460
+ return uploadResponse;
461
+ } catch (e) {
462
+ if (attempt === maxRetries) {
463
+ throw e;
464
+ }
465
+ const delay = baseDelay * Math.pow(2, attempt);
466
+ const jitter = Math.random() * 1e3;
467
+ await new Promise((resolve) => setTimeout(resolve, delay + jitter));
468
+ }
469
+ }
470
+ }
471
+ };
472
+ // Annotate the CommonJS export names for ESM import in node:
473
+ 0 && (module.exports = {
474
+ LangfuseMedia,
475
+ LangfuseSpanProcessor
476
+ });
477
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/span-processor.ts","../src/hash.ts","../src/media.ts"],"sourcesContent":["export {\n LangfuseSpanProcessor,\n type LangfuseSpanProcessorParams,\n} from \"./span-processor.js\";\n\nexport { LangfuseMedia } 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"]}