langsmith 0.3.52 → 0.3.54

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.
@@ -6,24 +6,10 @@ const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
6
6
  const constants_js_1 = require("./constants.cjs");
7
7
  const utils_js_1 = require("./utils.cjs");
8
8
  const run_trees_js_1 = require("../../run_trees.cjs");
9
- const NANOSECOND_DIGITS = 9;
10
- const MICROSECOND_DIGITS = 6;
11
9
  function isTraceableSpan(span) {
12
10
  return (span.attributes[constants_js_1.LANGSMITH_TRACEABLE] === "true" ||
13
11
  typeof span.attributes["ai.operationId"] === "string");
14
12
  }
15
- /**
16
- * Convert hrTime to timestamp, for example "2019-05-14T17:00:00.000123Z"
17
- * @param time
18
- */
19
- function hrTimeToTimeStamp(time) {
20
- const precision = NANOSECOND_DIGITS;
21
- const tmp = `${"0".repeat(precision)}${time[1]}Z`;
22
- const nanoString = tmp.substring(tmp.length - precision - 1);
23
- const date = new Date(time[0] * 1000).toISOString();
24
- // We only need 6 digits of precision for the dotted order
25
- return `${date.replace("000Z", nanoString.slice(0, MICROSECOND_DIGITS))}Z`;
26
- }
27
13
  function getParentSpanId(span) {
28
14
  // Backcompat shim to support OTEL 1.x and 2.x
29
15
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -53,35 +39,20 @@ class LangSmithOTLPSpanProcessor extends sdk_trace_base_1.BatchSpanProcessor {
53
39
  this.traceMap[span.spanContext().traceId].spanCount++;
54
40
  const isTraceable = isTraceableSpan(span);
55
41
  const parentSpanId = getParentSpanId(span);
42
+ this.traceMap[span.spanContext().traceId].spanInfo[span.spanContext().spanId] = {
43
+ isTraceable,
44
+ parentSpanId,
45
+ };
56
46
  let currentCandidateParentSpanId = parentSpanId;
57
47
  let traceableParentId;
58
- let parentDottedOrder;
59
- // LangSmith uses the first span's id as the trace id, NOT the actual OTEL trace id
60
- // Default to the current span if no parent information is present
61
- let lsTraceId = (0, utils_js_1.getUuidFromOtelSpanId)(span.spanContext().spanId);
62
48
  while (currentCandidateParentSpanId) {
63
49
  const currentSpanInfo = this.traceMap[span.spanContext().traceId].spanInfo[currentCandidateParentSpanId];
64
50
  if (currentSpanInfo?.isTraceable) {
65
51
  traceableParentId = currentCandidateParentSpanId;
66
- parentDottedOrder = currentSpanInfo.dottedOrder;
67
- lsTraceId = currentSpanInfo.lsTraceId;
68
52
  break;
69
53
  }
70
54
  currentCandidateParentSpanId = currentSpanInfo?.parentSpanId;
71
55
  }
72
- const startTimestamp = hrTimeToTimeStamp(span.startTime);
73
- const spanUuid = (0, utils_js_1.getUuidFromOtelSpanId)(span.spanContext().spanId);
74
- const dottedOrderComponent = (0, run_trees_js_1.stripNonAlphanumeric)(startTimestamp) + spanUuid;
75
- const currentDottedOrder = parentDottedOrder
76
- ? `${parentDottedOrder}.${dottedOrderComponent}`
77
- : dottedOrderComponent;
78
- this.traceMap[span.spanContext().traceId].spanInfo[span.spanContext().spanId] = {
79
- isTraceable,
80
- lsTraceId,
81
- spanId: span.spanContext().spanId,
82
- parentSpanId,
83
- dottedOrder: currentDottedOrder,
84
- };
85
56
  if (!traceableParentId) {
86
57
  span.attributes[constants_js_1.LANGSMITH_IS_ROOT] = true;
87
58
  }
@@ -89,8 +60,6 @@ class LangSmithOTLPSpanProcessor extends sdk_trace_base_1.BatchSpanProcessor {
89
60
  span.attributes[constants_js_1.LANGSMITH_PARENT_RUN_ID] =
90
61
  (0, utils_js_1.getUuidFromOtelSpanId)(traceableParentId);
91
62
  }
92
- span.attributes[constants_js_1.LANGSMITH_DOTTED_ORDER] = currentDottedOrder;
93
- span.attributes[constants_js_1.LANGSMITH_TRACE_ID] = lsTraceId;
94
63
  if (isTraceable) {
95
64
  super.onStart(span, parentContext);
96
65
  }
@@ -1,25 +1,11 @@
1
1
  import { BatchSpanProcessor, } from "@opentelemetry/sdk-trace-base";
2
- import { LANGSMITH_IS_ROOT, LANGSMITH_PARENT_RUN_ID, LANGSMITH_TRACEABLE, LANGSMITH_DOTTED_ORDER, LANGSMITH_TRACE_ID, } from "./constants.js";
2
+ import { LANGSMITH_IS_ROOT, LANGSMITH_PARENT_RUN_ID, LANGSMITH_TRACEABLE, } from "./constants.js";
3
3
  import { getUuidFromOtelSpanId } from "./utils.js";
4
- import { RunTree, stripNonAlphanumeric } from "../../run_trees.js";
5
- const NANOSECOND_DIGITS = 9;
6
- const MICROSECOND_DIGITS = 6;
4
+ import { RunTree } from "../../run_trees.js";
7
5
  export function isTraceableSpan(span) {
8
6
  return (span.attributes[LANGSMITH_TRACEABLE] === "true" ||
9
7
  typeof span.attributes["ai.operationId"] === "string");
10
8
  }
11
- /**
12
- * Convert hrTime to timestamp, for example "2019-05-14T17:00:00.000123Z"
13
- * @param time
14
- */
15
- function hrTimeToTimeStamp(time) {
16
- const precision = NANOSECOND_DIGITS;
17
- const tmp = `${"0".repeat(precision)}${time[1]}Z`;
18
- const nanoString = tmp.substring(tmp.length - precision - 1);
19
- const date = new Date(time[0] * 1000).toISOString();
20
- // We only need 6 digits of precision for the dotted order
21
- return `${date.replace("000Z", nanoString.slice(0, MICROSECOND_DIGITS))}Z`;
22
- }
23
9
  function getParentSpanId(span) {
24
10
  // Backcompat shim to support OTEL 1.x and 2.x
25
11
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -49,35 +35,20 @@ export class LangSmithOTLPSpanProcessor extends BatchSpanProcessor {
49
35
  this.traceMap[span.spanContext().traceId].spanCount++;
50
36
  const isTraceable = isTraceableSpan(span);
51
37
  const parentSpanId = getParentSpanId(span);
38
+ this.traceMap[span.spanContext().traceId].spanInfo[span.spanContext().spanId] = {
39
+ isTraceable,
40
+ parentSpanId,
41
+ };
52
42
  let currentCandidateParentSpanId = parentSpanId;
53
43
  let traceableParentId;
54
- let parentDottedOrder;
55
- // LangSmith uses the first span's id as the trace id, NOT the actual OTEL trace id
56
- // Default to the current span if no parent information is present
57
- let lsTraceId = getUuidFromOtelSpanId(span.spanContext().spanId);
58
44
  while (currentCandidateParentSpanId) {
59
45
  const currentSpanInfo = this.traceMap[span.spanContext().traceId].spanInfo[currentCandidateParentSpanId];
60
46
  if (currentSpanInfo?.isTraceable) {
61
47
  traceableParentId = currentCandidateParentSpanId;
62
- parentDottedOrder = currentSpanInfo.dottedOrder;
63
- lsTraceId = currentSpanInfo.lsTraceId;
64
48
  break;
65
49
  }
66
50
  currentCandidateParentSpanId = currentSpanInfo?.parentSpanId;
67
51
  }
68
- const startTimestamp = hrTimeToTimeStamp(span.startTime);
69
- const spanUuid = getUuidFromOtelSpanId(span.spanContext().spanId);
70
- const dottedOrderComponent = stripNonAlphanumeric(startTimestamp) + spanUuid;
71
- const currentDottedOrder = parentDottedOrder
72
- ? `${parentDottedOrder}.${dottedOrderComponent}`
73
- : dottedOrderComponent;
74
- this.traceMap[span.spanContext().traceId].spanInfo[span.spanContext().spanId] = {
75
- isTraceable,
76
- lsTraceId,
77
- spanId: span.spanContext().spanId,
78
- parentSpanId,
79
- dottedOrder: currentDottedOrder,
80
- };
81
52
  if (!traceableParentId) {
82
53
  span.attributes[LANGSMITH_IS_ROOT] = true;
83
54
  }
@@ -85,8 +56,6 @@ export class LangSmithOTLPSpanProcessor extends BatchSpanProcessor {
85
56
  span.attributes[LANGSMITH_PARENT_RUN_ID] =
86
57
  getUuidFromOtelSpanId(traceableParentId);
87
58
  }
88
- span.attributes[LANGSMITH_DOTTED_ORDER] = currentDottedOrder;
89
- span.attributes[LANGSMITH_TRACE_ID] = lsTraceId;
90
59
  if (isTraceable) {
91
60
  super.onStart(span, parentContext);
92
61
  }
package/dist/index.cjs CHANGED
@@ -10,4 +10,4 @@ Object.defineProperty(exports, "overrideFetchImplementation", { enumerable: true
10
10
  var project_js_1 = require("./utils/project.cjs");
11
11
  Object.defineProperty(exports, "getDefaultProjectName", { enumerable: true, get: function () { return project_js_1.getDefaultProjectName; } });
12
12
  // Update using yarn bump-version
13
- exports.__version__ = "0.3.52";
13
+ exports.__version__ = "0.3.54";
package/dist/index.d.ts CHANGED
@@ -3,4 +3,4 @@ export type { Dataset, Example, TracerSession, Run, Feedback, RetrieverOutput, }
3
3
  export { RunTree, type RunTreeConfig } from "./run_trees.js";
4
4
  export { overrideFetchImplementation } from "./singletons/fetch.js";
5
5
  export { getDefaultProjectName } from "./utils/project.js";
6
- export declare const __version__ = "0.3.52";
6
+ export declare const __version__ = "0.3.54";
package/dist/index.js CHANGED
@@ -3,4 +3,4 @@ export { RunTree } from "./run_trees.js";
3
3
  export { overrideFetchImplementation } from "./singletons/fetch.js";
4
4
  export { getDefaultProjectName } from "./utils/project.js";
5
5
  // Update using yarn bump-version
6
- export const __version__ = "0.3.52";
6
+ export const __version__ = "0.3.54";
@@ -34,7 +34,6 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.RunTree = void 0;
37
- exports.stripNonAlphanumeric = stripNonAlphanumeric;
38
37
  exports.convertToDottedOrderFormat = convertToDottedOrderFormat;
39
38
  exports.isRunTree = isRunTree;
40
39
  exports.isRunnableConfigLike = isRunnableConfigLike;
@@ -1,6 +1,5 @@
1
1
  import { Client } from "./client.js";
2
2
  import { Attachments, BaseRun, KVMap, RunCreate } from "./schemas.js";
3
- export declare function stripNonAlphanumeric(input: string): string;
4
3
  export declare function convertToDottedOrderFormat(epoch: number, runId: string, executionOrder?: number): {
5
4
  dottedOrder: string;
6
5
  microsecondPrecisionDatestring: string;
package/dist/run_trees.js CHANGED
@@ -7,7 +7,7 @@ import { getEnvironmentVariable, getRuntimeEnvironment, } from "./utils/env.js";
7
7
  import { getDefaultProjectName } from "./utils/project.js";
8
8
  import { getLangSmithEnvironmentVariable } from "./utils/env.js";
9
9
  import { warnOnce } from "./utils/warn.js";
10
- export function stripNonAlphanumeric(input) {
10
+ function stripNonAlphanumeric(input) {
11
11
  return input.replace(/[-:.]/g, "");
12
12
  }
13
13
  export function convertToDottedOrderFormat(epoch, runId, executionOrder = 1) {
package/dist/vercel.cjs CHANGED
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AISDKExporter = exports.toStrippedIsoTime = exports.parseStrippedIsoTime = void 0;
4
- exports.getMutableRunCreate = getMutableRunCreate;
3
+ exports.AISDKExporter = exports.parseStrippedIsoTime = void 0;
5
4
  const vercel_js_1 = require("./utils/vercel.cjs");
6
5
  const index_js_1 = require("./index.cjs");
7
6
  const uuid_1 = require("uuid");
@@ -19,19 +18,22 @@ function convertCoreToSmith(message) {
19
18
  return {
20
19
  type: "text",
21
20
  text: part.text,
22
- // @ts-expect-error Backcompat for AI SDK 4
21
+ // Backcompat for AI SDK 4
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
23
  ...part.experimental_providerMetadata,
24
24
  };
25
25
  }
26
26
  if (part.type === "tool-call") {
27
- // @ts-expect-error Backcompat for AI SDK 4
27
+ // Backcompat for AI SDK 4
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
29
  const legacyToolCallInput = part.args;
29
30
  return {
30
31
  type: "tool_use",
31
32
  name: part.toolName,
32
33
  id: part.toolCallId,
33
34
  input: legacyToolCallInput ?? part.input,
34
- // @ts-expect-error Backcompat for AI SDK 4
35
+ // Backcompat for AI SDK 4
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
37
  ...part.experimental_providerMetadata,
36
38
  };
37
39
  }
@@ -41,7 +43,8 @@ function convertCoreToSmith(message) {
41
43
  if (toolCalls.length > 0) {
42
44
  data.additional_kwargs ??= {};
43
45
  data.additional_kwargs.tool_calls = toolCalls.map((part) => {
44
- // @ts-expect-error Backcompat for AI SDK 4
46
+ // Backcompat for AI SDK 4
47
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
48
  const legacyToolCallInput = part.args;
46
49
  return {
47
50
  id: part.toolCallId,
@@ -65,7 +68,8 @@ function convertCoreToSmith(message) {
65
68
  return {
66
69
  type: "text",
67
70
  text: part.text,
68
- // @ts-expect-error Backcompat for AI SDK 4
71
+ // Backcompat for AI SDK 4
72
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
73
  ...part.experimental_providerMetadata,
70
74
  };
71
75
  }
@@ -100,7 +104,8 @@ function convertCoreToSmith(message) {
100
104
  return {
101
105
  type: "image_url",
102
106
  image_url: imageUrl,
103
- // @ts-expect-error Backcompat for AI SDK 4
107
+ // Backcompat for AI SDK 4
108
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
104
109
  ...part.experimental_providerMetadata,
105
110
  };
106
111
  }
@@ -114,7 +119,8 @@ function convertCoreToSmith(message) {
114
119
  }
115
120
  if (message.role === "tool") {
116
121
  const res = message.content.map((toolCall) => {
117
- // @ts-expect-error Backcompat for AI SDK 4
122
+ // Backcompat for AI SDK 4
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
118
124
  const legacyToolCallResult = toolCall.result;
119
125
  return {
120
126
  type: "tool",
@@ -173,58 +179,38 @@ function reparentDotOrder(dotOrder, sourceRunId, parentDotOrder) {
173
179
  return dotOrder;
174
180
  return joinDotOrder(...parentDotOrder.split("."), ...segments.slice(sourceIndex));
175
181
  }
176
- // Helper function to convert stripped ISO string back to parseable format
182
+ // Helper function to convert dotted order version of start time to ISO string
177
183
  const parseStrippedIsoTime = (stripped) => {
178
- // Insert back the removed characters: YYYYMMDDTHHMMSSSSSSSS -> YYYY-MM-DDTHH:MM:SS.SSSZ
179
- // The stripped format is timestamp part only (no Z - that becomes the separator)
180
- // Format includes microseconds: 20231201T120000000000 (milliseconds + microseconds)
181
184
  const year = stripped.slice(0, 4);
182
185
  const month = stripped.slice(4, 6);
183
186
  const day = stripped.slice(6, 8);
184
187
  const hour = stripped.slice(9, 11); // Skip 'T'
185
188
  const minute = stripped.slice(11, 13);
186
189
  const second = stripped.slice(13, 15);
187
- const ms = stripped.slice(15, 18); // Only use first 3 digits for milliseconds
188
- // Ignore microseconds (18-21) as Date only has millisecond precision
189
- return new Date(`${year}-${month}-${day}T${hour}:${minute}:${second}.${ms}Z`);
190
+ const ms = stripped.slice(15, 18); // milliseconds
191
+ const us = stripped.length >= 21 ? stripped.slice(18, 21) : "000"; // microseconds
192
+ // Create ISO string with microsecond precision only if microseconds are present
193
+ return us !== "000"
194
+ ? `${year}-${month}-${day}T${hour}:${minute}:${second}.${ms}${us}Z`
195
+ : `${year}-${month}-${day}T${hour}:${minute}:${second}.${ms}Z`;
190
196
  };
191
197
  exports.parseStrippedIsoTime = parseStrippedIsoTime;
192
- // Helper function to convert Date back to stripped format
193
- const toStrippedIsoTime = (date) => {
194
- return stripNonAlphanumeric(date.toISOString().slice(0, -1)) + "000";
195
- };
196
- exports.toStrippedIsoTime = toStrippedIsoTime;
197
198
  function getMutableRunCreate(dotOrder) {
198
199
  const segments = dotOrder.split(".").map((i) => {
199
200
  const [startTime, runId] = i.split("Z");
200
201
  return { startTime, runId };
201
202
  });
202
- // Iteratively check and fix timing to ensure each segment is greater than its parent
203
- for (let i = 1; i < segments.length; i++) {
204
- const parentTime = (0, exports.parseStrippedIsoTime)(segments[i - 1].startTime);
205
- const currentTime = (0, exports.parseStrippedIsoTime)(segments[i].startTime);
206
- if (currentTime.getTime() <= parentTime.getTime()) {
207
- // Increment by 1 millisecond to make it greater than parent
208
- const newTime = new Date(parentTime.getTime() + 1);
209
- segments[i].startTime = (0, exports.toStrippedIsoTime)(newTime);
210
- }
211
- }
212
- // Reconstruct the dotted order with potentially updated timestamps
213
- const updatedDotOrder = segments
214
- .map((segment) => `${segment.startTime}Z${segment.runId}`)
215
- .join(".");
216
203
  const traceId = segments[0].runId;
217
204
  const parentRunId = segments.at(-2)?.runId;
218
205
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
219
- const runId = segments.at(-1).runId;
220
- // If this is the last segment (current run), set start_time to its ISO timestamp
221
- const lastSegmentTime = (0, exports.parseStrippedIsoTime)(segments.at(-1).startTime);
206
+ const lastSegment = segments.at(-1);
207
+ const startTime = (0, exports.parseStrippedIsoTime)(lastSegment.startTime);
222
208
  return {
223
- id: runId,
209
+ id: lastSegment.runId,
224
210
  trace_id: traceId,
225
- dotted_order: updatedDotOrder,
211
+ dotted_order: dotOrder,
226
212
  parent_run_id: parentRunId,
227
- start_time: lastSegmentTime.toISOString(),
213
+ start_time: startTime,
228
214
  };
229
215
  }
230
216
  function convertToTimestamp([seconds, nanoseconds]) {
@@ -816,15 +802,10 @@ class AISDKExporter {
816
802
  }
817
803
  this.seenSpanInfo[task.id].dotOrder = taskDotOrder;
818
804
  if (!this.seenSpanInfo[task.id].sent) {
819
- const updated = {
805
+ sampled.push({
820
806
  ...task.run,
821
807
  ...getMutableRunCreate(taskDotOrder),
822
- };
823
- if (updated.end_time !== undefined &&
824
- updated.end_time < updated.start_time) {
825
- updated.end_time = new Date(new Date(updated.start_time).getTime() + 1).toISOString();
826
- }
827
- sampled.push(updated);
808
+ });
828
809
  }
829
810
  this.seenSpanInfo[task.id].sent = true;
830
811
  }
package/dist/vercel.d.ts CHANGED
@@ -7,16 +7,7 @@ export interface TelemetrySettings extends AITelemetrySettings {
7
7
  /** Name of the run sent to LangSmith */
8
8
  runName?: string;
9
9
  }
10
- interface MutableRunCreate {
11
- id: string;
12
- trace_id: string;
13
- dotted_order: string;
14
- parent_run_id: string | undefined;
15
- start_time: string;
16
- }
17
- export declare const parseStrippedIsoTime: (stripped: string) => Date;
18
- export declare const toStrippedIsoTime: (date: Date) => string;
19
- export declare function getMutableRunCreate(dotOrder: string): MutableRunCreate;
10
+ export declare const parseStrippedIsoTime: (stripped: string) => string;
20
11
  /**
21
12
  * OpenTelemetry trace exporter for Vercel AI SDK.
22
13
  *
@@ -91,4 +82,3 @@ export declare class AISDKExporter {
91
82
  forceFlush(): Promise<void>;
92
83
  protected logDebug(...args: Parameters<typeof console.debug>): void;
93
84
  }
94
- export {};
package/dist/vercel.js CHANGED
@@ -15,19 +15,22 @@ function convertCoreToSmith(message) {
15
15
  return {
16
16
  type: "text",
17
17
  text: part.text,
18
- // @ts-expect-error Backcompat for AI SDK 4
18
+ // Backcompat for AI SDK 4
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
20
  ...part.experimental_providerMetadata,
20
21
  };
21
22
  }
22
23
  if (part.type === "tool-call") {
23
- // @ts-expect-error Backcompat for AI SDK 4
24
+ // Backcompat for AI SDK 4
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
26
  const legacyToolCallInput = part.args;
25
27
  return {
26
28
  type: "tool_use",
27
29
  name: part.toolName,
28
30
  id: part.toolCallId,
29
31
  input: legacyToolCallInput ?? part.input,
30
- // @ts-expect-error Backcompat for AI SDK 4
32
+ // Backcompat for AI SDK 4
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
34
  ...part.experimental_providerMetadata,
32
35
  };
33
36
  }
@@ -37,7 +40,8 @@ function convertCoreToSmith(message) {
37
40
  if (toolCalls.length > 0) {
38
41
  data.additional_kwargs ??= {};
39
42
  data.additional_kwargs.tool_calls = toolCalls.map((part) => {
40
- // @ts-expect-error Backcompat for AI SDK 4
43
+ // Backcompat for AI SDK 4
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
45
  const legacyToolCallInput = part.args;
42
46
  return {
43
47
  id: part.toolCallId,
@@ -61,7 +65,8 @@ function convertCoreToSmith(message) {
61
65
  return {
62
66
  type: "text",
63
67
  text: part.text,
64
- // @ts-expect-error Backcompat for AI SDK 4
68
+ // Backcompat for AI SDK 4
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
70
  ...part.experimental_providerMetadata,
66
71
  };
67
72
  }
@@ -96,7 +101,8 @@ function convertCoreToSmith(message) {
96
101
  return {
97
102
  type: "image_url",
98
103
  image_url: imageUrl,
99
- // @ts-expect-error Backcompat for AI SDK 4
104
+ // Backcompat for AI SDK 4
105
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
106
  ...part.experimental_providerMetadata,
101
107
  };
102
108
  }
@@ -110,7 +116,8 @@ function convertCoreToSmith(message) {
110
116
  }
111
117
  if (message.role === "tool") {
112
118
  const res = message.content.map((toolCall) => {
113
- // @ts-expect-error Backcompat for AI SDK 4
119
+ // Backcompat for AI SDK 4
120
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
121
  const legacyToolCallResult = toolCall.result;
115
122
  return {
116
123
  type: "tool",
@@ -169,56 +176,37 @@ function reparentDotOrder(dotOrder, sourceRunId, parentDotOrder) {
169
176
  return dotOrder;
170
177
  return joinDotOrder(...parentDotOrder.split("."), ...segments.slice(sourceIndex));
171
178
  }
172
- // Helper function to convert stripped ISO string back to parseable format
179
+ // Helper function to convert dotted order version of start time to ISO string
173
180
  export const parseStrippedIsoTime = (stripped) => {
174
- // Insert back the removed characters: YYYYMMDDTHHMMSSSSSSSS -> YYYY-MM-DDTHH:MM:SS.SSSZ
175
- // The stripped format is timestamp part only (no Z - that becomes the separator)
176
- // Format includes microseconds: 20231201T120000000000 (milliseconds + microseconds)
177
181
  const year = stripped.slice(0, 4);
178
182
  const month = stripped.slice(4, 6);
179
183
  const day = stripped.slice(6, 8);
180
184
  const hour = stripped.slice(9, 11); // Skip 'T'
181
185
  const minute = stripped.slice(11, 13);
182
186
  const second = stripped.slice(13, 15);
183
- const ms = stripped.slice(15, 18); // Only use first 3 digits for milliseconds
184
- // Ignore microseconds (18-21) as Date only has millisecond precision
185
- return new Date(`${year}-${month}-${day}T${hour}:${minute}:${second}.${ms}Z`);
187
+ const ms = stripped.slice(15, 18); // milliseconds
188
+ const us = stripped.length >= 21 ? stripped.slice(18, 21) : "000"; // microseconds
189
+ // Create ISO string with microsecond precision only if microseconds are present
190
+ return us !== "000"
191
+ ? `${year}-${month}-${day}T${hour}:${minute}:${second}.${ms}${us}Z`
192
+ : `${year}-${month}-${day}T${hour}:${minute}:${second}.${ms}Z`;
186
193
  };
187
- // Helper function to convert Date back to stripped format
188
- export const toStrippedIsoTime = (date) => {
189
- return stripNonAlphanumeric(date.toISOString().slice(0, -1)) + "000";
190
- };
191
- export function getMutableRunCreate(dotOrder) {
194
+ function getMutableRunCreate(dotOrder) {
192
195
  const segments = dotOrder.split(".").map((i) => {
193
196
  const [startTime, runId] = i.split("Z");
194
197
  return { startTime, runId };
195
198
  });
196
- // Iteratively check and fix timing to ensure each segment is greater than its parent
197
- for (let i = 1; i < segments.length; i++) {
198
- const parentTime = parseStrippedIsoTime(segments[i - 1].startTime);
199
- const currentTime = parseStrippedIsoTime(segments[i].startTime);
200
- if (currentTime.getTime() <= parentTime.getTime()) {
201
- // Increment by 1 millisecond to make it greater than parent
202
- const newTime = new Date(parentTime.getTime() + 1);
203
- segments[i].startTime = toStrippedIsoTime(newTime);
204
- }
205
- }
206
- // Reconstruct the dotted order with potentially updated timestamps
207
- const updatedDotOrder = segments
208
- .map((segment) => `${segment.startTime}Z${segment.runId}`)
209
- .join(".");
210
199
  const traceId = segments[0].runId;
211
200
  const parentRunId = segments.at(-2)?.runId;
212
201
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
213
- const runId = segments.at(-1).runId;
214
- // If this is the last segment (current run), set start_time to its ISO timestamp
215
- const lastSegmentTime = parseStrippedIsoTime(segments.at(-1).startTime);
202
+ const lastSegment = segments.at(-1);
203
+ const startTime = parseStrippedIsoTime(lastSegment.startTime);
216
204
  return {
217
- id: runId,
205
+ id: lastSegment.runId,
218
206
  trace_id: traceId,
219
- dotted_order: updatedDotOrder,
207
+ dotted_order: dotOrder,
220
208
  parent_run_id: parentRunId,
221
- start_time: lastSegmentTime.toISOString(),
209
+ start_time: startTime,
222
210
  };
223
211
  }
224
212
  function convertToTimestamp([seconds, nanoseconds]) {
@@ -810,15 +798,10 @@ export class AISDKExporter {
810
798
  }
811
799
  this.seenSpanInfo[task.id].dotOrder = taskDotOrder;
812
800
  if (!this.seenSpanInfo[task.id].sent) {
813
- const updated = {
801
+ sampled.push({
814
802
  ...task.run,
815
803
  ...getMutableRunCreate(taskDotOrder),
816
- };
817
- if (updated.end_time !== undefined &&
818
- updated.end_time < updated.start_time) {
819
- updated.end_time = new Date(new Date(updated.start_time).getTime() + 1).toISOString();
820
- }
821
- sampled.push(updated);
804
+ });
822
805
  }
823
806
  this.seenSpanInfo[task.id].sent = true;
824
807
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.3.52",
3
+ "version": "0.3.54",
4
4
  "description": "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform.",
5
5
  "packageManager": "yarn@1.22.19",
6
6
  "files": [