@langfuse/core 5.5.2 → 5.6.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
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  CommentObjectType: () => CommentObjectType,
35
35
  CreateChatPromptType: () => CreateChatPromptType,
36
36
  CreateTextPromptType: () => CreateTextPromptType,
37
+ DatasetItemMediaReferenceField: () => DatasetItemMediaReferenceField,
37
38
  DatasetStatus: () => DatasetStatus,
38
39
  Error: () => Error2,
39
40
  LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT: () => LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT,
@@ -45,6 +46,7 @@ __export(index_exports, {
45
46
  LangfuseAPIError: () => LangfuseAPIError,
46
47
  LangfuseAPITimeoutError: () => LangfuseAPITimeoutError,
47
48
  LangfuseMedia: () => LangfuseMedia,
49
+ LangfuseMediaReference: () => LangfuseMediaReference,
48
50
  LangfuseOtelContextKeys: () => LangfuseOtelContextKeys,
49
51
  LangfuseOtelSpanAttributes: () => LangfuseOtelSpanAttributes,
50
52
  LlmAdapter: () => LlmAdapter,
@@ -111,6 +113,7 @@ __export(index_exports, {
111
113
  setLangfuseTraceIdInBaggage: () => setLangfuseTraceIdInBaggage,
112
114
  trace: () => trace_exports,
113
115
  unstable: () => unstable_exports,
116
+ uploadMedia: () => uploadMedia,
114
117
  utils: () => utils_exports
115
118
  });
116
119
  module.exports = __toCommonJS(index_exports);
@@ -387,7 +390,7 @@ var resetGlobalLogger = () => {
387
390
  // package.json
388
391
  var package_default = {
389
392
  name: "@langfuse/core",
390
- version: "5.5.2",
393
+ version: "5.6.0",
391
394
  description: "Core functions and utilities for Langfuse packages",
392
395
  type: "module",
393
396
  sideEffects: false,
@@ -573,6 +576,7 @@ var commons_exports = {};
573
576
  __export(commons_exports, {
574
577
  AccessDeniedError: () => AccessDeniedError,
575
578
  CommentObjectType: () => CommentObjectType,
579
+ DatasetItemMediaReferenceField: () => DatasetItemMediaReferenceField,
576
580
  DatasetStatus: () => DatasetStatus,
577
581
  Error: () => Error2,
578
582
  MethodNotAllowedError: () => MethodNotAllowedError,
@@ -586,6 +590,13 @@ __export(commons_exports, {
586
590
  UnauthorizedError: () => UnauthorizedError
587
591
  });
588
592
 
593
+ // src/api/api/resources/commons/types/DatasetItemMediaReferenceField.ts
594
+ var DatasetItemMediaReferenceField = {
595
+ Input: "input",
596
+ ExpectedOutput: "expectedOutput",
597
+ Metadata: "metadata"
598
+ };
599
+
589
600
  // src/api/api/resources/commons/types/PricingTierOperator.ts
590
601
  var PricingTierOperator = {
591
602
  Gt: "gt",
@@ -6912,8 +6923,10 @@ var Media = class {
6912
6923
  *
6913
6924
  * @example
6914
6925
  * await client.media.getUploadUrl({
6915
- * traceId: "traceId",
6926
+ * traceId: undefined,
6916
6927
  * observationId: undefined,
6928
+ * datasetId: undefined,
6929
+ * datasetItemId: undefined,
6917
6930
  * contentType: "image/png",
6918
6931
  * contentLength: 1,
6919
6932
  * sha256Hash: "sha256Hash",
@@ -12101,7 +12114,12 @@ var Sessions = class {
12101
12114
  this._options = _options;
12102
12115
  }
12103
12116
  /**
12104
- * Get sessions
12117
+ * Get sessions.
12118
+ *
12119
+ * This legacy endpoint is not recommended for new data extraction workflows.
12120
+ * Use the v2 observations endpoint with a bounded time range and group rows by
12121
+ * `sessionId` instead:
12122
+ * `GET /api/public/v2/observations?fromStartTime=<from>&toStartTime=<to>`.
12105
12123
  *
12106
12124
  * @param {LangfuseAPI.GetSessionsRequest} request
12107
12125
  * @param {Sessions.RequestOptions} requestOptions - Request-specific configuration.
@@ -12225,7 +12243,12 @@ var Sessions = class {
12225
12243
  }
12226
12244
  }
12227
12245
  /**
12228
- * Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=<sessionId>`
12246
+ * Get a session.
12247
+ *
12248
+ * Please note that `traces` on this endpoint are not paginated. For large
12249
+ * sessions or new data extraction workflows, use the v2 observations endpoint
12250
+ * with a URL-encoded `sessionId` filter and a bounded time range:
12251
+ * `GET /api/public/v2/observations?filter=<sessionId filter>&fromStartTime=<from>&toStartTime=<to>`.
12229
12252
  *
12230
12253
  * @param {string} sessionId - The unique id of a session
12231
12254
  * @param {Sessions.RequestOptions} requestOptions - Request-specific configuration.
@@ -14744,6 +14767,196 @@ var LangfuseMedia = class {
14744
14767
  return this.base64DataUri;
14745
14768
  }
14746
14769
  };
14770
+ var LangfuseMediaReference = class {
14771
+ constructor(params) {
14772
+ Object.assign(this, params);
14773
+ }
14774
+ /**
14775
+ * Serializes to the original `@@@langfuseMedia:…@@@` reference string.
14776
+ *
14777
+ * This makes resolved references round-trip losslessly through anything that
14778
+ * serializes with `JSON.stringify` — the dataset item API, experiment/trace
14779
+ * span attributes — so a re-used item links back to its media instead of
14780
+ * persisting a JSON object with a soon-to-expire signed URL.
14781
+ */
14782
+ toJSON() {
14783
+ return this.referenceString;
14784
+ }
14785
+ /**
14786
+ * Returns whether the signed download URL is expired or near expiry.
14787
+ *
14788
+ * @param thresholdSeconds - Treat the URL as expired this many seconds before
14789
+ * its actual expiry to account for clock skew and download time (default: 60).
14790
+ * @returns true if the URL is expired or within the threshold of expiry. If
14791
+ * the expiry is unknown or unparseable, returns false.
14792
+ */
14793
+ isUrlExpired(thresholdSeconds = 60) {
14794
+ if (!this.urlExpiry) {
14795
+ return false;
14796
+ }
14797
+ const expiryMs = Date.parse(this.urlExpiry);
14798
+ if (Number.isNaN(expiryMs)) {
14799
+ return false;
14800
+ }
14801
+ return expiryMs - Date.now() <= thresholdSeconds * 1e3;
14802
+ }
14803
+ /**
14804
+ * Fetches the media content from the signed URL over the network.
14805
+ *
14806
+ * Useful for local evaluators / image libraries, manual base64 conversion, or
14807
+ * the Vercel AI SDK (`{ type: "image", image: await media.fetchBytes() }`).
14808
+ *
14809
+ * @returns The media content as raw bytes
14810
+ * @throws {Error} If the download fails
14811
+ */
14812
+ async fetchBytes() {
14813
+ const response = await fetch(this.url, { method: "GET", headers: {} });
14814
+ if (!response.ok) {
14815
+ throw new Error(
14816
+ `Failed to fetch media ${this.mediaId}: HTTP ${response.status}`
14817
+ );
14818
+ }
14819
+ return new Uint8Array(await response.arrayBuffer());
14820
+ }
14821
+ /**
14822
+ * Fetches the media over the network and returns raw base64 (no data URI prefix).
14823
+ *
14824
+ * Useful for Anthropic (`{ source: { type: "base64", media_type: media.contentType, data: await media.fetchBase64() } }`)
14825
+ * or LangChain (`{ type: "image", base64: await media.fetchBase64(), mime_type: media.contentType }`).
14826
+ *
14827
+ * @returns The media content as a base64 string
14828
+ * @throws {Error} If the download fails
14829
+ */
14830
+ async fetchBase64() {
14831
+ return bytesToBase64(await this.fetchBytes());
14832
+ }
14833
+ /**
14834
+ * Fetches the media over the network and returns a `data:<contentType>;base64,...` URI.
14835
+ *
14836
+ * Useful for OpenAI (`{ type: "input_image", image_url: await media.fetchDataUri() }`).
14837
+ *
14838
+ * @returns The media content as a base64 data URI
14839
+ * @throws {Error} If the download fails
14840
+ */
14841
+ async fetchDataUri() {
14842
+ return `data:${this.contentType};base64,${await this.fetchBase64()}`;
14843
+ }
14844
+ };
14845
+
14846
+ // src/mediaUpload.ts
14847
+ async function uploadMedia(params) {
14848
+ var _a2;
14849
+ const {
14850
+ apiClient,
14851
+ media,
14852
+ traceId,
14853
+ observationId,
14854
+ datasetId,
14855
+ datasetItemId,
14856
+ field,
14857
+ maxRetries = 3,
14858
+ baseDelay = 1e3
14859
+ } = params;
14860
+ const logger = (_a2 = params.logger) != null ? _a2 : getGlobalLogger();
14861
+ const contentSha256Hash = await media.getSha256Hash();
14862
+ if (!media.contentLength || !media._contentType || !contentSha256Hash || !media._contentBytes) {
14863
+ throw new Error("Cannot upload media: media content is incomplete.");
14864
+ }
14865
+ const { uploadUrl, mediaId } = await apiClient.media.getUploadUrl({
14866
+ contentLength: media.contentLength,
14867
+ traceId,
14868
+ observationId,
14869
+ datasetId,
14870
+ datasetItemId,
14871
+ field,
14872
+ contentType: media._contentType,
14873
+ sha256Hash: contentSha256Hash
14874
+ });
14875
+ if (!uploadUrl) {
14876
+ logger.debug(
14877
+ `Media status: Media with ID ${mediaId} already uploaded. Skipping duplicate upload.`
14878
+ );
14879
+ return;
14880
+ }
14881
+ const clientSideMediaId = await media.getId();
14882
+ if (clientSideMediaId !== mediaId) {
14883
+ throw new Error(
14884
+ `Media integrity error: Media ID mismatch between SDK (${clientSideMediaId}) and Server (${mediaId}). Upload cancelled.`
14885
+ );
14886
+ }
14887
+ logger.debug(`Uploading media ${mediaId}...`);
14888
+ const startTime = Date.now();
14889
+ const uploadResponse = await uploadWithBackoff({
14890
+ uploadUrl,
14891
+ contentBytes: media._contentBytes,
14892
+ contentType: media._contentType,
14893
+ contentSha256Hash,
14894
+ maxRetries,
14895
+ baseDelay
14896
+ });
14897
+ if (!uploadResponse) {
14898
+ throw new Error("Media upload process failed");
14899
+ }
14900
+ const uploadSucceeded = uploadResponse.status === 200 || uploadResponse.status === 201;
14901
+ await apiClient.media.patch(mediaId, {
14902
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
14903
+ uploadHttpStatus: uploadResponse.status,
14904
+ uploadHttpError: await uploadResponse.text(),
14905
+ uploadTimeMs: Date.now() - startTime
14906
+ });
14907
+ logger.debug(`Media upload status reported for ${mediaId}`);
14908
+ if (!uploadSucceeded) {
14909
+ throw new Error(
14910
+ `Media upload failed with HTTP ${uploadResponse.status} after ${maxRetries} retries.`
14911
+ );
14912
+ }
14913
+ }
14914
+ async function uploadWithBackoff(params) {
14915
+ const {
14916
+ uploadUrl,
14917
+ contentType,
14918
+ contentSha256Hash,
14919
+ contentBytes,
14920
+ maxRetries,
14921
+ baseDelay
14922
+ } = params;
14923
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
14924
+ try {
14925
+ let parsedHostname;
14926
+ try {
14927
+ parsedHostname = new URL(uploadUrl).hostname;
14928
+ } catch {
14929
+ parsedHostname = "";
14930
+ }
14931
+ const isSelfHostedGcsBucket = parsedHostname === "storage.googleapis.com" || parsedHostname.endsWith(".storage.googleapis.com");
14932
+ const headers = isSelfHostedGcsBucket ? { "Content-Type": contentType } : {
14933
+ "Content-Type": contentType,
14934
+ "x-amz-checksum-sha256": contentSha256Hash,
14935
+ "x-ms-blob-type": "BlockBlob"
14936
+ };
14937
+ const uploadResponse = await fetch(uploadUrl, {
14938
+ method: "PUT",
14939
+ // Recent fetch typings narrow BodyInit to an ArrayBuffer-backed view,
14940
+ // while Uint8Array now defaults to Uint8Array<ArrayBufferLike>. The
14941
+ // media bytes are always ArrayBuffer-backed, so assert the narrower type.
14942
+ body: contentBytes,
14943
+ headers
14944
+ });
14945
+ if (attempt < maxRetries && uploadResponse.status !== 200 && uploadResponse.status !== 201) {
14946
+ throw new Error(`Upload failed with status ${uploadResponse.status}`);
14947
+ }
14948
+ return uploadResponse;
14949
+ } catch (e) {
14950
+ if (attempt === maxRetries) {
14951
+ throw e;
14952
+ }
14953
+ const delay = baseDelay * Math.pow(2, attempt);
14954
+ const jitter = Math.random() * 1e3;
14955
+ await new Promise((resolve) => setTimeout(resolve, delay + jitter));
14956
+ }
14957
+ }
14958
+ return void 0;
14959
+ }
14747
14960
 
14748
14961
  // src/propagation.ts
14749
14962
  var import_api = require("@opentelemetry/api");
@@ -15175,6 +15388,7 @@ function getSpanKeyFromBaggageKey(baggageKey) {
15175
15388
  CommentObjectType,
15176
15389
  CreateChatPromptType,
15177
15390
  CreateTextPromptType,
15391
+ DatasetItemMediaReferenceField,
15178
15392
  DatasetStatus,
15179
15393
  Error,
15180
15394
  LANGFUSE_SDK_EXPERIMENT_ENVIRONMENT,
@@ -15186,6 +15400,7 @@ function getSpanKeyFromBaggageKey(baggageKey) {
15186
15400
  LangfuseAPIError,
15187
15401
  LangfuseAPITimeoutError,
15188
15402
  LangfuseMedia,
15403
+ LangfuseMediaReference,
15189
15404
  LangfuseOtelContextKeys,
15190
15405
  LangfuseOtelSpanAttributes,
15191
15406
  LlmAdapter,
@@ -15252,6 +15467,7 @@ function getSpanKeyFromBaggageKey(baggageKey) {
15252
15467
  setLangfuseTraceIdInBaggage,
15253
15468
  trace,
15254
15469
  unstable,
15470
+ uploadMedia,
15255
15471
  utils
15256
15472
  });
15257
15473
  //# sourceMappingURL=index.cjs.map