langsmith 0.8.4 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LS_MESSAGE_VIEW_EXCLUDE = exports.__version__ = exports.InternalServerError = exports.RateLimitError = exports.UnprocessableEntityError = exports.ConflictError = exports.NotFoundError = exports.PermissionDeniedError = exports.AuthenticationError = exports.BadRequestError = exports.APIConnectionTimeoutError = exports.APIConnectionError = exports.APIUserAbortError = exports.APIError = exports.LangsmithError = exports.promptCacheSingleton = exports.configureGlobalPromptCache = exports.PromptCache = exports.Cache = exports.isTracingEnabled = exports.uuid7FromTime = exports.uuid7 = exports.getDefaultProjectName = exports.overrideFetchImplementation = exports.RunTree = exports.Client = void 0;
3
+ exports.LS_MESSAGE_VIEW_EXCLUDE = exports.__version__ = exports.InternalServerError = exports.RateLimitError = exports.UnprocessableEntityError = exports.ConflictError = exports.NotFoundError = exports.PermissionDeniedError = exports.AuthenticationError = exports.BadRequestError = exports.APIConnectionTimeoutError = exports.APIConnectionError = exports.APIUserAbortError = exports.APIError = exports.LangsmithError = exports.promptCacheSingleton = exports.configureGlobalPromptCache = exports.PromptCache = exports.Cache = exports.isTracingEnabled = exports.uuid7FromTime = exports.uuid7 = exports.computeRunIdForSecondaryReplica = exports.getDefaultProjectName = exports.overrideFetchImplementation = exports.RunTree = exports.Client = void 0;
4
4
  var client_js_1 = require("./client.cjs");
5
5
  Object.defineProperty(exports, "Client", { enumerable: true, get: function () { return client_js_1.Client; } });
6
6
  var run_trees_js_1 = require("./run_trees.cjs");
@@ -10,6 +10,7 @@ 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
  var uuid_js_1 = require("./uuid.cjs");
13
+ Object.defineProperty(exports, "computeRunIdForSecondaryReplica", { enumerable: true, get: function () { return uuid_js_1.computeRunIdForSecondaryReplica; } });
13
14
  Object.defineProperty(exports, "uuid7", { enumerable: true, get: function () { return uuid_js_1.uuid7; } });
14
15
  Object.defineProperty(exports, "uuid7FromTime", { enumerable: true, get: function () { return uuid_js_1.uuid7FromTime; } });
15
16
  var guard_js_1 = require("./utils/guard.cjs");
@@ -34,6 +35,6 @@ Object.defineProperty(exports, "UnprocessableEntityError", { enumerable: true, g
34
35
  Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return error_js_1.RateLimitError; } });
35
36
  Object.defineProperty(exports, "InternalServerError", { enumerable: true, get: function () { return error_js_1.InternalServerError; } });
36
37
  // Update using pnpm bump-version
37
- exports.__version__ = "0.8.4";
38
+ exports.__version__ = "0.8.6";
38
39
  // Metadata key to hide a traced run from LangSmith's Messages View.
39
40
  exports.LS_MESSAGE_VIEW_EXCLUDE = "ls_message_view_exclude";
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  export { Client, type ClientConfig, type LangSmithTracingClientInterface, } from "./client.js";
2
2
  export type { Dataset, Example, TracerSession, Run, Feedback, FeedbackConfigSchema, RetrieverOutput, } from "./schemas.js";
3
- export { RunTree, type RunTreeConfig } from "./run_trees.js";
3
+ export { RunTree, type RunTreeConfig, type WriteReplica } from "./run_trees.js";
4
4
  export { overrideFetchImplementation } from "./singletons/fetch.js";
5
5
  export { getDefaultProjectName } from "./utils/project.js";
6
- export { uuid7, uuid7FromTime } from "./uuid.js";
6
+ export { computeRunIdForSecondaryReplica, uuid7, uuid7FromTime, } from "./uuid.js";
7
7
  export { isTracingEnabled } from "./utils/guard.js";
