@langfuse/otel 4.0.0-beta.4 → 4.0.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +75 -281
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -153
- package/dist/index.d.ts +17 -153
- package/dist/index.mjs +50 -261
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -20,219 +20,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
LangfuseMedia: () => LangfuseMedia,
|
|
24
23
|
LangfuseSpanProcessor: () => LangfuseSpanProcessor
|
|
25
24
|
});
|
|
26
25
|
module.exports = __toCommonJS(index_exports);
|
|
27
26
|
|
|
28
27
|
// src/span-processor.ts
|
|
29
|
-
var
|
|
30
|
-
var
|
|
28
|
+
var import_core = require("@langfuse/core");
|
|
29
|
+
var import_core2 = require("@opentelemetry/core");
|
|
31
30
|
var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
|
|
32
31
|
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
|
-
/**
|
|
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
|
-
*/
|
|
88
|
-
constructor(params) {
|
|
89
|
-
const { source } = params;
|
|
90
|
-
this._source = source;
|
|
91
|
-
if (source === "base64_data_uri") {
|
|
92
|
-
const [contentBytesParsed, contentTypeParsed] = this.parseBase64DataUri(
|
|
93
|
-
params.base64DataUri
|
|
94
|
-
);
|
|
95
|
-
this._contentBytes = contentBytesParsed;
|
|
96
|
-
this._contentType = contentTypeParsed;
|
|
97
|
-
} else {
|
|
98
|
-
this._contentBytes = params.contentBytes;
|
|
99
|
-
this._contentType = params.contentType;
|
|
100
|
-
}
|
|
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
|
-
*/
|
|
109
|
-
parseBase64DataUri(data) {
|
|
110
|
-
try {
|
|
111
|
-
if (!data || typeof data !== "string") {
|
|
112
|
-
throw new Error("Data URI is not a string");
|
|
113
|
-
}
|
|
114
|
-
if (!data.startsWith("data:")) {
|
|
115
|
-
throw new Error("Data URI does not start with 'data:'");
|
|
116
|
-
}
|
|
117
|
-
const [header, actualData] = data.slice(5).split(",", 2);
|
|
118
|
-
if (!header || !actualData) {
|
|
119
|
-
throw new Error("Invalid URI");
|
|
120
|
-
}
|
|
121
|
-
const headerParts = header.split(";");
|
|
122
|
-
if (!headerParts.includes("base64")) {
|
|
123
|
-
throw new Error("Data is not base64 encoded");
|
|
124
|
-
}
|
|
125
|
-
const contentType = headerParts[0];
|
|
126
|
-
if (!contentType) {
|
|
127
|
-
throw new Error("Content type is empty");
|
|
128
|
-
}
|
|
129
|
-
return [
|
|
130
|
-
Buffer.from(actualData, "base64"),
|
|
131
|
-
contentType
|
|
132
|
-
];
|
|
133
|
-
} catch (error) {
|
|
134
|
-
(0, import_core2.getGlobalLogger)().error("Error parsing base64 data URI", error);
|
|
135
|
-
return [void 0, void 0];
|
|
136
|
-
}
|
|
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
|
-
*/
|
|
152
|
-
get id() {
|
|
153
|
-
if (!this.contentSha256Hash) return null;
|
|
154
|
-
const urlSafeContentHash = this.contentSha256Hash.replaceAll("+", "-").replaceAll("/", "_");
|
|
155
|
-
return urlSafeContentHash.slice(0, 22);
|
|
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
|
-
*/
|
|
162
|
-
get contentLength() {
|
|
163
|
-
var _a2;
|
|
164
|
-
return (_a2 = this._contentBytes) == null ? void 0 : _a2.length;
|
|
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
|
-
*/
|
|
174
|
-
get contentSha256Hash() {
|
|
175
|
-
if (!this._contentBytes || !isCryptoAvailable) {
|
|
176
|
-
return void 0;
|
|
177
|
-
}
|
|
178
|
-
try {
|
|
179
|
-
return getSha256HashFromBytes(this._contentBytes);
|
|
180
|
-
} catch (error) {
|
|
181
|
-
(0, import_core2.getGlobalLogger)().warn(
|
|
182
|
-
"[Langfuse] Failed to generate SHA-256 hash for media content:",
|
|
183
|
-
error
|
|
184
|
-
);
|
|
185
|
-
return void 0;
|
|
186
|
-
}
|
|
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
|
-
*/
|
|
204
|
-
get tag() {
|
|
205
|
-
if (!this._contentType || !this._source || !this.id) return null;
|
|
206
|
-
return `@@@langfuseMedia:type=${this._contentType}|id=${this.id}|source=${this._source}@@@`;
|
|
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
|
-
*/
|
|
220
|
-
get base64DataUri() {
|
|
221
|
-
if (!this._contentBytes) return null;
|
|
222
|
-
return `data:${this._contentType};base64,${Buffer.from(this._contentBytes).toString("base64")}`;
|
|
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
|
-
*/
|
|
229
|
-
toJSON() {
|
|
230
|
-
return this.base64DataUri;
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
// src/span-processor.ts
|
|
235
|
-
var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcessor {
|
|
32
|
+
var LangfuseSpanProcessor = class {
|
|
236
33
|
/**
|
|
237
34
|
* Creates a new LangfuseSpanProcessor instance.
|
|
238
35
|
*
|
|
@@ -260,11 +57,13 @@ var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcess
|
|
|
260
57
|
* ```
|
|
261
58
|
*/
|
|
262
59
|
constructor(params) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
const
|
|
60
|
+
this.pendingMediaUploads = /* @__PURE__ */ new Set();
|
|
61
|
+
this.pendingEndedSpans = /* @__PURE__ */ new Set();
|
|
62
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
63
|
+
const logger = (0, import_core.getGlobalLogger)();
|
|
64
|
+
const publicKey = (_a = params == null ? void 0 : params.publicKey) != null ? _a : (0, import_core.getEnv)("LANGFUSE_PUBLIC_KEY");
|
|
65
|
+
const secretKey = (_b = params == null ? void 0 : params.secretKey) != null ? _b : (0, import_core.getEnv)("LANGFUSE_SECRET_KEY");
|
|
66
|
+
const baseUrl = (_e = (_d = (_c = params == null ? void 0 : params.baseUrl) != null ? _c : (0, import_core.getEnv)("LANGFUSE_BASE_URL")) != null ? _d : (0, import_core.getEnv)("LANGFUSE_BASEURL")) != null ? _e : (
|
|
268
67
|
// legacy v2
|
|
269
68
|
"https://cloud.langfuse.com"
|
|
270
69
|
);
|
|
@@ -278,40 +77,37 @@ var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcess
|
|
|
278
77
|
"No exporter configured and no secret key provided in constructor or as LANGFUSE_SECRET_KEY env var. Span exports will fail."
|
|
279
78
|
);
|
|
280
79
|
}
|
|
281
|
-
const flushAt = (_f = params == null ? void 0 : params.flushAt) != null ? _f : (0,
|
|
282
|
-
const flushIntervalSeconds = (_g = params == null ? void 0 : params.flushInterval) != null ? _g : (0,
|
|
283
|
-
const authHeaderValue = (0,
|
|
284
|
-
|
|
285
|
-
);
|
|
286
|
-
const timeoutSeconds = (_i = params == null ? void 0 : params.timeout) != null ? _i : Number((_h = (0, import_core3.getEnv)("LANGFUSE_TIMEOUT")) != null ? _h : 5);
|
|
80
|
+
const flushAt = (_f = params == null ? void 0 : params.flushAt) != null ? _f : (0, import_core.getEnv)("LANGFUSE_FLUSH_AT");
|
|
81
|
+
const flushIntervalSeconds = (_g = params == null ? void 0 : params.flushInterval) != null ? _g : (0, import_core.getEnv)("LANGFUSE_FLUSH_INTERVAL");
|
|
82
|
+
const authHeaderValue = (0, import_core.base64Encode)(`${publicKey}:${secretKey}`);
|
|
83
|
+
const timeoutSeconds = (_i = params == null ? void 0 : params.timeout) != null ? _i : Number((_h = (0, import_core.getEnv)("LANGFUSE_TIMEOUT")) != null ? _h : 5);
|
|
287
84
|
const exporter = (_j = params == null ? void 0 : params.exporter) != null ? _j : new import_exporter_trace_otlp_http.OTLPTraceExporter({
|
|
288
85
|
url: `${baseUrl}/api/public/otel/v1/traces`,
|
|
289
86
|
headers: {
|
|
290
87
|
Authorization: `Basic ${authHeaderValue}`,
|
|
291
88
|
x_langfuse_sdk_name: "javascript",
|
|
292
|
-
x_langfuse_sdk_version:
|
|
89
|
+
x_langfuse_sdk_version: import_core.LANGFUSE_SDK_VERSION,
|
|
293
90
|
x_langfuse_public_key: publicKey != null ? publicKey : "<missing>",
|
|
294
91
|
...params == null ? void 0 : params.additionalHeaders
|
|
295
92
|
},
|
|
296
93
|
timeoutMillis: timeoutSeconds * 1e3
|
|
297
94
|
});
|
|
298
|
-
|
|
95
|
+
this.processor = (params == null ? void 0 : params.exportMode) === "immediate" ? new import_sdk_trace_base.SimpleSpanProcessor(exporter) : new import_sdk_trace_base.BatchSpanProcessor(exporter, {
|
|
299
96
|
maxExportBatchSize: flushAt ? Number(flushAt) : void 0,
|
|
300
97
|
scheduledDelayMillis: flushIntervalSeconds ? Number(flushIntervalSeconds) * 1e3 : void 0
|
|
301
98
|
});
|
|
302
|
-
this.pendingMediaUploads = {};
|
|
303
99
|
this.publicKey = publicKey;
|
|
304
100
|
this.baseUrl = baseUrl;
|
|
305
|
-
this.environment = (_k = params == null ? void 0 : params.environment) != null ? _k : (0,
|
|
306
|
-
this.release = (_l = params == null ? void 0 : params.release) != null ? _l : (0,
|
|
101
|
+
this.environment = (_k = params == null ? void 0 : params.environment) != null ? _k : (0, import_core.getEnv)("LANGFUSE_TRACING_ENVIRONMENT");
|
|
102
|
+
this.release = (_l = params == null ? void 0 : params.release) != null ? _l : (0, import_core.getEnv)("LANGFUSE_RELEASE");
|
|
307
103
|
this.mask = params == null ? void 0 : params.mask;
|
|
308
104
|
this.shouldExportSpan = params == null ? void 0 : params.shouldExportSpan;
|
|
309
|
-
this.apiClient = new
|
|
105
|
+
this.apiClient = new import_core.LangfuseAPIClient({
|
|
310
106
|
baseUrl: this.baseUrl,
|
|
311
107
|
username: this.publicKey,
|
|
312
108
|
password: secretKey,
|
|
313
109
|
xLangfusePublicKey: this.publicKey,
|
|
314
|
-
xLangfuseSdkVersion:
|
|
110
|
+
xLangfuseSdkVersion: import_core.LANGFUSE_SDK_VERSION,
|
|
315
111
|
xLangfuseSdkName: "javascript",
|
|
316
112
|
environment: "",
|
|
317
113
|
// noop as baseUrl is set
|
|
@@ -326,14 +122,9 @@ var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcess
|
|
|
326
122
|
flushAt,
|
|
327
123
|
flushIntervalSeconds
|
|
328
124
|
});
|
|
329
|
-
if (!isCryptoAvailable) {
|
|
330
|
-
logger.warn(
|
|
331
|
-
"[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."
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
125
|
}
|
|
335
126
|
get logger() {
|
|
336
|
-
return (0,
|
|
127
|
+
return (0, import_core.getGlobalLogger)();
|
|
337
128
|
}
|
|
338
129
|
/**
|
|
339
130
|
* Called when a span is started. Adds environment and release attributes to the span.
|
|
@@ -345,10 +136,10 @@ var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcess
|
|
|
345
136
|
*/
|
|
346
137
|
onStart(span, parentContext) {
|
|
347
138
|
span.setAttributes({
|
|
348
|
-
[
|
|
349
|
-
[
|
|
139
|
+
[import_core.LangfuseOtelSpanAttributes.ENVIRONMENT]: this.environment,
|
|
140
|
+
[import_core.LangfuseOtelSpanAttributes.RELEASE]: this.release
|
|
350
141
|
});
|
|
351
|
-
return
|
|
142
|
+
return this.processor.onStart(span, parentContext);
|
|
352
143
|
}
|
|
353
144
|
/**
|
|
354
145
|
* Called when a span ends. Processes the span for export to Langfuse.
|
|
@@ -365,7 +156,16 @@ var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcess
|
|
|
365
156
|
* @override
|
|
366
157
|
*/
|
|
367
158
|
onEnd(span) {
|
|
368
|
-
|
|
159
|
+
const processEndedSpanPromise = this.processEndedSpan(span).catch((err) => {
|
|
160
|
+
this.logger.error(err);
|
|
161
|
+
});
|
|
162
|
+
this.pendingEndedSpans.add(processEndedSpanPromise);
|
|
163
|
+
void processEndedSpanPromise.finally(
|
|
164
|
+
() => this.pendingEndedSpans.delete(processEndedSpanPromise)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
async processEndedSpan(span) {
|
|
168
|
+
var _a, _b;
|
|
369
169
|
if (this.shouldExportSpan) {
|
|
370
170
|
try {
|
|
371
171
|
if (this.shouldExportSpan({ otelSpan: span }) === false) return;
|
|
@@ -378,7 +178,7 @@ var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcess
|
|
|
378
178
|
}
|
|
379
179
|
}
|
|
380
180
|
this.applyMaskInPlace(span);
|
|
381
|
-
this.handleMediaInPlace(span);
|
|
181
|
+
await this.handleMediaInPlace(span);
|
|
382
182
|
this.logger.debug(
|
|
383
183
|
`Processed span:
|
|
384
184
|
${JSON.stringify(
|
|
@@ -386,11 +186,11 @@ ${JSON.stringify(
|
|
|
386
186
|
name: span.name,
|
|
387
187
|
traceId: span.spanContext().traceId,
|
|
388
188
|
spanId: span.spanContext().spanId,
|
|
389
|
-
parentSpanId: (_b = (
|
|
189
|
+
parentSpanId: (_b = (_a = span.parentSpanContext) == null ? void 0 : _a.spanId) != null ? _b : null,
|
|
390
190
|
attributes: span.attributes,
|
|
391
|
-
startTime: new Date((0,
|
|
392
|
-
endTime: new Date((0,
|
|
393
|
-
durationMs: (0,
|
|
191
|
+
startTime: new Date((0, import_core2.hrTimeToMilliseconds)(span.startTime)),
|
|
192
|
+
endTime: new Date((0, import_core2.hrTimeToMilliseconds)(span.endTime)),
|
|
193
|
+
durationMs: (0, import_core2.hrTimeToMilliseconds)(span.duration),
|
|
394
194
|
kind: span.kind,
|
|
395
195
|
status: span.status,
|
|
396
196
|
resource: span.resource.attributes,
|
|
@@ -400,14 +200,11 @@ ${JSON.stringify(
|
|
|
400
200
|
2
|
|
401
201
|
)}`
|
|
402
202
|
);
|
|
403
|
-
|
|
203
|
+
this.processor.onEnd(span);
|
|
404
204
|
}
|
|
405
205
|
async flush() {
|
|
406
|
-
await Promise.all(
|
|
407
|
-
|
|
408
|
-
e instanceof Error ? e.message : "Unhandled media upload error"
|
|
409
|
-
);
|
|
410
|
-
});
|
|
206
|
+
await Promise.all(Array.from(this.pendingEndedSpans));
|
|
207
|
+
await Promise.all(Array.from(this.pendingMediaUploads));
|
|
411
208
|
}
|
|
412
209
|
/**
|
|
413
210
|
* Forces an immediate flush of all pending spans and media uploads.
|
|
@@ -418,7 +215,7 @@ ${JSON.stringify(
|
|
|
418
215
|
*/
|
|
419
216
|
async forceFlush() {
|
|
420
217
|
await this.flush();
|
|
421
|
-
return
|
|
218
|
+
return this.processor.forceFlush();
|
|
422
219
|
}
|
|
423
220
|
/**
|
|
424
221
|
* Gracefully shuts down the processor, ensuring all pending operations are completed.
|
|
@@ -429,23 +226,17 @@ ${JSON.stringify(
|
|
|
429
226
|
*/
|
|
430
227
|
async shutdown() {
|
|
431
228
|
await this.flush();
|
|
432
|
-
return
|
|
229
|
+
return this.processor.shutdown();
|
|
433
230
|
}
|
|
434
|
-
handleMediaInPlace(span) {
|
|
435
|
-
var
|
|
436
|
-
if (!isCryptoAvailable) {
|
|
437
|
-
this.logger.debug(
|
|
438
|
-
"[Langfuse] Crypto not available, skipping media processing"
|
|
439
|
-
);
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
231
|
+
async handleMediaInPlace(span) {
|
|
232
|
+
var _a;
|
|
442
233
|
const mediaAttributes = [
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
234
|
+
import_core.LangfuseOtelSpanAttributes.OBSERVATION_INPUT,
|
|
235
|
+
import_core.LangfuseOtelSpanAttributes.TRACE_INPUT,
|
|
236
|
+
import_core.LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,
|
|
237
|
+
import_core.LangfuseOtelSpanAttributes.TRACE_OUTPUT,
|
|
238
|
+
import_core.LangfuseOtelSpanAttributes.OBSERVATION_METADATA,
|
|
239
|
+
import_core.LangfuseOtelSpanAttributes.TRACE_METADATA
|
|
449
240
|
];
|
|
450
241
|
for (const mediaAttribute of mediaAttributes) {
|
|
451
242
|
const mediaRelevantAttributeKeys = Object.keys(span.attributes).filter(
|
|
@@ -461,14 +252,15 @@ ${JSON.stringify(
|
|
|
461
252
|
}
|
|
462
253
|
let mediaReplacedValue = value;
|
|
463
254
|
const regex = /data:[^;]+;base64,[A-Za-z0-9+/]+=*/g;
|
|
464
|
-
const foundMedia = [...new Set((
|
|
255
|
+
const foundMedia = [...new Set((_a = value.match(regex)) != null ? _a : [])];
|
|
465
256
|
if (foundMedia.length === 0) continue;
|
|
466
257
|
for (const mediaDataUri of foundMedia) {
|
|
467
|
-
const media = new LangfuseMedia({
|
|
258
|
+
const media = new import_core.LangfuseMedia({
|
|
468
259
|
base64DataUri: mediaDataUri,
|
|
469
260
|
source: "base64_data_uri"
|
|
470
261
|
});
|
|
471
|
-
|
|
262
|
+
const langfuseMediaTag = await media.getTag();
|
|
263
|
+
if (!langfuseMediaTag) {
|
|
472
264
|
this.logger.warn(
|
|
473
265
|
"Failed to create Langfuse media tag. Skipping media item."
|
|
474
266
|
);
|
|
@@ -480,15 +272,16 @@ ${JSON.stringify(
|
|
|
480
272
|
observationId: span.spanContext().spanId,
|
|
481
273
|
field: mediaAttribute.includes("input") ? "input" : mediaAttribute.includes("output") ? "output" : "metadata"
|
|
482
274
|
// todo: make more robust
|
|
275
|
+
}).catch((err) => {
|
|
276
|
+
this.logger.error("Media upload failed with error: ", err);
|
|
483
277
|
});
|
|
484
|
-
|
|
485
|
-
this.pendingMediaUploads[promiseId] = uploadPromise;
|
|
278
|
+
this.pendingMediaUploads.add(uploadPromise);
|
|
486
279
|
uploadPromise.finally(() => {
|
|
487
|
-
|
|
280
|
+
this.pendingMediaUploads.delete(uploadPromise);
|
|
488
281
|
});
|
|
489
282
|
mediaReplacedValue = mediaReplacedValue.replaceAll(
|
|
490
283
|
mediaDataUri,
|
|
491
|
-
|
|
284
|
+
langfuseMediaTag
|
|
492
285
|
);
|
|
493
286
|
}
|
|
494
287
|
span.attributes[key] = mediaReplacedValue;
|
|
@@ -497,12 +290,12 @@ ${JSON.stringify(
|
|
|
497
290
|
}
|
|
498
291
|
applyMaskInPlace(span) {
|
|
499
292
|
const maskCandidates = [
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
293
|
+
import_core.LangfuseOtelSpanAttributes.OBSERVATION_INPUT,
|
|
294
|
+
import_core.LangfuseOtelSpanAttributes.TRACE_INPUT,
|
|
295
|
+
import_core.LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,
|
|
296
|
+
import_core.LangfuseOtelSpanAttributes.TRACE_OUTPUT,
|
|
297
|
+
import_core.LangfuseOtelSpanAttributes.OBSERVATION_METADATA,
|
|
298
|
+
import_core.LangfuseOtelSpanAttributes.TRACE_METADATA
|
|
506
299
|
];
|
|
507
300
|
for (const maskCandidate of maskCandidates) {
|
|
508
301
|
if (maskCandidate in span.attributes) {
|
|
@@ -530,7 +323,8 @@ ${JSON.stringify(
|
|
|
530
323
|
field
|
|
531
324
|
}) {
|
|
532
325
|
try {
|
|
533
|
-
|
|
326
|
+
const contentSha256Hash = await media.getSha256Hash();
|
|
327
|
+
if (!media.contentLength || !media._contentType || !contentSha256Hash || !media._contentBytes) {
|
|
534
328
|
return;
|
|
535
329
|
}
|
|
536
330
|
const { uploadUrl, mediaId } = await this.apiClient.media.getUploadUrl({
|
|
@@ -539,17 +333,18 @@ ${JSON.stringify(
|
|
|
539
333
|
observationId,
|
|
540
334
|
field,
|
|
541
335
|
contentType: media._contentType,
|
|
542
|
-
sha256Hash:
|
|
336
|
+
sha256Hash: contentSha256Hash
|
|
543
337
|
});
|
|
544
338
|
if (!uploadUrl) {
|
|
545
339
|
this.logger.debug(
|
|
546
|
-
`Media status: Media with ID ${
|
|
340
|
+
`Media status: Media with ID ${mediaId} already uploaded. Skipping duplicate upload.`
|
|
547
341
|
);
|
|
548
342
|
return;
|
|
549
343
|
}
|
|
550
|
-
|
|
344
|
+
const clientSideMediaId = await media.getId();
|
|
345
|
+
if (clientSideMediaId !== mediaId) {
|
|
551
346
|
this.logger.error(
|
|
552
|
-
`Media integrity error: Media ID mismatch between SDK (${
|
|
347
|
+
`Media integrity error: Media ID mismatch between SDK (${clientSideMediaId}) and Server (${mediaId}). Upload cancelled. Please check media ID generation logic.`
|
|
553
348
|
);
|
|
554
349
|
return;
|
|
555
350
|
}
|
|
@@ -559,7 +354,7 @@ ${JSON.stringify(
|
|
|
559
354
|
uploadUrl,
|
|
560
355
|
contentBytes: media._contentBytes,
|
|
561
356
|
contentType: media._contentType,
|
|
562
|
-
contentSha256Hash
|
|
357
|
+
contentSha256Hash,
|
|
563
358
|
maxRetries: 3,
|
|
564
359
|
baseDelay: 1e3
|
|
565
360
|
});
|
|
@@ -614,7 +409,6 @@ ${JSON.stringify(
|
|
|
614
409
|
};
|
|
615
410
|
// Annotate the CommonJS export names for ESM import in node:
|
|
616
411
|
0 && (module.exports = {
|
|
617
|
-
LangfuseMedia,
|
|
618
412
|
LangfuseSpanProcessor
|
|
619
413
|
});
|
|
620
414
|
//# sourceMappingURL=index.cjs.map
|