langsmith 0.8.6 → 0.8.8

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.
Files changed (72) hide show
  1. package/dist/_openapi_client/client.cjs +8 -0
  2. package/dist/_openapi_client/client.d.ts +3 -0
  3. package/dist/_openapi_client/client.js +8 -0
  4. package/dist/_openapi_client/resources/annotation-queues/annotation-queues.cjs +147 -0
  5. package/dist/_openapi_client/resources/annotation-queues/annotation-queues.d.ts +372 -0
  6. package/dist/_openapi_client/resources/annotation-queues/annotation-queues.js +110 -0
  7. package/dist/_openapi_client/resources/annotation-queues/items.cjs +84 -0
  8. package/dist/_openapi_client/resources/annotation-queues/items.d.ts +207 -0
  9. package/dist/_openapi_client/resources/annotation-queues/items.js +80 -0
  10. package/dist/_openapi_client/resources/annotation-queues/runs.cjs +61 -0
  11. package/dist/_openapi_client/resources/annotation-queues/runs.d.ts +185 -0
  12. package/dist/_openapi_client/resources/annotation-queues/runs.js +57 -0
  13. package/dist/_openapi_client/resources/datasets/experiment-runs.cjs +1 -1
  14. package/dist/_openapi_client/resources/datasets/experiment-runs.js +1 -1
  15. package/dist/_openapi_client/resources/index.cjs +3 -1
  16. package/dist/_openapi_client/resources/index.d.ts +1 -0
  17. package/dist/_openapi_client/resources/index.js +1 -0
  18. package/dist/_openapi_client/resources/info.cjs +3 -1
  19. package/dist/_openapi_client/resources/info.d.ts +16 -22
  20. package/dist/_openapi_client/resources/info.js +3 -1
  21. package/dist/_openapi_client/resources/issues.cjs +2 -2
  22. package/dist/_openapi_client/resources/issues.js +2 -2
  23. package/dist/_openapi_client/resources/online-evaluators.cjs +7 -7
  24. package/dist/_openapi_client/resources/online-evaluators.d.ts +8 -0
  25. package/dist/_openapi_client/resources/online-evaluators.js +7 -7
  26. package/dist/_openapi_client/resources/public/runs.cjs +2 -2
  27. package/dist/_openapi_client/resources/public/runs.d.ts +1 -1
  28. package/dist/_openapi_client/resources/public/runs.js +2 -2
  29. package/dist/_openapi_client/resources/runs/runs.cjs +3 -3
  30. package/dist/_openapi_client/resources/runs/runs.d.ts +9 -4
  31. package/dist/_openapi_client/resources/runs/runs.js +3 -3
  32. package/dist/_openapi_client/resources/runs/share.cjs +2 -2
  33. package/dist/_openapi_client/resources/runs/share.js +2 -2
  34. package/dist/_openapi_client/resources/sandboxes/boxes.cjs +10 -10
  35. package/dist/_openapi_client/resources/sandboxes/boxes.d.ts +40 -0
  36. package/dist/_openapi_client/resources/sandboxes/boxes.js +10 -10
  37. package/dist/_openapi_client/resources/sandboxes/registries.cjs +5 -5
  38. package/dist/_openapi_client/resources/sandboxes/registries.js +5 -5
  39. package/dist/_openapi_client/resources/sandboxes/sandboxes.d.ts +17 -0
  40. package/dist/_openapi_client/resources/sandboxes/snapshots.cjs +4 -4
  41. package/dist/_openapi_client/resources/sandboxes/snapshots.d.ts +12 -0
  42. package/dist/_openapi_client/resources/sandboxes/snapshots.js +4 -4
  43. package/dist/_openapi_client/resources/threads.cjs +3 -3
  44. package/dist/_openapi_client/resources/threads.d.ts +2 -1
  45. package/dist/_openapi_client/resources/threads.js +3 -3
  46. package/dist/_openapi_client/resources/traces.cjs +2 -2
  47. package/dist/_openapi_client/resources/traces.d.ts +1 -1
  48. package/dist/_openapi_client/resources/traces.js +2 -2
  49. package/dist/client.cjs +57 -21
  50. package/dist/client.d.ts +58 -22
  51. package/dist/client.js +57 -21
  52. package/dist/index.cjs +1 -1
  53. package/dist/index.d.ts +1 -1
  54. package/dist/index.js +1 -1
  55. package/dist/sandbox/command_handle.cjs +4 -4
  56. package/dist/sandbox/command_handle.js +5 -5
  57. package/dist/sandbox/errors.cjs +16 -1
  58. package/dist/sandbox/errors.d.ts +11 -0
  59. package/dist/sandbox/errors.js +14 -0
  60. package/dist/sandbox/index.cjs +2 -1
  61. package/dist/sandbox/index.d.ts +1 -1
  62. package/dist/sandbox/index.js +1 -1
  63. package/dist/sandbox/sandbox.cjs +33 -17
  64. package/dist/sandbox/sandbox.d.ts +2 -1
  65. package/dist/sandbox/sandbox.js +35 -19
  66. package/dist/sandbox/ws_execute.cjs +87 -13
  67. package/dist/sandbox/ws_execute.d.ts +10 -0
  68. package/dist/sandbox/ws_execute.js +83 -13
  69. package/package.json +1 -1
  70. package/dist/utils/constants.cjs +0 -4
  71. package/dist/utils/constants.d.ts +0 -1
  72. package/dist/utils/constants.js +0 -1
