@langfuse/otel 4.0.0-alpha.2 → 4.0.0-beta.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/dist/index.cjs CHANGED
@@ -155,36 +155,36 @@ var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcess
155
155
  constructor(params) {
156
156
  var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
157
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 : (
158
+ const publicKey = (_a2 = params == null ? void 0 : params.publicKey) != null ? _a2 : (0, import_core3.getEnv)("LANGFUSE_PUBLIC_KEY");
159
+ const secretKey = (_b = params == null ? void 0 : params.secretKey) != null ? _b : (0, import_core3.getEnv)("LANGFUSE_SECRET_KEY");
160
+ const baseUrl = (_e = (_d = (_c = params == null ? void 0 : params.baseUrl) != null ? _c : (0, import_core3.getEnv)("LANGFUSE_BASE_URL")) != null ? _d : (0, import_core3.getEnv)("LANGFUSE_BASEURL")) != null ? _e : (
161
161
  // legacy v2
162
162
  "https://cloud.langfuse.com"
163
163
  );
164
- if (!params.exporter && !publicKey) {
164
+ if (!(params == null ? void 0 : params.exporter) && !publicKey) {
165
165
  logger.warn(
166
166
  "No exporter configured and no public key provided in constructor or as LANGFUSE_PUBLIC_KEY env var. Span exports will fail."
167
167
  );
168
168
  }
169
- if (!params.exporter && !secretKey) {
169
+ if (!(params == null ? void 0 : params.exporter) && !secretKey) {
170
170
  logger.warn(
171
171
  "No exporter configured and no secret key provided in constructor or as LANGFUSE_SECRET_KEY env var. Span exports will fail."
172
172
  );
173
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");
174
+ const flushAt = (_f = params == null ? void 0 : params.flushAt) != null ? _f : (0, import_core3.getEnv)("LANGFUSE_FLUSH_AT");
175
+ const flushIntervalSeconds = (_g = params == null ? void 0 : params.flushInterval) != null ? _g : (0, import_core3.getEnv)("LANGFUSE_FLUSH_INTERVAL");
176
176
  const authHeaderValue = Buffer.from(`${publicKey}:${secretKey}`).toString(
177
177
  "base64"
178
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({
179
+ const timeoutSeconds = (_i = params == null ? void 0 : params.timeout) != null ? _i : Number((_h = (0, import_core3.getEnv)("LANGFUSE_TIMEOUT")) != null ? _h : 5);
180
+ const exporter = (_j = params == null ? void 0 : params.exporter) != null ? _j : new import_exporter_trace_otlp_http.OTLPTraceExporter({
181
181
  url: `${baseUrl}/api/public/otel/v1/traces`,
182
182
  headers: {
183
183
  Authorization: `Basic ${authHeaderValue}`,
184
184
  x_langfuse_sdk_name: "javascript",
185
185
  x_langfuse_sdk_version: import_core3.LANGFUSE_SDK_VERSION,
186
186
  x_langfuse_public_key: publicKey != null ? publicKey : "<missing>",
187
- ...params.additionalHeaders
187
+ ...params == null ? void 0 : params.additionalHeaders
188
188
  },
189
189
  timeoutMillis: timeoutSeconds * 1e3
190
190
  });
@@ -195,10 +195,10 @@ var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcess
195
195
  this.pendingMediaUploads = {};
196
196
  this.publicKey = publicKey;
197
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;
198
+ this.environment = (_k = params == null ? void 0 : params.environment) != null ? _k : (0, import_core3.getEnv)("LANGFUSE_TRACING_ENVIRONMENT");
199
+ this.release = (_l = params == null ? void 0 : params.release) != null ? _l : (0, import_core3.getEnv)("LANGFUSE_RELEASE");
200
+ this.mask = params == null ? void 0 : params.mask;
201
+ this.shouldExportSpan = params == null ? void 0 : params.shouldExportSpan;
202
202
  this.apiClient = new import_core3.LangfuseAPIClient({
203
203
  baseUrl: this.baseUrl,
204
204
  username: this.publicKey,
@@ -208,7 +208,7 @@ var LangfuseSpanProcessor = class extends import_sdk_trace_base.BatchSpanProcess
208
208
  xLangfuseSdkName: "javascript",
209
209
  environment: "",
210
210
  // noop as baseUrl is set
211
- headers: params.additionalHeaders
211
+ headers: params == null ? void 0 : params.additionalHeaders
212
212
  });
213
213
  logger.debug("Initialized LangfuseSpanProcessor with params:", {
214
214
  publicKey,
@@ -1 +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"]}
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"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
- import { BatchSpanProcessor, SpanExporter, ReadableSpan, Span } from '@opentelemetry/sdk-trace-base';
1
+ import { ReadableSpan, SpanExporter, BatchSpanProcessor, Span } from '@opentelemetry/sdk-trace-base';
2
2
  import { MediaContentType } from '@langfuse/core';
3
+ export { MediaContentType } from '@langfuse/core';
3
4
 
4
5
  type MaskFunction = (params: {
5
6
  data: any;
@@ -30,7 +31,7 @@ declare class LangfuseSpanProcessor extends BatchSpanProcessor {
30
31
  private mask?;
31
32
  private shouldExportSpan?;
32
33
  private apiClient;
33
- constructor(params: LangfuseSpanProcessorParams);
34
+ constructor(params?: LangfuseSpanProcessorParams);
34
35
  private get logger();
35
36
  onStart(span: Span, parentContext: any): void;
36
37
  onEnd(span: ReadableSpan): void;
@@ -72,4 +73,4 @@ declare class LangfuseMedia {
72
73
  toJSON(): string | null;
73
74
  }
74
75
 
75
- export { LangfuseMedia, LangfuseSpanProcessor, type LangfuseSpanProcessorParams };
76
+ export { LangfuseMedia, type LangfuseMediaParams, LangfuseSpanProcessor, type LangfuseSpanProcessorParams, type MaskFunction, type ShouldExportSpan };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,76 @@
1
- export { LangfuseSpanProcessor, type LangfuseSpanProcessorParams, } from "./span-processor.js";
2
- export { LangfuseMedia } from "./media.js";
3
- //# sourceMappingURL=index.d.ts.map
1
+ import { ReadableSpan, SpanExporter, BatchSpanProcessor, Span } from '@opentelemetry/sdk-trace-base';
2
+ import { MediaContentType } from '@langfuse/core';
3
+ export { MediaContentType } from '@langfuse/core';
4
+
5
+ type MaskFunction = (params: {
6
+ data: any;
7
+ }) => any;
8
+ type ShouldExportSpan = (params: {
9
+ otelSpan: ReadableSpan;
10
+ }) => boolean;
11
+ interface LangfuseSpanProcessorParams {
12
+ exporter?: SpanExporter;
13
+ publicKey?: string;
14
+ secretKey?: string;
15
+ baseUrl?: string;
16
+ flushAt?: number;
17
+ flushInterval?: number;
18
+ mask?: MaskFunction;
19
+ shouldExportSpan?: ShouldExportSpan;
20
+ environment?: string;
21
+ release?: string;
22
+ timeout?: number;
23
+ additionalHeaders?: Record<string, string>;
24
+ }
25
+ declare class LangfuseSpanProcessor extends BatchSpanProcessor {
26
+ private pendingMediaUploads;
27
+ private publicKey?;
28
+ private baseUrl?;
29
+ private environment?;
30
+ private release?;
31
+ private mask?;
32
+ private shouldExportSpan?;
33
+ private apiClient;
34
+ constructor(params?: LangfuseSpanProcessorParams);
35
+ private get logger();
36
+ onStart(span: Span, parentContext: any): void;
37
+ onEnd(span: ReadableSpan): void;
38
+ private flush;
39
+ forceFlush(): Promise<void>;
40
+ shutdown(): Promise<void>;
41
+ private handleMediaInPlace;
42
+ private applyMaskInPlace;
43
+ private applyMask;
44
+ private processMediaItem;
45
+ private uploadMediaWithBackoff;
46
+ }
47
+
48
+ type LangfuseMediaParams = {
49
+ source: "base64_data_uri";
50
+ base64DataUri: string;
51
+ } | {
52
+ source: "bytes";
53
+ contentBytes: Buffer;
54
+ contentType: MediaContentType;
55
+ };
56
+ /**
57
+ * A class for wrapping media objects for upload to Langfuse.
58
+ *
59
+ * This class handles the preparation and formatting of media content for Langfuse,
60
+ * supporting both base64 data URIs and raw content bytes.
61
+ */
62
+ declare class LangfuseMedia {
63
+ _contentBytes?: Buffer;
64
+ _contentType?: MediaContentType;
65
+ _source?: string;
66
+ constructor(params: LangfuseMediaParams);
67
+ private parseBase64DataUri;
68
+ get id(): string | null;
69
+ get contentLength(): number | undefined;
70
+ get contentSha256Hash(): string | undefined;
71
+ get tag(): string | null;
72
+ get base64DataUri(): string | null;
73
+ toJSON(): string | null;
74
+ }
75
+
76
+ export { LangfuseMedia, type LangfuseMediaParams, LangfuseSpanProcessor, type LangfuseSpanProcessorParams, type MaskFunction, type ShouldExportSpan };
package/dist/index.mjs CHANGED
@@ -144,36 +144,36 @@ var LangfuseSpanProcessor = class extends BatchSpanProcessor {
144
144
  constructor(params) {
145
145
  var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
146
146
  const logger = getGlobalLogger3();
147
- const publicKey = (_a2 = params.publicKey) != null ? _a2 : getEnv("LANGFUSE_PUBLIC_KEY");
148
- const secretKey = (_b = params.secretKey) != null ? _b : getEnv("LANGFUSE_SECRET_KEY");
149
- const baseUrl = (_e = (_d = (_c = params.baseUrl) != null ? _c : getEnv("LANGFUSE_BASE_URL")) != null ? _d : getEnv("LANGFUSE_BASEURL")) != null ? _e : (
147
+ const publicKey = (_a2 = params == null ? void 0 : params.publicKey) != null ? _a2 : getEnv("LANGFUSE_PUBLIC_KEY");
148
+ const secretKey = (_b = params == null ? void 0 : params.secretKey) != null ? _b : getEnv("LANGFUSE_SECRET_KEY");
149
+ const baseUrl = (_e = (_d = (_c = params == null ? void 0 : params.baseUrl) != null ? _c : getEnv("LANGFUSE_BASE_URL")) != null ? _d : getEnv("LANGFUSE_BASEURL")) != null ? _e : (
150
150
  // legacy v2
151
151
  "https://cloud.langfuse.com"
152
152
  );
153
- if (!params.exporter && !publicKey) {
153
+ if (!(params == null ? void 0 : params.exporter) && !publicKey) {
154
154
  logger.warn(
155
155
  "No exporter configured and no public key provided in constructor or as LANGFUSE_PUBLIC_KEY env var. Span exports will fail."
156
156
  );
157
157
  }
158
- if (!params.exporter && !secretKey) {
158
+ if (!(params == null ? void 0 : params.exporter) && !secretKey) {
159
159
  logger.warn(
160
160
  "No exporter configured and no secret key provided in constructor or as LANGFUSE_SECRET_KEY env var. Span exports will fail."
161
161
  );
162
162
  }
163
- const flushAt = (_f = params.flushAt) != null ? _f : getEnv("LANGFUSE_FLUSH_AT");
164
- const flushIntervalSeconds = (_g = params.flushInterval) != null ? _g : getEnv("LANGFUSE_FLUSH_INTERVAL");
163
+ const flushAt = (_f = params == null ? void 0 : params.flushAt) != null ? _f : getEnv("LANGFUSE_FLUSH_AT");
164
+ const flushIntervalSeconds = (_g = params == null ? void 0 : params.flushInterval) != null ? _g : getEnv("LANGFUSE_FLUSH_INTERVAL");
165
165
  const authHeaderValue = Buffer.from(`${publicKey}:${secretKey}`).toString(
166
166
  "base64"
167
167
  );
168
- const timeoutSeconds = (_i = params.timeout) != null ? _i : Number((_h = getEnv("LANGFUSE_TIMEOUT")) != null ? _h : 5);
169
- const exporter = (_j = params.exporter) != null ? _j : new OTLPTraceExporter({
168
+ const timeoutSeconds = (_i = params == null ? void 0 : params.timeout) != null ? _i : Number((_h = getEnv("LANGFUSE_TIMEOUT")) != null ? _h : 5);
169
+ const exporter = (_j = params == null ? void 0 : params.exporter) != null ? _j : new OTLPTraceExporter({
170
170
  url: `${baseUrl}/api/public/otel/v1/traces`,
171
171
  headers: {
172
172
  Authorization: `Basic ${authHeaderValue}`,
173
173
  x_langfuse_sdk_name: "javascript",
174
174
  x_langfuse_sdk_version: LANGFUSE_SDK_VERSION,
175
175
  x_langfuse_public_key: publicKey != null ? publicKey : "<missing>",
176
- ...params.additionalHeaders
176
+ ...params == null ? void 0 : params.additionalHeaders
177
177
  },
178
178
  timeoutMillis: timeoutSeconds * 1e3
179
179
  });
@@ -184,10 +184,10 @@ var LangfuseSpanProcessor = class extends BatchSpanProcessor {
184
184
  this.pendingMediaUploads = {};
185
185
  this.publicKey = publicKey;
186
186
  this.baseUrl = baseUrl;
187
- this.environment = (_k = params.environment) != null ? _k : getEnv("LANGFUSE_TRACING_ENVIRONMENT");
188
- this.release = (_l = params.release) != null ? _l : getEnv("LANGFUSE_RELEASE");
189
- this.mask = params.mask;
190
- this.shouldExportSpan = params.shouldExportSpan;
187
+ this.environment = (_k = params == null ? void 0 : params.environment) != null ? _k : getEnv("LANGFUSE_TRACING_ENVIRONMENT");
188
+ this.release = (_l = params == null ? void 0 : params.release) != null ? _l : getEnv("LANGFUSE_RELEASE");
189
+ this.mask = params == null ? void 0 : params.mask;
190
+ this.shouldExportSpan = params == null ? void 0 : params.shouldExportSpan;
191
191
  this.apiClient = new LangfuseAPIClient({
192
192
  baseUrl: this.baseUrl,
193
193
  username: this.publicKey,
@@ -197,7 +197,7 @@ var LangfuseSpanProcessor = class extends BatchSpanProcessor {
197
197
  xLangfuseSdkName: "javascript",
198
198
  environment: "",
199
199
  // noop as baseUrl is set
200
- headers: params.additionalHeaders
200
+ headers: params == null ? void 0 : params.additionalHeaders
201
201
  });
202
202
  logger.debug("Initialized LangfuseSpanProcessor with params:", {
203
203
  publicKey,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/span-processor.ts","../src/hash.ts","../src/media.ts"],"sourcesContent":["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":["getGlobalLogger","getGlobalLogger","getGlobalLogger","_a","_a","getGlobalLogger"]}
1
+ {"version":3,"sources":["../src/span-processor.ts","../src/hash.ts","../src/media.ts"],"sourcesContent":["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":["getGlobalLogger","getGlobalLogger","getGlobalLogger","_a","_a","getGlobalLogger"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langfuse/otel",
3
- "version": "4.0.0-alpha.2",
3
+ "version": "4.0.0-beta.0",
4
4
  "author": "Langfuse",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -23,7 +23,7 @@
23
23
  "dist"
24
24
  ],
25
25
  "dependencies": {
26
- "@langfuse/core": "^4.0.0-alpha.2"
26
+ "@langfuse/core": "^4.0.0-beta.0"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "@opentelemetry/api": "^1.9.0",
package/dist/hash.d.ts DELETED
@@ -1,4 +0,0 @@
1
- declare let isCryptoAvailable: boolean;
2
- export { isCryptoAvailable };
3
- export declare function getSha256HashFromBytes(data: Uint8Array): string;
4
- //# sourceMappingURL=hash.d.ts.map
@@ -1 +0,0 @@
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 DELETED
@@ -1,42 +0,0 @@
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
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,KAAK,2BAA2B,GACjC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js DELETED
@@ -1,2 +0,0 @@
1
- export { LangfuseSpanProcessor, } from "./span-processor.js";
2
- export { LangfuseMedia } from "./media.js";
package/dist/media.d.ts DELETED
@@ -1,30 +0,0 @@
1
- import { type MediaContentType } from "@langfuse/core";
2
- export type LangfuseMediaParams = {
3
- source: "base64_data_uri";
4
- base64DataUri: string;
5
- } | {
6
- source: "bytes";
7
- contentBytes: Buffer;
8
- contentType: MediaContentType;
9
- };
10
- /**
11
- * A class for wrapping media objects for upload to Langfuse.
12
- *
13
- * This class handles the preparation and formatting of media content for Langfuse,
14
- * supporting both base64 data URIs and raw content bytes.
15
- */
16
- declare class LangfuseMedia {
17
- _contentBytes?: Buffer;
18
- _contentType?: MediaContentType;
19
- _source?: string;
20
- constructor(params: LangfuseMediaParams);
21
- private parseBase64DataUri;
22
- get id(): string | null;
23
- get contentLength(): number | undefined;
24
- get contentSha256Hash(): string | undefined;
25
- get tag(): string | null;
26
- get base64DataUri(): string | null;
27
- toJSON(): string | null;
28
- }
29
- export { LangfuseMedia, type MediaContentType };
30
- //# sourceMappingURL=media.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAIxE,MAAM,MAAM,mBAAmB,GAC3B;IAAE,MAAM,EAAE,iBAAiB,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GACpD;IACE,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,gBAAgB,CAAC;CAC/B,CAAC;AAEN;;;;;GAKG;AACH,cAAM,aAAa;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;gBAEL,MAAM,EAAE,mBAAmB;IAiBvC,OAAO,CAAC,kBAAkB;IAqC1B,IAAI,EAAE,IAAI,MAAM,GAAG,IAAI,CAQtB;IAED,IAAI,aAAa,IAAI,MAAM,GAAG,SAAS,CAEtC;IAED,IAAI,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAe1C;IAED,IAAI,GAAG,IAAI,MAAM,GAAG,IAAI,CAIvB;IAED,IAAI,aAAa,IAAI,MAAM,GAAG,IAAI,CAIjC;IAED,MAAM,IAAI,MAAM,GAAG,IAAI;CAGxB;AAED,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,CAAC"}
package/dist/media.js DELETED
@@ -1,91 +0,0 @@
1
- import { getGlobalLogger } from "@langfuse/core";
2
- import { getSha256HashFromBytes, isCryptoAvailable } from "./hash.js";
3
- /**
4
- * A class for wrapping media objects for upload to Langfuse.
5
- *
6
- * This class handles the preparation and formatting of media content for Langfuse,
7
- * supporting both base64 data URIs and raw content bytes.
8
- */
9
- class LangfuseMedia {
10
- constructor(params) {
11
- const { source } = params;
12
- this._source = source;
13
- if (source === "base64_data_uri") {
14
- const [contentBytesParsed, contentTypeParsed] = this.parseBase64DataUri(params.base64DataUri);
15
- this._contentBytes = contentBytesParsed;
16
- this._contentType = contentTypeParsed;
17
- }
18
- else {
19
- this._contentBytes = params.contentBytes;
20
- this._contentType = params.contentType;
21
- }
22
- }
23
- parseBase64DataUri(data) {
24
- try {
25
- if (!data || typeof data !== "string") {
26
- throw new Error("Data URI is not a string");
27
- }
28
- if (!data.startsWith("data:")) {
29
- throw new Error("Data URI does not start with 'data:'");
30
- }
31
- const [header, actualData] = data.slice(5).split(",", 2);
32
- if (!header || !actualData) {
33
- throw new Error("Invalid URI");
34
- }
35
- const headerParts = header.split(";");
36
- if (!headerParts.includes("base64")) {
37
- throw new Error("Data is not base64 encoded");
38
- }
39
- const contentType = headerParts[0];
40
- if (!contentType) {
41
- throw new Error("Content type is empty");
42
- }
43
- return [
44
- Buffer.from(actualData, "base64"),
45
- contentType,
46
- ];
47
- }
48
- catch (error) {
49
- getGlobalLogger().error("Error parsing base64 data URI", error);
50
- return [undefined, undefined];
51
- }
52
- }
53
- get id() {
54
- if (!this.contentSha256Hash)
55
- return null;
56
- const urlSafeContentHash = this.contentSha256Hash
57
- .replaceAll("+", "-")
58
- .replaceAll("/", "_");
59
- return urlSafeContentHash.slice(0, 22);
60
- }
61
- get contentLength() {
62
- var _a;
63
- return (_a = this._contentBytes) === null || _a === void 0 ? void 0 : _a.length;
64
- }
65
- get contentSha256Hash() {
66
- if (!this._contentBytes || !isCryptoAvailable) {
67
- return undefined;
68
- }
69
- try {
70
- return getSha256HashFromBytes(this._contentBytes);
71
- }
72
- catch (error) {
73
- getGlobalLogger().warn("[Langfuse] Failed to generate SHA-256 hash for media content:", error);
74
- return undefined;
75
- }
76
- }
77
- get tag() {
78
- if (!this._contentType || !this._source || !this.id)
79
- return null;
80
- return `@@@langfuseMedia:type=${this._contentType}|id=${this.id}|source=${this._source}@@@`;
81
- }
82
- get base64DataUri() {
83
- if (!this._contentBytes)
84
- return null;
85
- return `data:${this._contentType};base64,${Buffer.from(this._contentBytes).toString("base64")}`;
86
- }
87
- toJSON() {
88
- return this.base64DataUri;
89
- }
90
- }
91
- export { LangfuseMedia };
@@ -1,44 +0,0 @@
1
- import { Span, BatchSpanProcessor, SpanExporter, ReadableSpan } from "@opentelemetry/sdk-trace-base";
2
- export type MaskFunction = (params: {
3
- data: any;
4
- }) => any;
5
- export type ShouldExportSpan = (params: {
6
- otelSpan: ReadableSpan;
7
- }) => boolean;
8
- export interface LangfuseSpanProcessorParams {
9
- exporter?: SpanExporter;
10
- publicKey?: string;
11
- secretKey?: string;
12
- baseUrl?: string;
13
- flushAt?: number;
14
- flushInterval?: number;
15
- mask?: MaskFunction;
16
- shouldExportSpan?: ShouldExportSpan;
17
- environment?: string;
18
- release?: string;
19
- timeout?: number;
20
- additionalHeaders?: Record<string, string>;
21
- }
22
- export declare class LangfuseSpanProcessor extends BatchSpanProcessor {
23
- private pendingMediaUploads;
24
- private publicKey?;
25
- private baseUrl?;
26
- private environment?;
27
- private release?;
28
- private mask?;
29
- private shouldExportSpan?;
30
- private apiClient;
31
- constructor(params: LangfuseSpanProcessorParams);
32
- private get logger();
33
- onStart(span: Span, parentContext: any): void;
34
- onEnd(span: ReadableSpan): void;
35
- private flush;
36
- forceFlush(): Promise<void>;
37
- shutdown(): Promise<void>;
38
- private handleMediaInPlace;
39
- private applyMaskInPlace;
40
- private applyMask;
41
- private processMediaItem;
42
- private uploadMediaWithBackoff;
43
- }
44
- //# sourceMappingURL=span-processor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"span-processor.d.ts","sourceRoot":"","sources":["../src/span-processor.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,IAAI,EACJ,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACb,MAAM,+BAA+B,CAAC;AAKvC,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,GAAG,CAAA;CAAE,KAAK,GAAG,CAAC;AAC1D,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,YAAY,CAAA;CAAE,KAAK,OAAO,CAAC;AAE/E,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5C;AAED,qBAAa,qBAAsB,SAAQ,kBAAkB;IAC3D,OAAO,CAAC,mBAAmB,CAAoC;IAE/D,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,IAAI,CAAC,CAAe;IAC5B,OAAO,CAAC,gBAAgB,CAAC,CAAmB;IAC5C,OAAO,CAAC,SAAS,CAAoB;gBAEzB,MAAM,EAAE,2BAA2B;IAyF/C,OAAO,KAAK,MAAM,GAEjB;IAEM,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,GAAG,IAAI;IAS7C,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;YAyCxB,KAAK;IAQN,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtC,OAAO,CAAC,kBAAkB;IAsF1B,OAAO,CAAC,gBAAgB;IAmBxB,OAAO,CAAC,SAAS;YAcH,gBAAgB;YA4EhB,sBAAsB;CAkDrC"}
@@ -1,293 +0,0 @@
1
- import { getGlobalLogger, generateUUID, LangfuseAPIClient, LANGFUSE_SDK_VERSION, LangfuseOtelSpanAttributes, getEnv, } from "@langfuse/core";
2
- import { hrTimeToMilliseconds } from "@opentelemetry/core";
3
- import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
4
- import { BatchSpanProcessor, } from "@opentelemetry/sdk-trace-base";
5
- import { isCryptoAvailable } from "./hash.js";
6
- import { LangfuseMedia } from "./media.js";
7
- export class LangfuseSpanProcessor extends BatchSpanProcessor {
8
- constructor(params) {
9
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
10
- const logger = getGlobalLogger();
11
- const publicKey = (_a = params.publicKey) !== null && _a !== void 0 ? _a : getEnv("LANGFUSE_PUBLIC_KEY");
12
- const secretKey = (_b = params.secretKey) !== null && _b !== void 0 ? _b : getEnv("LANGFUSE_SECRET_KEY");
13
- const baseUrl = (_e = (_d = (_c = params.baseUrl) !== null && _c !== void 0 ? _c : getEnv("LANGFUSE_BASE_URL")) !== null && _d !== void 0 ? _d : getEnv("LANGFUSE_BASEURL")) !== null && _e !== void 0 ? _e : "https://cloud.langfuse.com";
14
- if (!params.exporter && !publicKey) {
15
- logger.warn("No exporter configured and no public key provided in constructor or as LANGFUSE_PUBLIC_KEY env var. Span exports will fail.");
16
- }
17
- if (!params.exporter && !secretKey) {
18
- logger.warn("No exporter configured and no secret key provided in constructor or as LANGFUSE_SECRET_KEY env var. Span exports will fail.");
19
- }
20
- const flushAt = (_f = params.flushAt) !== null && _f !== void 0 ? _f : getEnv("LANGFUSE_FLUSH_AT");
21
- const flushIntervalSeconds = (_g = params.flushInterval) !== null && _g !== void 0 ? _g : getEnv("LANGFUSE_FLUSH_INTERVAL");
22
- const authHeaderValue = Buffer.from(`${publicKey}:${secretKey}`).toString("base64");
23
- const timeoutSeconds = (_h = params.timeout) !== null && _h !== void 0 ? _h : Number((_j = getEnv("LANGFUSE_TIMEOUT")) !== null && _j !== void 0 ? _j : 5);
24
- const exporter = (_k = params.exporter) !== null && _k !== void 0 ? _k : new OTLPTraceExporter({
25
- url: `${baseUrl}/api/public/otel/v1/traces`,
26
- headers: {
27
- Authorization: `Basic ${authHeaderValue}`,
28
- x_langfuse_sdk_name: "javascript",
29
- x_langfuse_sdk_version: LANGFUSE_SDK_VERSION,
30
- x_langfuse_public_key: publicKey !== null && publicKey !== void 0 ? publicKey : "<missing>",
31
- ...params.additionalHeaders,
32
- },
33
- timeoutMillis: timeoutSeconds * 1000,
34
- });
35
- super(exporter, {
36
- maxExportBatchSize: flushAt ? Number(flushAt) : undefined,
37
- scheduledDelayMillis: flushIntervalSeconds
38
- ? Number(flushIntervalSeconds) * 1000
39
- : undefined,
40
- });
41
- this.pendingMediaUploads = {};
42
- this.publicKey = publicKey;
43
- this.baseUrl = baseUrl;
44
- this.environment =
45
- (_l = params.environment) !== null && _l !== void 0 ? _l : getEnv("LANGFUSE_TRACING_ENVIRONMENT");
46
- this.release = (_m = params.release) !== null && _m !== void 0 ? _m : getEnv("LANGFUSE_RELEASE");
47
- this.mask = params.mask;
48
- this.shouldExportSpan = params.shouldExportSpan;
49
- this.apiClient = new LangfuseAPIClient({
50
- baseUrl: this.baseUrl,
51
- username: this.publicKey,
52
- password: secretKey,
53
- xLangfusePublicKey: this.publicKey,
54
- xLangfuseSdkVersion: LANGFUSE_SDK_VERSION,
55
- xLangfuseSdkName: "javascript",
56
- environment: "", // noop as baseUrl is set
57
- headers: params.additionalHeaders,
58
- });
59
- logger.debug("Initialized LangfuseSpanProcessor with params:", {
60
- publicKey,
61
- baseUrl,
62
- environment: this.environment,
63
- release: this.release,
64
- timeoutSeconds,
65
- flushAt,
66
- flushIntervalSeconds,
67
- });
68
- // Warn if crypto is not available
69
- if (!isCryptoAvailable) {
70
- logger.warn("[Langfuse] Crypto module not available in this runtime. Media upload functionality will be disabled. " +
71
- "Spans will still be processed normally, but any media content in base64 data URIs will not be uploaded to Langfuse.");
72
- }
73
- }
74
- get logger() {
75
- return getGlobalLogger();
76
- }
77
- onStart(span, parentContext) {
78
- span.setAttributes({
79
- [LangfuseOtelSpanAttributes.ENVIRONMENT]: this.environment,
80
- [LangfuseOtelSpanAttributes.RELEASE]: this.release,
81
- });
82
- return super.onStart(span, parentContext);
83
- }
84
- onEnd(span) {
85
- var _a, _b;
86
- if (this.shouldExportSpan) {
87
- try {
88
- if (this.shouldExportSpan({ otelSpan: span }) === false)
89
- return;
90
- }
91
- catch (err) {
92
- this.logger.error("ShouldExportSpan failed with error. Excluding span. Error: ", err);
93
- return;
94
- }
95
- }
96
- this.applyMaskInPlace(span);
97
- this.handleMediaInPlace(span);
98
- this.logger.debug(`Processed span:\n${JSON.stringify({
99
- name: span.name,
100
- traceId: span.spanContext().traceId,
101
- spanId: span.spanContext().spanId,
102
- parentSpanId: (_b = (_a = span.parentSpanContext) === null || _a === void 0 ? void 0 : _a.spanId) !== null && _b !== void 0 ? _b : null,
103
- attributes: span.attributes,
104
- startTime: new Date(hrTimeToMilliseconds(span.startTime)),
105
- endTime: new Date(hrTimeToMilliseconds(span.endTime)),
106
- durationMs: hrTimeToMilliseconds(span.duration),
107
- kind: span.kind,
108
- status: span.status,
109
- resource: span.resource.attributes,
110
- instrumentationScope: span.instrumentationScope,
111
- }, null, 2)}`);
112
- super.onEnd(span);
113
- }
114
- async flush() {
115
- await Promise.all(Object.values(this.pendingMediaUploads)).catch((e) => {
116
- this.logger.error(e instanceof Error ? e.message : "Unhandled media upload error");
117
- });
118
- }
119
- async forceFlush() {
120
- await this.flush();
121
- return super.forceFlush();
122
- }
123
- async shutdown() {
124
- await this.flush();
125
- return super.shutdown();
126
- }
127
- handleMediaInPlace(span) {
128
- var _a;
129
- // Skip media handling if crypto is not available
130
- if (!isCryptoAvailable) {
131
- this.logger.debug("[Langfuse] Crypto not available, skipping media processing");
132
- return;
133
- }
134
- const mediaAttributes = [
135
- LangfuseOtelSpanAttributes.OBSERVATION_INPUT,
136
- LangfuseOtelSpanAttributes.TRACE_INPUT,
137
- LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,
138
- LangfuseOtelSpanAttributes.TRACE_OUTPUT,
139
- LangfuseOtelSpanAttributes.OBSERVATION_METADATA,
140
- LangfuseOtelSpanAttributes.TRACE_METADATA,
141
- ];
142
- for (const mediaAttribute of mediaAttributes) {
143
- const mediaRelevantAttributeKeys = Object.keys(span.attributes).filter((attributeName) => attributeName.startsWith(mediaAttribute));
144
- for (const key of mediaRelevantAttributeKeys) {
145
- const value = span.attributes[key];
146
- if (typeof value !== "string") {
147
- this.logger.warn(`Span attribute ${mediaAttribute} is not a stringified object. Skipping media handling.`);
148
- continue;
149
- }
150
- // Find media base64 data URI
151
- let mediaReplacedValue = value;
152
- const regex = /data:[^;]+;base64,[A-Za-z0-9+/]+=*/g;
153
- const foundMedia = [...new Set((_a = value.match(regex)) !== null && _a !== void 0 ? _a : [])];
154
- if (foundMedia.length === 0)
155
- continue;
156
- for (const mediaDataUri of foundMedia) {
157
- // For each media, create media tag and initiate upload
158
- const media = new LangfuseMedia({
159
- base64DataUri: mediaDataUri,
160
- source: "base64_data_uri",
161
- });
162
- if (!media.tag) {
163
- this.logger.warn("Failed to create Langfuse media tag. Skipping media item.");
164
- continue;
165
- }
166
- const uploadPromise = this.processMediaItem({
167
- media,
168
- traceId: span.spanContext().traceId,
169
- observationId: span.spanContext().spanId,
170
- field: mediaAttribute.includes("input")
171
- ? "input"
172
- : mediaAttribute.includes("output")
173
- ? "output"
174
- : "metadata", // todo: make more robust
175
- });
176
- const promiseId = generateUUID();
177
- this.pendingMediaUploads[promiseId] = uploadPromise;
178
- uploadPromise.finally(() => {
179
- delete this.pendingMediaUploads[promiseId];
180
- });
181
- // Replace original attribute with media escaped attribute
182
- mediaReplacedValue = mediaReplacedValue.replaceAll(mediaDataUri, media.tag);
183
- }
184
- span.attributes[key] = mediaReplacedValue;
185
- }
186
- }
187
- }
188
- applyMaskInPlace(span) {
189
- const maskCandidates = [
190
- LangfuseOtelSpanAttributes.OBSERVATION_INPUT,
191
- LangfuseOtelSpanAttributes.TRACE_INPUT,
192
- LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT,
193
- LangfuseOtelSpanAttributes.TRACE_OUTPUT,
194
- LangfuseOtelSpanAttributes.OBSERVATION_METADATA,
195
- LangfuseOtelSpanAttributes.TRACE_METADATA,
196
- ];
197
- for (const maskCandidate of maskCandidates) {
198
- if (maskCandidate in span.attributes) {
199
- span.attributes[maskCandidate] = this.applyMask(span.attributes[maskCandidate]);
200
- }
201
- }
202
- }
203
- applyMask(data) {
204
- if (!this.mask)
205
- return data;
206
- try {
207
- return this.mask({ data });
208
- }
209
- catch (err) {
210
- this.logger.warn(`Applying mask function failed due to error, fully masking property. Error: ${err}`);
211
- return "<fully masked due to failed mask function>";
212
- }
213
- }
214
- async processMediaItem({ media, traceId, observationId, field, }) {
215
- try {
216
- if (!media.contentLength ||
217
- !media._contentType ||
218
- !media.contentSha256Hash ||
219
- !media._contentBytes) {
220
- return;
221
- }
222
- const { uploadUrl, mediaId } = await this.apiClient.media.getUploadUrl({
223
- contentLength: media.contentLength,
224
- traceId,
225
- observationId,
226
- field,
227
- contentType: media._contentType,
228
- sha256Hash: media.contentSha256Hash,
229
- });
230
- if (!uploadUrl) {
231
- this.logger.debug(`Media status: Media with ID ${media.id} already uploaded. Skipping duplicate upload.`);
232
- return;
233
- }
234
- if (media.id !== mediaId) {
235
- this.logger.error(`Media integrity error: Media ID mismatch between SDK (${media.id}) and Server (${mediaId}). Upload cancelled. Please check media ID generation logic.`);
236
- return;
237
- }
238
- this.logger.debug(`Uploading media ${mediaId}...`);
239
- const startTime = Date.now();
240
- const uploadResponse = await this.uploadMediaWithBackoff({
241
- uploadUrl,
242
- contentBytes: media._contentBytes,
243
- contentType: media._contentType,
244
- contentSha256Hash: media.contentSha256Hash,
245
- maxRetries: 3,
246
- baseDelay: 1000,
247
- });
248
- if (!uploadResponse) {
249
- throw Error("Media upload process failed");
250
- }
251
- await this.apiClient.media.patch(mediaId, {
252
- uploadedAt: new Date().toISOString(),
253
- uploadHttpStatus: uploadResponse.status,
254
- uploadHttpError: await uploadResponse.text(),
255
- uploadTimeMs: Date.now() - startTime,
256
- });
257
- this.logger.debug(`Media upload status reported for ${mediaId}`);
258
- }
259
- catch (err) {
260
- this.logger.error(`Error processing media item: ${err}`);
261
- }
262
- }
263
- async uploadMediaWithBackoff(params) {
264
- const { uploadUrl, contentType, contentSha256Hash, contentBytes, maxRetries, baseDelay, } = params;
265
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
266
- try {
267
- const uploadResponse = await fetch(uploadUrl, {
268
- method: "PUT",
269
- body: contentBytes,
270
- headers: {
271
- "Content-Type": contentType,
272
- "x-amz-checksum-sha256": contentSha256Hash,
273
- "x-ms-blob-type": "BlockBlob",
274
- },
275
- });
276
- if (attempt < maxRetries &&
277
- uploadResponse.status !== 200 &&
278
- uploadResponse.status !== 201) {
279
- throw new Error(`Upload failed with status ${uploadResponse.status}`);
280
- }
281
- return uploadResponse;
282
- }
283
- catch (e) {
284
- if (attempt === maxRetries) {
285
- throw e;
286
- }
287
- const delay = baseDelay * Math.pow(2, attempt);
288
- const jitter = Math.random() * 1000;
289
- await new Promise((resolve) => setTimeout(resolve, delay + jitter));
290
- }
291
- }
292
- }
293
- }