8
8
  export { Cache, PromptCache, type CacheConfig, type CacheMetrics, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
9
9
  export { LangsmithError, APIError, APIUserAbortError, APIConnectionError, APIConnectionTimeoutError, BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, } from "./_openapi_client/core/error.js";
10
- export declare const __version__ = "0.8.4";
10
+ export declare const __version__ = "0.8.6";
11
11
  export declare const LS_MESSAGE_VIEW_EXCLUDE: "ls_message_view_exclude";
package/dist/index.js CHANGED
@@ -2,11 +2,11 @@ export { Client, } from "./client.js";
2
2
  export { RunTree } from "./run_trees.js";
3
3
  export { overrideFetchImplementation } from "./singletons/fetch.js";
4
4
  export { getDefaultProjectName } from "./utils/project.js";
5
- export { uuid7, uuid7FromTime } from "./uuid.js";
5
+ export { computeRunIdForSecondaryReplica, uuid7, uuid7FromTime, } from "./uuid.js";
6
6
  export { isTracingEnabled } from "./utils/guard.js";
7
7
  export { Cache, PromptCache, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
8
8
  export { LangsmithError, APIError, APIUserAbortError, APIConnectionError, APIConnectionTimeoutError, BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, } from "./_openapi_client/core/error.js";
9
9
  // Update using pnpm bump-version
10
- export const __version__ = "0.8.4";
10
+ export const __version__ = "0.8.6";
11
11
  // Metadata key to hide a traced run from LangSmith's Messages View.
12
12
  export const LS_MESSAGE_VIEW_EXCLUDE = "ls_message_view_exclude";
@@ -46,12 +46,16 @@ function convertToDottedOrderFormat(epoch, runId, executionOrder = 1) {
46
46
  }
47
47
  const HEADER_SAFE_REPLICA_FIELDS = new Set([
48
48
  "projectName",
49
+ "primary",
49
50
  "updates",
50
51
  "reroot",
51
52
  ]);
52
53
  function filterReplicaForHeaders(replica) {
53
54
  const filtered = {};
54
55
  for (const key of Object.keys(replica)) {
56
+ if (key === "primary" && typeof replica[key] !== "boolean") {
57
+ continue;
58
+ }
55
59
  if (HEADER_SAFE_REPLICA_FIELDS.has(key)) {
56
60
  filtered[key] = replica[key];
57
61
  }
@@ -567,10 +571,10 @@ class RunTree {
567
571
  }
568
572
  }
569
573
  _remapForProject(params) {
570
- const { projectName, runtimeEnv, excludeChildRuns = true, reroot = false, distributedParentId, apiUrl, apiKey, workspaceId, } = params;
574
+ const { projectName, primary, runtimeEnv, excludeChildRuns = true, reroot = false, distributedParentId, apiUrl, apiKey, workspaceId, } = params;
571
575
  const baseRun = this._convertToCreate(this, runtimeEnv, excludeChildRuns);
572
- // Skip remapping if project name is the same
573
- if (projectName === this.project_name) {
576
+ // Preserve legacy behavior when `primary` is omitted.
577
+ if (primary === undefined && projectName === this.project_name) {
574
578
  return {
575
579
  ...baseRun,
576
580
  session_name: projectName,
@@ -643,6 +647,12 @@ class RunTree {
643
647
  }
644
648
  }
645
649
  }
650
+ if (primary) {
651
+ return {
652
+ ...baseRun,
653
+ session_name: projectName,
654
+ };
655
+ }
646
656
  // Remap IDs for the replica using nonCryptographicUuid7Deterministic
647
657
  // This ensures consistency across runs in the same replica while
648
658
  // preserving UUID7 properties (time-ordering, monotonicity)
@@ -691,9 +701,10 @@ class RunTree {
691
701
  try {
692
702
  const runtimeEnv = (0, env_js_2.getRuntimeEnvironment)();
693
703
  if (this.replicas && this.replicas.length > 0) {
694
- for (const { projectName, apiKey, apiUrl, workspaceId, reroot, client: replicaClient, } of this.replicas) {
704
+ for (const { projectName, primary, apiKey, apiUrl, workspaceId, reroot, client: replicaClient, } of this.replicas) {
695
705
  const runCreate = this._remapForProject({
696
706
  projectName: projectName ?? this.project_name,
707
+ primary,
697
708
  runtimeEnv,
698
709
  excludeChildRuns: true,
699
710
  reroot,
@@ -728,9 +739,10 @@ class RunTree {
728
739
  }
729
740
  async patchRun(options) {
730
741
  if (this.replicas && this.replicas.length > 0) {
731
- for (const { projectName, apiKey, apiUrl, workspaceId, updates, reroot, client: replicaClient, } of this.replicas) {
742
+ for (const { projectName, primary, apiKey, apiUrl, workspaceId, updates, reroot, client: replicaClient, } of this.replicas) {
732
743
  const runData = this._remapForProject({
733
744
  projectName: projectName ?? this.project_name,
745
+ primary,
734
746
  runtimeEnv: undefined,
735
747
  excludeChildRuns: true,
736
748
  reroot,
@@ -996,10 +1008,16 @@ function _getWriteReplicasFromEnv() {
996
1008
  `expected string, got ${typeof item.project_name}`);
997
1009
  continue;
998
1010
  }
1011
+ if (item.primary !== undefined && typeof item.primary !== "boolean") {
1012
+ console.warn(`Invalid primary type in LANGSMITH_RUNS_ENDPOINTS: ` +
1013
+ `expected boolean, got ${typeof item.primary}`);
1014
+ continue;
1015
+ }
999
1016
  replicas.push({
1000
1017
  apiUrl: item.api_url.replace(/\/$/, ""),
1001
1018
  apiKey: item.api_key,
1002
1019
  projectName: item.project_name ?? undefined,
1020
+ primary: item.primary ?? undefined,
1003
1021
  });
1004
1022
  }
1005
1023
  return replicas;
@@ -1039,9 +1057,8 @@ function _getWriteReplicasFromEnv() {
1039
1057
  }
1040
1058
  }
1041
1059
  function _ensureWriteReplicas(replicas) {
1042
- // If null -> fetch from env
1043
- if (replicas) {
1044
- return replicas.map((replica) => {
1060
+ const ensured = replicas
1061
+ ? replicas.map((replica) => {
1045
1062
  if (Array.isArray(replica)) {
1046
1063
  return {
1047
1064
  projectName: replica[0],
@@ -1049,9 +1066,12 @@ function _ensureWriteReplicas(replicas) {
1049
1066
  };
1050
1067
  }
1051
1068
  return replica;
1052
- });
1069
+ })
1070
+ : _getWriteReplicasFromEnv();
1071
+ if (ensured.filter((replica) => replica.primary === true).length > 1) {
1072
+ throw new Error("Only one replica can be marked as primary.");
1053
1073
  }
1054
- return _getWriteReplicasFromEnv();
1074
+ return ensured;
1055
1075
  }
1056
1076
  function _checkEndpointEnvUnset(parsed) {
1057
1077
  if (Object.keys(parsed).length > 0 &&
@@ -55,11 +55,13 @@ interface HeadersLike {
55
55
  set(name: string, value: string): void;
56
56
  }
57
57
  type ProjectReplica = [string, KVMap | undefined];
58
- type WriteReplica = {
58
+ export type WriteReplica = {
59
59
  apiUrl?: string;
60
60
  apiKey?: string;
61
61
  workspaceId?: string;
62
62
  projectName?: string;
63
+ /** Whether this replica keeps the original run IDs. */
64
+ primary?: boolean;
63
65
  updates?: KVMap | undefined;
64
66
  fromEnv?: boolean;
65
67
  reroot?: boolean;
package/dist/run_trees.js CHANGED
@@ -40,12 +40,16 @@ export function convertToDottedOrderFormat(epoch, runId, executionOrder = 1) {
40
40
  }
41
41
  const HEADER_SAFE_REPLICA_FIELDS = new Set([
42
42
  "projectName",
43
+ "primary",
43
44
  "updates",
44
45
  "reroot",
45
46
  ]);
46
47
  function filterReplicaForHeaders(replica) {
47
48
  const filtered = {};
48
49
  for (const key of Object.keys(replica)) {
50
+ if (key === "primary" && typeof replica[key] !== "boolean") {
51
+ continue;
52
+ }
49
53
  if (HEADER_SAFE_REPLICA_FIELDS.has(key)) {
50
54
  filtered[key] = replica[key];
51
55
  }
@@ -561,10 +565,10 @@ export class RunTree {
561
565
  }
562
566
  }
563
567
  _remapForProject(params) {
564
- const { projectName, runtimeEnv, excludeChildRuns = true, reroot = false, distributedParentId, apiUrl, apiKey, workspaceId, } = params;
568
+ const { projectName, primary, runtimeEnv, excludeChildRuns = true, reroot = false, distributedParentId, apiUrl, apiKey, workspaceId, } = params;
565
569
  const baseRun = this._convertToCreate(this, runtimeEnv, excludeChildRuns);
566
- // Skip remapping if project name is the same
567
- if (projectName === this.project_name) {
570
+ // Preserve legacy behavior when `primary` is omitted.
571
+ if (primary === undefined && projectName === this.project_name) {
568
572
  return {
569
573
  ...baseRun,
570
574
  session_name: projectName,
@@ -637,6 +641,12 @@ export class RunTree {
637
641
  }
638
642
  }
639
643
  }
644
+ if (primary) {
645
+ return {
646
+ ...baseRun,
647
+ session_name: projectName,
648
+ };
649
+ }
640
650
  // Remap IDs for the replica using nonCryptographicUuid7Deterministic
641
651
  // This ensures consistency across runs in the same replica while
642
652
  // preserving UUID7 properties (time-ordering, monotonicity)
@@ -685,9 +695,10 @@ export class RunTree {
685
695
  try {
686
696
  const runtimeEnv = getRuntimeEnvironment();
687
697
  if (this.replicas && this.replicas.length > 0) {
688
- for (const { projectName, apiKey, apiUrl, workspaceId, reroot, client: replicaClient, } of this.replicas) {
698
+ for (const { projectName, primary, apiKey, apiUrl, workspaceId, reroot, client: replicaClient, } of this.replicas) {
689
699
  const runCreate = this._remapForProject({
690
700
  projectName: projectName ?? this.project_name,
701
+ primary,
691
702
  runtimeEnv,
692
703
  excludeChildRuns: true,
693
704
  reroot,
@@ -722,9 +733,10 @@ export class RunTree {
722
733
  }
723
734
  async patchRun(options) {
724
735
  if (this.replicas && this.replicas.length > 0) {
725
- for (const { projectName, apiKey, apiUrl, workspaceId, updates, reroot, client: replicaClient, } of this.replicas) {
736
+ for (const { projectName, primary, apiKey, apiUrl, workspaceId, updates, reroot, client: replicaClient, } of this.replicas) {
726
737
  const runData = this._remapForProject({
727
738
  projectName: projectName ?? this.project_name,
739
+ primary,
728
740
  runtimeEnv: undefined,
729
741
  excludeChildRuns: true,
730
742
  reroot,
@@ -989,10 +1001,16 @@ function _getWriteReplicasFromEnv() {
989
1001
  `expected string, got ${typeof item.project_name}`);
990
1002
  continue;
991
1003
  }
1004
+ if (item.primary !== undefined && typeof item.primary !== "boolean") {
1005
+ console.warn(`Invalid primary type in LANGSMITH_RUNS_ENDPOINTS: ` +
1006
+ `expected boolean, got ${typeof item.primary}`);
1007
+ continue;
1008
+ }
992
1009
  replicas.push({
993
1010
  apiUrl: item.api_url.replace(/\/$/, ""),
994
1011
  apiKey: item.api_key,
995
1012
  projectName: item.project_name ?? undefined,
1013
+ primary: item.primary ?? undefined,
996
1014
  });
997
1015
  }
998
1016
  return replicas;
@@ -1032,9 +1050,8 @@ function _getWriteReplicasFromEnv() {
1032
1050
  }
1033
1051
  }
1034
1052
  function _ensureWriteReplicas(replicas) {
1035
- // If null -> fetch from env
1036
- if (replicas) {
1037
- return replicas.map((replica) => {
1053
+ const ensured = replicas
1054
+ ? replicas.map((replica) => {
1038
1055
  if (Array.isArray(replica)) {
1039
1056
  return {
1040
1057
  projectName: replica[0],
@@ -1042,9 +1059,12 @@ function _ensureWriteReplicas(replicas) {
1042
1059
  };
1043
1060
  }
1044
1061
  return replica;
1045
- });
1062
+ })
1063
+ : _getWriteReplicasFromEnv();
1064
+ if (ensured.filter((replica) => replica.primary === true).length > 1) {
1065
+ throw new Error("Only one replica can be marked as primary.");
1046
1066
  }
1047
- return _getWriteReplicasFromEnv();
1067
+ return ensured;
1048
1068
  }
1049
1069
  function _checkEndpointEnvUnset(parsed) {
1050
1070
  if (Object.keys(parsed).length > 0 &&
@@ -147,7 +147,7 @@ class CommandHandle {
147
147
  return;
148
148
  const firstResult = await this._stream.next();
149
149
  if (firstResult.done) {
150
- throw new errors_js_1.LangSmithSandboxOperationError("Command stream ended before 'started' message", "command");
150
+ throw new errors_js_1.LangSmithStreamEndedBeforeStartedError("Command stream ended before 'started' message");
151
151
  }
152
152
  const firstMsg = firstResult.value;
153
153
  if (firstMsg.type !== "started") {
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Port of Python's AsyncCommandHandle to TypeScript.
6
6
  */
7
- import { LangSmithSandboxConnectionError, LangSmithSandboxOperationError, } from "./errors.js";
7
+ import { LangSmithSandboxConnectionError, LangSmithSandboxOperationError, LangSmithStreamEndedBeforeStartedError, } from "./errors.js";
8
8
  /**
9
9
  * Async handle to a running command with streaming output and auto-reconnect.
10
10
  *
@@ -144,7 +144,7 @@ export class CommandHandle {
144
144
  return;
145
145
  const firstResult = await this._stream.next();
146
146
  if (firstResult.done) {
147
- throw new LangSmithSandboxOperationError("Command stream ended before 'started' message", "command");
147
+ throw new LangSmithStreamEndedBeforeStartedError("Command stream ended before 'started' message");
148
148
  }
149
149
  const firstMsg = firstResult.value;
150
150
  if (firstMsg.type !== "started") {
@@ -7,7 +7,7 @@
7
7
  * properties for specific handling when needed.
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.LangSmithSandboxServerReloadError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = void 0;
10
+ exports.LangSmithSandboxServerReloadError = exports.LangSmithStreamEndedBeforeStartedError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = void 0;
11
11
  /**
12
12
  * Base exception for sandbox client errors.
13
13
  */
@@ -357,6 +357,21 @@ class LangSmithCommandTimeoutError extends LangSmithSandboxOperationError {
357
357
  }
358
358
  }
359
359
  exports.LangSmithCommandTimeoutError = LangSmithCommandTimeoutError;
360
+ /**
361
+ * Internal: a command WebSocket closed before the guest sent its 'started'
362
+ * frame (the proxied tunnel was torn down gracefully mid-handshake).
363
+ *
364
+ * Marks the idempotently-retryable early close, as distinct from a
365
+ * command-level failure. Subclasses the operation error so callers that catch
366
+ * the public type are unaffected.
367
+ */
368
+ class LangSmithStreamEndedBeforeStartedError extends LangSmithSandboxOperationError {
369
+ constructor(message) {
370
+ super(message, "command");
371
+ this.name = "LangSmithStreamEndedBeforeStartedError";
372
+ }
373
+ }
374
+ exports.LangSmithStreamEndedBeforeStartedError = LangSmithStreamEndedBeforeStartedError;
360
375
  /**
361
376
  * Raised when the sandbox server is reloading (close code 1001).
362
377
  *
@@ -154,6 +154,17 @@ export declare class LangSmithCommandTimeoutError extends LangSmithSandboxOperat
154
154
  timeout?: number;
155
155
  constructor(message: string, timeout?: number);
156
156
  }
157
+ /**
158
+ * Internal: a command WebSocket closed before the guest sent its 'started'
159
+ * frame (the proxied tunnel was torn down gracefully mid-handshake).
160
+ *
161
+ * Marks the idempotently-retryable early close, as distinct from a
162
+ * command-level failure. Subclasses the operation error so callers that catch
163
+ * the public type are unaffected.
164
+ */
165
+ export declare class LangSmithStreamEndedBeforeStartedError extends LangSmithSandboxOperationError {
166
+ constructor(message: string);
167
+ }
157
168
  /**
158
169
  * Raised when the sandbox server is reloading (close code 1001).
159
170
  *
@@ -337,6 +337,20 @@ export class LangSmithCommandTimeoutError extends LangSmithSandboxOperationError
337
337
  this.timeout = timeout;
338
338
  }
339
339
  }
340
+ /**
341
+ * Internal: a command WebSocket closed before the guest sent its 'started'
342
+ * frame (the proxied tunnel was torn down gracefully mid-handshake).
343
+ *
344
+ * Marks the idempotently-retryable early close, as distinct from a
345
+ * command-level failure. Subclasses the operation error so callers that catch
346
+ * the public type are unaffected.
347
+ */
348
+ export class LangSmithStreamEndedBeforeStartedError extends LangSmithSandboxOperationError {
349
+ constructor(message) {
350
+ super(message, "command");
351
+ this.name = "LangSmithStreamEndedBeforeStartedError";
352
+ }
353
+ }
340
354
  /**
341
355
  * Raised when the sandbox server is reloading (close code 1001).
342
356
  *
@@ -4,6 +4,7 @@
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Sandbox = void 0;
7
+ const uuid_js_1 = require("../uuid.cjs");
7
8
  const errors_js_1 = require("./errors.cjs");
8
9
  const helpers_js_1 = require("./helpers.cjs");
9
10
  const command_handle_js_1 = require("./command_handle.cjs");
@@ -229,26 +230,49 @@ class Sandbox {
229
230
  const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout, killOnDisconnect, ttlSeconds, pty, } = options;
230
231
  const dataplaneUrl = this.requireDataplaneUrl();
231
232
  const clientHeaders = this._client.getDefaultHeaders();
232
- const [stream, control] = await (0, ws_execute_js_1.runWsStream)(dataplaneUrl, this._client.getApiKey(), command, {
233
- timeout,
234
- env,
235
- cwd,
236
- shell,
237
- commandId,
238
- idleTimeout,
239
- killOnDisconnect,
240
- ttlSeconds,
241
- pty,
242
- ...(Object.keys(clientHeaders).length > 0
243
- ? { headers: clientHeaders }
244
- : {}),
245
- });
246
- const handle = new command_handle_js_1.CommandHandle(stream, control, this, {
247
- onStdout,
248
- onStderr,
249
- });
250
- await handle._ensureStarted();
251
- return handle;
233
+ // A client-supplied command_id makes execute idempotent: the daemon does
234
+ // get-or-create keyed on it, so if the tunnel closes before "started" we
235
+ // can re-issue the same id and reattach to the existing command instead of
236
+ // spawning a second one.
237
+ const execCommandId = commandId ?? (0, uuid_js_1.uuid7)();
238
+ let attempt = 0;
239
+ // eslint-disable-next-line no-constant-condition
240
+ while (true) {
241
+ const [stream, control] = await (0, ws_execute_js_1.runWsStream)(dataplaneUrl, this._client.getApiKey(), command, {
242
+ timeout,
243
+ env,
244
+ cwd,
245
+ shell,
246
+ commandId: execCommandId,
247
+ idleTimeout,
248
+ killOnDisconnect,
249
+ ttlSeconds,
250
+ pty,
251
+ ...(Object.keys(clientHeaders).length > 0
252
+ ? { headers: clientHeaders }
253
+ : {}),
254
+ });
255
+ const handle = new command_handle_js_1.CommandHandle(stream, control, this, {
256
+ onStdout,
257
+ onStderr,
258
+ });
259
+ try {
260
+ await handle._ensureStarted();
261
+ return handle;
262
+ }
263
+ catch (e) {
264
+ // Idempotent re-issue (same command_id) after an early close.
265
+ if (!(e instanceof errors_js_1.LangSmithStreamEndedBeforeStartedError)) {
266
+ throw e;
267
+ }
268
+ attempt++;
269
+ if (attempt > command_handle_js_1.CommandHandle.MAX_AUTO_RECONNECTS) {
270
+ throw e;
271
+ }
272
+ const delay = Math.min(command_handle_js_1.CommandHandle.BACKOFF_BASE * 2 ** (attempt - 1), command_handle_js_1.CommandHandle.BACKOFF_MAX);
273
+ await new Promise((r) => setTimeout(r, delay * 1000));
274
+ }
275
+ }
252
276
  }
253
277
  /**
254
278
  * Execute a command via HTTP POST (blocking).
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Sandbox class for interacting with a specific sandbox instance.
3
3
  */
4
- import { LangSmithDataplaneNotConfiguredError } from "./errors.js";
4
+ import { uuid7 } from "../uuid.js";
5
+ import { LangSmithDataplaneNotConfiguredError, LangSmithStreamEndedBeforeStartedError, } from "./errors.js";
5
6
  import { handleSandboxHttpError } from "./helpers.js";
6
7
  import { CommandHandle } from "./command_handle.js";
7
8
  import { reconnectWsStream, runWsStream } from "./ws_execute.js";
@@ -226,26 +227,49 @@ export class Sandbox {
226
227
  const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout, killOnDisconnect, ttlSeconds, pty, } = options;
227
228
  const dataplaneUrl = this.requireDataplaneUrl();
228
229
  const clientHeaders = this._client.getDefaultHeaders();
229
- const [stream, control] = await runWsStream(dataplaneUrl, this._client.getApiKey(), command, {
230
- timeout,
231
- env,
232
- cwd,
233
- shell,
234
- commandId,
235
- idleTimeout,
236
- killOnDisconnect,
237
- ttlSeconds,
238
- pty,
239
- ...(Object.keys(clientHeaders).length > 0
240
- ? { headers: clientHeaders }
241
- : {}),
242
- });
243
- const handle = new CommandHandle(stream, control, this, {
244
- onStdout,
245
- onStderr,
246
- });
247
- await handle._ensureStarted();
248
- return handle;
230
+ // A client-supplied command_id makes execute idempotent: the daemon does
231
+ // get-or-create keyed on it, so if the tunnel closes before "started" we
232
+ // can re-issue the same id and reattach to the existing command instead of
233
+ // spawning a second one.
234
+ const execCommandId = commandId ?? uuid7();
235
+ let attempt = 0;
236
+ // eslint-disable-next-line no-constant-condition
237
+ while (true) {
238
+ const [stream, control] = await runWsStream(dataplaneUrl, this._client.getApiKey(), command, {
239
+ timeout,
240
+ env,
241
+ cwd,
242
+ shell,
243
+ commandId: execCommandId,
244
+ idleTimeout,
245
+ killOnDisconnect,
246
+ ttlSeconds,
247
+ pty,
248
+ ...(Object.keys(clientHeaders).length > 0
249
+ ? { headers: clientHeaders }
250
+ : {}),
251
+ });
252
+ const handle = new CommandHandle(stream, control, this, {
253
+ onStdout,
254
+ onStderr,
255
+ });
256
+ try {
257
+ await handle._ensureStarted();
258
+ return handle;
259
+ }
260
+ catch (e) {
261
+ // Idempotent re-issue (same command_id) after an early close.
262
+ if (!(e instanceof LangSmithStreamEndedBeforeStartedError)) {
263
+ throw e;
264
+ }
265
+ attempt++;
266
+ if (attempt > CommandHandle.MAX_AUTO_RECONNECTS) {
267
+ throw e;
268
+ }
269
+ const delay = Math.min(CommandHandle.BACKOFF_BASE * 2 ** (attempt - 1), CommandHandle.BACKOFF_MAX);
270
+ await new Promise((r) => setTimeout(r, delay * 1000));
271
+ }
272
+ }
249
273
  }
250
274
  /**
251
275
  * Execute a command via HTTP POST (blocking).
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.syncExamplePromises = exports.evaluatorLogFeedbackPromises = exports.testWrapperAsyncLocalStorageInstance = exports.DEFAULT_TEST_CLIENT = void 0;
3
+ exports.evaluatorLogFeedbackPromises = exports.testWrapperAsyncLocalStorageInstance = exports.DEFAULT_TEST_CLIENT = void 0;
4
4
  exports.trackingEnabled = trackingEnabled;
5
5
  exports._logTestFeedback = _logTestFeedback;
6
6
  const node_async_hooks_1 = require("node:async_hooks");
@@ -19,7 +19,6 @@ function trackingEnabled(context) {
19
19
  return (0, env_js_2.isEnvTracingEnabled)();
20
20
  }
21
21
  exports.evaluatorLogFeedbackPromises = new Set();
22
- exports.syncExamplePromises = new Map();
23
22
  function _logTestFeedback(params) {
24
23
  const { exampleId, feedback, context, runTree, client, sourceRunId } = params;
25
24
  if (trackingEnabled(context)) {
@@ -30,10 +29,18 @@ function _logTestFeedback(params) {
30
29
  throw new Error("Could not log feedback to LangSmith: missing run information. Please contact us for help.");
31
30
  }
32
31
  exports.evaluatorLogFeedbackPromises.add((async () => {
33
- await exports.syncExamplePromises.get(exampleId);
34
- await client?.logEvaluationFeedback(feedback, runTree, sourceRunId !== undefined
35
- ? { __run: { run_id: sourceRunId } }
36
- : undefined);
32
+ if (context.project == null) {
33
+ throw new Error("Could not log feedback to LangSmith: missing project information. Please contact us for help.");
34
+ }
35
+ await context.syncExamplePromises?.get(exampleId);
36
+ await client?.logEvaluationFeedback({
37
+ evaluatorResponse: feedback,
38
+ run: runTree,
39
+ projectId: context.project.id,
40
+ sourceInfo: sourceRunId !== undefined
41
+ ? { __run: { run_id: sourceRunId } }
42
+ : undefined,
43
+ });
37
44
  })());
38
45
  }
39
46
  context.onFeedbackLogged?.(feedback);
@@ -20,11 +20,11 @@ export type TestWrapperAsyncLocalStorageData = {
20
20
  suiteName: string;
21
21
  testRootRunTree?: RunTree;
22
22
  setupPromise?: Promise<void>;
23
+ syncExamplePromises?: Map<string, Promise<Example>>;
23
24
  };
24
25
  export declare const testWrapperAsyncLocalStorageInstance: AsyncLocalStorage<TestWrapperAsyncLocalStorageData>;
25
26
  export declare function trackingEnabled(context: TestWrapperAsyncLocalStorageData): boolean;
26
27
  export declare const evaluatorLogFeedbackPromises: Set<unknown>;
27
- export declare const syncExamplePromises: Map<any, any>;
28
28
  export declare function _logTestFeedback(params: {
29
29
  exampleId?: string;
30
30
  feedback: SimpleEvaluationResult;
@@ -14,7 +14,6 @@ export function trackingEnabled(context) {
14
14
  return isEnvTracingEnabled();
15
15
  }
16
16
  export const evaluatorLogFeedbackPromises = new Set();
17
- export const syncExamplePromises = new Map();
18
17
  export function _logTestFeedback(params) {
19
18
  const { exampleId, feedback, context, runTree, client, sourceRunId } = params;
20
19
  if (trackingEnabled(context)) {
@@ -25,10 +24,18 @@ export function _logTestFeedback(params) {
25
24
  throw new Error("Could not log feedback to LangSmith: missing run information. Please contact us for help.");
26
25
  }
27
26
  evaluatorLogFeedbackPromises.add((async () => {
28
- await syncExamplePromises.get(exampleId);
29
- await client?.logEvaluationFeedback(feedback, runTree, sourceRunId !== undefined
30
- ? { __run: { run_id: sourceRunId } }
31
- : undefined);
27
+ if (context.project == null) {
28
+ throw new Error("Could not log feedback to LangSmith: missing project information. Please contact us for help.");
29
+ }
30
+ await context.syncExamplePromises?.get(exampleId);
31
+ await client?.logEvaluationFeedback({
32
+ evaluatorResponse: feedback,
33
+ run: runTree,
34
+ projectId: context.project.id,
35
+ sourceInfo: sourceRunId !== undefined
36
+ ? { __run: { run_id: sourceRunId } }
37
+ : undefined,
38
+ });
32
39
  })());
33
40
  }
34
41
  context.onFeedbackLogged?.(feedback);