@@ -351,6 +351,20 @@ export class LangSmithStreamEndedBeforeStartedError extends LangSmithSandboxOper
351
351
  this.name = "LangSmithStreamEndedBeforeStartedError";
352
352
  }
353
353
  }
354
+ /**
355
+ * Thrown when the socket fails or times out before the WebSocket handshake.
356
+ *
357
+ * Distinct from its parent because it is safely retryable: the execute frame
358
+ * was never sent, so re-issuing the same command_id cannot double-run a
359
+ * command. run() retries this with backoff; a plain connection error (a
360
+ * rejected handshake) is permanent and propagates immediately.
361
+ */
362
+ export class LangSmithSandboxConnectTimeoutError extends LangSmithSandboxConnectionError {
363
+ constructor(message) {
364
+ super(message);
365
+ this.name = "LangSmithSandboxConnectTimeoutError";
366
+ }
367
+ }
354
368
  /**
355
369
  * Raised when the sandbox server is reloading (close code 1001).
356
370
  *
@@ -29,7 +29,7 @@
29
29
  * @packageDocumentation
30
30
  */
31
31
  Object.defineProperty(exports, "__esModule", { value: true });
32
- exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxServerReloadError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = exports.s3Mount = exports.mountConfig = exports.gitMount = exports.gcsMount = exports.workspaceSecret = exports.proxyConfig = exports.opaqueSecret = exports.gcpAuth = exports.awsAuth = exports.CommandHandle = exports.Sandbox = exports.SandboxClient = void 0;
32
+ exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxServerReloadError = exports.LangSmithSandboxConnectTimeoutError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = exports.s3Mount = exports.mountConfig = exports.gitMount = exports.gcsMount = exports.workspaceSecret = exports.proxyConfig = exports.opaqueSecret = exports.gcpAuth = exports.awsAuth = exports.CommandHandle = exports.Sandbox = exports.SandboxClient = void 0;
33
33
  // Main classes
34
34
  var client_js_1 = require("./client.cjs");
35
35
  Object.defineProperty(exports, "SandboxClient", { enumerable: true, get: function () { return client_js_1.SandboxClient; } });
@@ -55,6 +55,7 @@ Object.defineProperty(exports, "LangSmithSandboxError", { enumerable: true, get:
55
55
  Object.defineProperty(exports, "LangSmithSandboxAPIError", { enumerable: true, get: function () { return errors_js_1.LangSmithSandboxAPIError; } });
56
56
  Object.defineProperty(exports, "LangSmithSandboxAuthenticationError", { enumerable: true, get: function () { return errors_js_1.LangSmithSandboxAuthenticationError; } });
57
57
  Object.defineProperty(exports, "LangSmithSandboxConnectionError", { enumerable: true, get: function () { return errors_js_1.LangSmithSandboxConnectionError; } });
58
+ Object.defineProperty(exports, "LangSmithSandboxConnectTimeoutError", { enumerable: true, get: function () { return errors_js_1.LangSmithSandboxConnectTimeoutError; } });
58
59
  Object.defineProperty(exports, "LangSmithSandboxServerReloadError", { enumerable: true, get: function () { return errors_js_1.LangSmithSandboxServerReloadError; } });
59
60
  // Resource errors (type-based with resourceType attribute)
60
61
  Object.defineProperty(exports, "LangSmithResourceNotFoundError", { enumerable: true, get: function () { return errors_js_1.LangSmithResourceNotFoundError; } });
@@ -33,4 +33,4 @@ export { CommandHandle } from "./command_handle.js";
33
33
  export { awsAuth, gcpAuth, opaqueSecret, proxyConfig, workspaceSecret, } from "./proxy_config.js";
34
34
  export { gcsMount, gitMount, mountConfig, s3Mount } from "./mounts.js";
35
35
  export type { ExecutionResult, OutputChunk, WsMessage, WsRunOptions, ResourceStatus, Snapshot, SandboxData, SandboxClientConfig, RunOptions, CreateSandboxOptions, SandboxAccessControl, SandboxAwsAuthRule, SandboxAwsMountAuthConfig, SandboxGcpAuthRule, SandboxGcpMountAuthConfig, SandboxMountAuth, SandboxMountAuthConfig, SandboxMountConfig, SandboxProxyConfig, SandboxProxyRule, SandboxProxySecret, SandboxMount, MountCacheConfig, GCSMountConfig, GCSMountSpec, GitMountConfig, GitMountRefSpec, GitMountSpec, S3MountConfig, S3MountSpec, CreateSnapshotOptions, CreateDockerfileSnapshotOptions, CaptureSnapshotOptions, ListSnapshotsOptions, WaitForSnapshotOptions, StartSandboxOptions, UpdateSandboxOptions, WaitForSandboxOptions, } from "./types.js";
36
- export { LangSmithSandboxError, LangSmithSandboxAPIError, LangSmithSandboxAuthenticationError, LangSmithSandboxConnectionError, LangSmithSandboxServerReloadError, LangSmithResourceNotFoundError, LangSmithResourceTimeoutError, LangSmithResourceInUseError, LangSmithResourceAlreadyExistsError, LangSmithResourceNameConflictError, LangSmithValidationError, LangSmithQuotaExceededError, LangSmithResourceCreationError, LangSmithSandboxCreationError, LangSmithSandboxNotReadyError, LangSmithSandboxOperationError, LangSmithCommandTimeoutError, LangSmithDataplaneNotConfiguredError, } from "./errors.js";
36
+ export { LangSmithSandboxError, LangSmithSandboxAPIError, LangSmithSandboxAuthenticationError, LangSmithSandboxConnectionError, LangSmithSandboxConnectTimeoutError, LangSmithSandboxServerReloadError, LangSmithResourceNotFoundError, LangSmithResourceTimeoutError, LangSmithResourceInUseError, LangSmithResourceAlreadyExistsError, LangSmithResourceNameConflictError, LangSmithValidationError, LangSmithQuotaExceededError, LangSmithResourceCreationError, LangSmithSandboxCreationError, LangSmithSandboxNotReadyError, LangSmithSandboxOperationError, LangSmithCommandTimeoutError, LangSmithDataplaneNotConfiguredError, } from "./errors.js";
@@ -36,7 +36,7 @@ export { gcsMount, gitMount, mountConfig, s3Mount } from "./mounts.js";
36
36
  // Errors
37
37
  export {
38
38
  // Base and connection errors
39
- LangSmithSandboxError, LangSmithSandboxAPIError, LangSmithSandboxAuthenticationError, LangSmithSandboxConnectionError, LangSmithSandboxServerReloadError,
39
+ LangSmithSandboxError, LangSmithSandboxAPIError, LangSmithSandboxAuthenticationError, LangSmithSandboxConnectionError, LangSmithSandboxConnectTimeoutError, LangSmithSandboxServerReloadError,
40
40
  // Resource errors (type-based with resourceType attribute)
41
41
  LangSmithResourceNotFoundError, LangSmithResourceTimeoutError, LangSmithResourceInUseError, LangSmithResourceAlreadyExistsError, LangSmithResourceNameConflictError,
42
42
  // Validation and quota errors
@@ -199,8 +199,9 @@ class Sandbox {
199
199
  // wait=true with callbacks: drain stream and return result
200
200
  return handle.result;
201
201
  }
202
- // wait=true, no callbacks: try WS, fall back to HTTP
203
- try {
202
+ // wait=true, no callbacks: use WebSocket when the 'ws' package is
203
+ // available, otherwise the blocking HTTP endpoint. WS errors propagate.
204
+ if (await this._wsAvailable()) {
204
205
  const handle = await this._runWs(command, {
205
206
  ...restOptions,
206
207
  idleTimeout,
@@ -210,17 +211,14 @@ class Sandbox {
210
211
  });
211
212
  return await handle.result;
212
213
  }
213
- catch (e) {
214
- // Fall back to HTTP on connection errors or missing ws package
215
- const name = e != null && typeof e === "object" ? e.name : "";
216
- const message = e != null && typeof e === "object" ? (e.message ?? "") : "";
217
- if (name === "LangSmithSandboxConnectionError" ||
218
- name === "LangSmithSandboxServerReloadError" ||
219
- message.includes("'ws' package")) {
220
- return this._runHttp(command, restOptions);
221
- }
222
- throw e;
223
- }
214
+ return this._runHttp(command, restOptions);
215
+ }
216
+ /**
217
+ * Whether the optional `ws` package is importable (resolved once).
218
+ * @internal
219
+ */
220
+ _wsAvailable() {
221
+ return (0, ws_execute_js_1.isWsAvailable)();
224
222
  }
225
223
  /**
226
224
  * Execute a command via WebSocket streaming.
@@ -236,6 +234,7 @@ class Sandbox {
236
234
  // spawning a second one.
237
235
  const execCommandId = commandId ?? (0, uuid_js_1.uuid7)();
238
236
  let attempt = 0;
237
+ const deadline = (0, ws_execute_js_1.connectDeadline)();
239
238
  // eslint-disable-next-line no-constant-condition
240
239
  while (true) {
241
240
  const [stream, control] = await (0, ws_execute_js_1.runWsStream)(dataplaneUrl, this._client.getApiKey(), command, {
@@ -248,6 +247,7 @@ class Sandbox {
248
247
  killOnDisconnect,
249
248
  ttlSeconds,
250
249
  pty,
250
+ openTimeout: (0, ws_execute_js_1.openTimeoutFor)(deadline),
251
251
  ...(Object.keys(clientHeaders).length > 0
252
252
  ? { headers: clientHeaders }
253
253
  : {}),
@@ -261,16 +261,31 @@ class Sandbox {
261
261
  return handle;
262
262
  }
263
263
  catch (e) {
264
- // Idempotent re-issue (same command_id) after an early close.
265
- if (!(e instanceof errors_js_1.LangSmithStreamEndedBeforeStartedError)) {
264
+ // Idempotent re-issue (same command_id): neither an early close nor a
265
+ // failed connect can have started a second command.
266
+ if (!(e instanceof errors_js_1.LangSmithStreamEndedBeforeStartedError) &&
267
+ !(e instanceof errors_js_1.LangSmithSandboxConnectTimeoutError)) {
266
268
  throw e;
267
269
  }
268
270
  attempt++;
269
271
  if (attempt > command_handle_js_1.CommandHandle.MAX_AUTO_RECONNECTS) {
270
272
  throw e;
271
273
  }
272
- const delay = Math.min(command_handle_js_1.CommandHandle.BACKOFF_BASE * 2 ** (attempt - 1), command_handle_js_1.CommandHandle.BACKOFF_MAX);
274
+ let delay = Math.min(command_handle_js_1.CommandHandle.BACKOFF_BASE * 2 ** (attempt - 1), command_handle_js_1.CommandHandle.BACKOFF_MAX);
275
+ const remaining = (0, ws_execute_js_1.remainingBudget)(deadline);
276
+ if (remaining !== undefined) {
277
+ if (remaining <= 0) {
278
+ throw e;
279
+ }
280
+ delay = Math.min(delay, remaining);
281
+ }
273
282
  await new Promise((r) => setTimeout(r, delay * 1000));
283
+ // Never start an attempt with no budget left: openTimeoutFor would
284
+ // return 0, which ws forwards to Node as "no timeout at all".
285
+ const left = (0, ws_execute_js_1.remainingBudget)(deadline);
286
+ if (left !== undefined && left <= 0) {
287
+ throw e;
288
+ }
274
289
  }
275
290
  }
276
291
  }
@@ -428,8 +443,9 @@ class Sandbox {
428
443
  */
429
444
  async stop() {
430
445
  await this._client.stopSandbox(this.name);
446
+ // dataplane_url stays set: it is stable across stop/start and a request on
447
+ // it resumes the sandbox.
431
448
  this.status = "stopped";
432
- this.dataplane_url = undefined;
433
449
  }
434
450
  /**
435
451
  * Capture a snapshot from this sandbox.
@@ -78,7 +78,8 @@ export declare class Sandbox {
78
78
  * Execute a command in the sandbox.
79
79
  *
80
80
  * When `wait` is true (default) and no streaming callbacks are provided,
81
- * tries WebSocket first and falls back to HTTP POST.
81
+ * uses WebSocket, falling back to HTTP POST only when the optional `ws`
82
+ * package isn't installed.
82
83
  *
83
84
  * When `wait` is false or streaming callbacks are provided, uses WebSocket
84
85
  * (required). Returns a CommandHandle for streaming output.
@@ -2,10 +2,10 @@
2
2
  * Sandbox class for interacting with a specific sandbox instance.
3
3
  */
4
4
  import { uuid7 } from "../uuid.js";
5
- import { LangSmithDataplaneNotConfiguredError, LangSmithStreamEndedBeforeStartedError, } from "./errors.js";
5
+ import { LangSmithDataplaneNotConfiguredError, LangSmithSandboxConnectTimeoutError, LangSmithStreamEndedBeforeStartedError, } from "./errors.js";
6
6
  import { handleSandboxHttpError } from "./helpers.js";
7
7
  import { CommandHandle } from "./command_handle.js";
8
- import { reconnectWsStream, runWsStream } from "./ws_execute.js";
8
+ import { connectDeadline, isWsAvailable, openTimeoutFor, reconnectWsStream, remainingBudget, runWsStream, } from "./ws_execute.js";
9
9
  /**
10
10
  * Represents an active sandbox for running commands and file operations.
11
11
  *
@@ -196,8 +196,9 @@ export class Sandbox {
196
196
  // wait=true with callbacks: drain stream and return result
197
197
  return handle.result;
198
198
  }
199
- // wait=true, no callbacks: try WS, fall back to HTTP
200
- try {
199
+ // wait=true, no callbacks: use WebSocket when the 'ws' package is
200
+ // available, otherwise the blocking HTTP endpoint. WS errors propagate.
201
+ if (await this._wsAvailable()) {
201
202
  const handle = await this._runWs(command, {
202
203
  ...restOptions,
203
204
  idleTimeout,
@@ -207,17 +208,14 @@ export class Sandbox {
207
208
  });
208
209
  return await handle.result;
209
210
  }
210
- catch (e) {
211
- // Fall back to HTTP on connection errors or missing ws package
212
- const name = e != null && typeof e === "object" ? e.name : "";
213
- const message = e != null && typeof e === "object" ? (e.message ?? "") : "";
214
- if (name === "LangSmithSandboxConnectionError" ||
215
- name === "LangSmithSandboxServerReloadError" ||
216
- message.includes("'ws' package")) {
217
- return this._runHttp(command, restOptions);
218
- }
219
- throw e;
220
- }
211
+ return this._runHttp(command, restOptions);
212
+ }
213
+ /**
214
+ * Whether the optional `ws` package is importable (resolved once).
215
+ * @internal
216
+ */
217
+ _wsAvailable() {
218
+ return isWsAvailable();
221
219
  }
222
220
  /**
223
221
  * Execute a command via WebSocket streaming.
@@ -233,6 +231,7 @@ export class Sandbox {
233
231
  // spawning a second one.
234
232
  const execCommandId = commandId ?? uuid7();
235
233
  let attempt = 0;
234
+ const deadline = connectDeadline();
236
235
  // eslint-disable-next-line no-constant-condition
237
236
  while (true) {
238
237
  const [stream, control] = await runWsStream(dataplaneUrl, this._client.getApiKey(), command, {
@@ -245,6 +244,7 @@ export class Sandbox {
245
244
  killOnDisconnect,
246
245
  ttlSeconds,
247
246
  pty,
247
+ openTimeout: openTimeoutFor(deadline),
248
248
  ...(Object.keys(clientHeaders).length > 0
249
249
  ? { headers: clientHeaders }
250
250
  : {}),
@@ -258,16 +258,31 @@ export class Sandbox {
258
258
  return handle;
259
259
  }
260
260
  catch (e) {
261
- // Idempotent re-issue (same command_id) after an early close.
262
- if (!(e instanceof LangSmithStreamEndedBeforeStartedError)) {
261
+ // Idempotent re-issue (same command_id): neither an early close nor a
262
+ // failed connect can have started a second command.
263
+ if (!(e instanceof LangSmithStreamEndedBeforeStartedError) &&
264
+ !(e instanceof LangSmithSandboxConnectTimeoutError)) {
263
265
  throw e;
264
266
  }
265
267
  attempt++;
266
268
  if (attempt > CommandHandle.MAX_AUTO_RECONNECTS) {
267
269
  throw e;
268
270
  }
269
- const delay = Math.min(CommandHandle.BACKOFF_BASE * 2 ** (attempt - 1), CommandHandle.BACKOFF_MAX);
271
+ let delay = Math.min(CommandHandle.BACKOFF_BASE * 2 ** (attempt - 1), CommandHandle.BACKOFF_MAX);
272
+ const remaining = remainingBudget(deadline);
273
+ if (remaining !== undefined) {
274
+ if (remaining <= 0) {
275
+ throw e;
276
+ }
277
+ delay = Math.min(delay, remaining);
278
+ }
270
279
  await new Promise((r) => setTimeout(r, delay * 1000));
280
+ // Never start an attempt with no budget left: openTimeoutFor would
281
+ // return 0, which ws forwards to Node as "no timeout at all".
282
+ const left = remainingBudget(deadline);
283
+ if (left !== undefined && left <= 0) {
284
+ throw e;
285
+ }
271
286
  }
272
287
  }
273
288
  }
@@ -425,8 +440,9 @@ export class Sandbox {
425
440
  */
426
441
  async stop() {
427
442
  await this._client.stopSandbox(this.name);
443
+ // dataplane_url stays set: it is stable across stop/start and a request on
444
+ // it resumes the sandbox.
428
445
  this.status = "stopped";
429
- this.dataplane_url = undefined;
430
446
  }
431
447
  /**
432
448
  * Capture a snapshot from this sandbox.
@@ -6,22 +6,72 @@
6
6
  * Install with: npm install ws
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.WSStreamControl = void 0;
9
+ exports.WSStreamControl = exports.WS_CONNECT_BUDGET = exports.WS_OPEN_TIMEOUT = void 0;
10
+ exports.connectDeadline = connectDeadline;
11
+ exports.remainingBudget = remainingBudget;
12
+ exports.openTimeoutFor = openTimeoutFor;
13
+ exports.isWsAvailable = isWsAvailable;
10
14
  exports.buildWsUrl = buildWsUrl;
11
15
  exports.buildAuthHeaders = buildAuthHeaders;
12
16
  exports.raiseForWsError = raiseForWsError;
13
17
  exports.runWsStream = runWsStream;
14
18
  exports.reconnectWsStream = reconnectWsStream;
15
19
  const errors_js_1 = require("./errors.cjs");
16
- async function ensureWs() {
17
- try {
18
- const ws = await import("ws");
19
- return { WebSocket: ws.default || ws.WebSocket || ws };
20
+ const env_js_1 = require("../utils/env.cjs");
21
+ /** Read a WebSocket timeout override, in seconds. `<= 0` disables it. */
22
+ function envTimeout(name, defaultSeconds) {
23
+ const raw = (0, env_js_1.getLangSmithEnvironmentVariable)(name);
24
+ if (raw === undefined || raw.trim() === "")
25
+ return defaultSeconds;
26
+ const value = Number(raw);
27
+ if (!Number.isFinite(value)) {
28
+ console.warn(`Ignoring invalid LANGSMITH_${name}=${raw}, using ${defaultSeconds}`);
29
+ return defaultSeconds;
20
30
  }
21
- catch {
31
+ return value > 0 ? value : undefined;
32
+ }
33
+ // A slow cold start is recovered by the retry loop in run(), not by waiting here.
34
+ exports.WS_OPEN_TIMEOUT = envTimeout("SANDBOX_WS_TIMEOUT_OPEN", 30);
35
+ // Ceiling on the whole connect phase. Without it, retrying a blackholed
36
+ // handshake costs MAX_AUTO_RECONNECTS + 1 full open timeouts plus backoff.
37
+ exports.WS_CONNECT_BUDGET = envTimeout("SANDBOX_WS_TIMEOUT_CONNECT_BUDGET", 120);
38
+ function nowSeconds() {
39
+ return performance.now() / 1000;
40
+ }
41
+ /** Instant after which connect attempts must stop, if bounded. */
42
+ function connectDeadline() {
43
+ return exports.WS_CONNECT_BUDGET === undefined
44
+ ? undefined
45
+ : nowSeconds() + exports.WS_CONNECT_BUDGET;
46
+ }
47
+ /** Seconds left in the connect budget, or undefined when unbounded. */
48
+ function remainingBudget(deadline) {
49
+ return deadline === undefined ? undefined : deadline - nowSeconds();
50
+ }
51
+ /** Per-attempt open timeout, clamped so it cannot outlive `deadline`. */
52
+ function openTimeoutFor(deadline) {
53
+ if (deadline === undefined)
54
+ return exports.WS_OPEN_TIMEOUT;
55
+ const remaining = Math.max(deadline - nowSeconds(), 0);
56
+ return exports.WS_OPEN_TIMEOUT === undefined
57
+ ? remaining
58
+ : Math.min(exports.WS_OPEN_TIMEOUT, remaining);
59
+ }
60
+ // Load the optional 'ws' package once, at module load, instead of importing on
61
+ // every call. Resolves to the WebSocket constructor, or null if 'ws' isn't
62
+ // installed. run() reads isWsAvailable() to choose WS vs the HTTP fallback.
63
+ const wsWebSocketPromise = import("ws").then((ws) => ws.default || ws.WebSocket || ws, () => null);
64
+ /** Whether the optional `ws` package could be loaded (resolved once). */
65
+ async function isWsAvailable() {
66
+ return (await wsWebSocketPromise) !== null;
67
+ }
68
+ async function ensureWs() {
69
+ const WebSocket = await wsWebSocketPromise;
70
+ if (!WebSocket) {
22
71
  throw new Error("WebSocket-based execution requires the 'ws' package. " +
23
72
  "Install it with: npm install ws");
24
73
  }
74
+ return { WebSocket };
25
75
  }
26
76
  // =============================================================================
27
77
  // URL and Auth Helpers
@@ -133,18 +183,42 @@ function raiseForWsError(msg, commandId = "") {
133
183
  /**
134
184
  * Create a ws WebSocket connection and return a promise that resolves when open
135
185
  * or rejects on error.
186
+ *
187
+ * A socket-level failure rejects with the retryable connect-timeout error; a
188
+ * non-101 response rejects with the permanent connection error.
136
189
  */
137
- async function connectWs(url, headers) {
190
+ async function connectWs(url, headers, openTimeout = exports.WS_OPEN_TIMEOUT) {
138
191
  const { WebSocket: WS } = await ensureWs();
139
192
  return new Promise((resolve, reject) => {
140
- const ws = new WS(url, { headers });
193
+ const ws = new WS(url, {
194
+ headers,
195
+ handshakeTimeout: openTimeout === undefined ? undefined : openTimeout * 1000,
196
+ });
197
+ let settled = false;
141
198
  ws.on("open", () => {
142
- ws.removeAllListeners("error");
199
+ if (settled)
200
+ return;
201
+ settled = true;
143
202
  resolve(ws);
144
203
  });
204
+ // Attaching this suppresses ws's own "error" for a non-101 response, so the
205
+ // rejected upgrade cannot be misreported as a retryable socket failure. ws
206
+ // leaves cleanup to the handler once a listener is present.
207
+ ws.on("unexpected-response", (req, res) => {
208
+ res.resume?.();
209
+ req.destroy?.();
210
+ if (settled)
211
+ return;
212
+ settled = true;
213
+ reject(new errors_js_1.LangSmithSandboxConnectionError(`WebSocket upgrade to ${url} rejected by server (HTTP ${res.statusCode})`));
214
+ });
215
+ // Stays attached after settling so a late error has a listener and cannot
216
+ // become an unhandled 'error' event.
145
217
  ws.on("error", (err) => {
146
- ws.removeAllListeners("open");
147
- reject(new errors_js_1.LangSmithSandboxConnectionError(`Failed to connect to sandbox WebSocket: ${err.message}`));
218
+ if (settled)
219
+ return;
220
+ settled = true;
221
+ reject(new errors_js_1.LangSmithSandboxConnectTimeoutError(`Failed to connect to sandbox WebSocket: ${err.message}`));
148
222
  });
149
223
  });
150
224
  }
@@ -242,14 +316,14 @@ async function* readWsMessages(ws) {
242
316
  * data arrives in addition to yielding the messages.
243
317
  */
244
318
  async function runWsStream(dataplaneUrl, apiKey, command, options = {}) {
245
- const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout = 300, killOnDisconnect = false, ttlSeconds = 600, pty, headers: extraHeaders, } = options;
319
+ const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout = 300, killOnDisconnect = false, ttlSeconds = 600, pty, headers: extraHeaders, openTimeout = exports.WS_OPEN_TIMEOUT, } = options;
246
320
  const wsUrl = buildWsUrl(dataplaneUrl);
247
321
  const headers = buildAuthHeaders(apiKey, extraHeaders);
248
322
  const control = new WSStreamControl();
249
323
  async function* stream() {
250
324
  let ws;
251
325
  try {
252
- ws = await connectWs(wsUrl, headers);
326
+ ws = await connectWs(wsUrl, headers, openTimeout);
253
327
  control._bind(ws);
254
328
  // Send execute request
255
329
  const payload = {
@@ -5,7 +5,17 @@
5
5
  * Install with: npm install ws
6
6
  */
7
7
  import type { WsMessage, WsRunOptions } from "./types.js";
8
+ export declare const WS_OPEN_TIMEOUT: number | undefined;
9
+ export declare const WS_CONNECT_BUDGET: number | undefined;
10
+ /** Instant after which connect attempts must stop, if bounded. */
11
+ export declare function connectDeadline(): number | undefined;
12
+ /** Seconds left in the connect budget, or undefined when unbounded. */
13
+ export declare function remainingBudget(deadline: number | undefined): number | undefined;
14
+ /** Per-attempt open timeout, clamped so it cannot outlive `deadline`. */
15
+ export declare function openTimeoutFor(deadline: number | undefined): number | undefined;
8
16
  type WsWebSocket = any;
17
+ /** Whether the optional `ws` package could be loaded (resolved once). */
18
+ export declare function isWsAvailable(): Promise<boolean>;
9
19
  /**
10
20
  * Convert a dataplane HTTP URL to a WebSocket URL for /execute/ws.
11
21
  */
@@ -4,16 +4,62 @@
4
4
  * Uses the `ws` npm package (optional peer dependency).
5
5
  * Install with: npm install ws
6
6
  */
7
- import { LangSmithCommandTimeoutError, LangSmithSandboxConnectionError, LangSmithSandboxOperationError, LangSmithSandboxServerReloadError, } from "./errors.js";
8
- async function ensureWs() {
9
- try {
10
- const ws = await import("ws");
11
- return { WebSocket: ws.default || ws.WebSocket || ws };
7
+ import { LangSmithCommandTimeoutError, LangSmithSandboxConnectionError, LangSmithSandboxConnectTimeoutError, LangSmithSandboxOperationError, LangSmithSandboxServerReloadError, } from "./errors.js";
8
+ import { getLangSmithEnvironmentVariable } from "../utils/env.js";
9
+ /** Read a WebSocket timeout override, in seconds. `<= 0` disables it. */
10
+ function envTimeout(name, defaultSeconds) {
11
+ const raw = getLangSmithEnvironmentVariable(name);
12
+ if (raw === undefined || raw.trim() === "")
13
+ return defaultSeconds;
14
+ const value = Number(raw);
15
+ if (!Number.isFinite(value)) {
16
+ console.warn(`Ignoring invalid LANGSMITH_${name}=${raw}, using ${defaultSeconds}`);
17
+ return defaultSeconds;
12
18
  }
13
- catch {
19
+ return value > 0 ? value : undefined;
20
+ }
21
+ // A slow cold start is recovered by the retry loop in run(), not by waiting here.
22
+ export const WS_OPEN_TIMEOUT = envTimeout("SANDBOX_WS_TIMEOUT_OPEN", 30);
23
+ // Ceiling on the whole connect phase. Without it, retrying a blackholed
24
+ // handshake costs MAX_AUTO_RECONNECTS + 1 full open timeouts plus backoff.
25
+ export const WS_CONNECT_BUDGET = envTimeout("SANDBOX_WS_TIMEOUT_CONNECT_BUDGET", 120);
26
+ function nowSeconds() {
27
+ return performance.now() / 1000;
28
+ }
29
+ /** Instant after which connect attempts must stop, if bounded. */
30
+ export function connectDeadline() {
31
+ return WS_CONNECT_BUDGET === undefined
32
+ ? undefined
33
+ : nowSeconds() + WS_CONNECT_BUDGET;
34
+ }
35
+ /** Seconds left in the connect budget, or undefined when unbounded. */
36
+ export function remainingBudget(deadline) {
37
+ return deadline === undefined ? undefined : deadline - nowSeconds();
38
+ }
39
+ /** Per-attempt open timeout, clamped so it cannot outlive `deadline`. */
40
+ export function openTimeoutFor(deadline) {
41
+ if (deadline === undefined)
42
+ return WS_OPEN_TIMEOUT;
43
+ const remaining = Math.max(deadline - nowSeconds(), 0);
44
+ return WS_OPEN_TIMEOUT === undefined
45
+ ? remaining
46
+ : Math.min(WS_OPEN_TIMEOUT, remaining);
47
+ }
48
+ // Load the optional 'ws' package once, at module load, instead of importing on
49
+ // every call. Resolves to the WebSocket constructor, or null if 'ws' isn't
50
+ // installed. run() reads isWsAvailable() to choose WS vs the HTTP fallback.
51
+ const wsWebSocketPromise = import("ws").then((ws) => ws.default || ws.WebSocket || ws, () => null);
52
+ /** Whether the optional `ws` package could be loaded (resolved once). */
53
+ export async function isWsAvailable() {
54
+ return (await wsWebSocketPromise) !== null;
55
+ }
56
+ async function ensureWs() {
57
+ const WebSocket = await wsWebSocketPromise;
58
+ if (!WebSocket) {
14
59
  throw new Error("WebSocket-based execution requires the 'ws' package. " +
15
60
  "Install it with: npm install ws");
16
61
  }
62
+ return { WebSocket };
17
63
  }
18
64
  // =============================================================================
19
65
  // URL and Auth Helpers
@@ -124,18 +170,42 @@ export function raiseForWsError(msg, commandId = "") {
124
170
  /**
125
171
  * Create a ws WebSocket connection and return a promise that resolves when open
126
172
  * or rejects on error.
173
+ *
174
+ * A socket-level failure rejects with the retryable connect-timeout error; a
175
+ * non-101 response rejects with the permanent connection error.
127
176
  */
128
- async function connectWs(url, headers) {
177
+ async function connectWs(url, headers, openTimeout = WS_OPEN_TIMEOUT) {
129
178
  const { WebSocket: WS } = await ensureWs();
130
179
  return new Promise((resolve, reject) => {
131
- const ws = new WS(url, { headers });
180
+ const ws = new WS(url, {
181
+ headers,
182
+ handshakeTimeout: openTimeout === undefined ? undefined : openTimeout * 1000,
183
+ });
184
+ let settled = false;
132
185
  ws.on("open", () => {
133
- ws.removeAllListeners("error");
186
+ if (settled)
187
+ return;
188
+ settled = true;
134
189
  resolve(ws);
135
190
  });
191
+ // Attaching this suppresses ws's own "error" for a non-101 response, so the
192
+ // rejected upgrade cannot be misreported as a retryable socket failure. ws
193
+ // leaves cleanup to the handler once a listener is present.
194
+ ws.on("unexpected-response", (req, res) => {
195
+ res.resume?.();
196
+ req.destroy?.();
197
+ if (settled)
198
+ return;
199
+ settled = true;
200
+ reject(new LangSmithSandboxConnectionError(`WebSocket upgrade to ${url} rejected by server (HTTP ${res.statusCode})`));
201
+ });
202
+ // Stays attached after settling so a late error has a listener and cannot
203
+ // become an unhandled 'error' event.
136
204
  ws.on("error", (err) => {
137
- ws.removeAllListeners("open");
138
- reject(new LangSmithSandboxConnectionError(`Failed to connect to sandbox WebSocket: ${err.message}`));
205
+ if (settled)
206
+ return;
207
+ settled = true;
208
+ reject(new LangSmithSandboxConnectTimeoutError(`Failed to connect to sandbox WebSocket: ${err.message}`));
139
209
  });
140
210
  });
141
211
  }
@@ -233,14 +303,14 @@ async function* readWsMessages(ws) {
233
303
  * data arrives in addition to yielding the messages.
234
304
  */
235
305
  export async function runWsStream(dataplaneUrl, apiKey, command, options = {}) {
236
- const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout = 300, killOnDisconnect = false, ttlSeconds = 600, pty, headers: extraHeaders, } = options;
306
+ const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout = 300, killOnDisconnect = false, ttlSeconds = 600, pty, headers: extraHeaders, openTimeout = WS_OPEN_TIMEOUT, } = options;
237
307
  const wsUrl = buildWsUrl(dataplaneUrl);
238
308
  const headers = buildAuthHeaders(apiKey, extraHeaders);
239
309
  const control = new WSStreamControl();
240
310
  async function* stream() {
241
311
  let ws;
242
312
  try {
243
- ws = await connectWs(wsUrl, headers);
313
+ ws = await connectWs(wsUrl, headers, openTimeout);
244
314
  control._bind(ws);
245
315
  // Send execute request
246
316
  const payload = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.8.6",
3
+ "version": "0.8.8",
4
4
  "description": "Client library to connect to the LangSmith Observability and Evaluation Platform.",
5
5
  "packageManager": "pnpm@10.33.0",
6
6
  "files": [
@@ -1,4 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._MIN_BACKEND_VERSION = void 0;
4
- exports._MIN_BACKEND_VERSION = "0.16.14rc1";
@@ -1 +0,0 @@
1
- export declare const _MIN_BACKEND_VERSION = "0.16.14rc1";
@@ -1 +0,0 @@
1
- export const _MIN_BACKEND_VERSION = "0.16.14rc1";