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.
- package/dist/_openapi_client/client.cjs +8 -0
- package/dist/_openapi_client/client.d.ts +3 -0
- package/dist/_openapi_client/client.js +8 -0
- package/dist/_openapi_client/resources/annotation-queues/annotation-queues.cjs +147 -0
- package/dist/_openapi_client/resources/annotation-queues/annotation-queues.d.ts +372 -0
- package/dist/_openapi_client/resources/annotation-queues/annotation-queues.js +110 -0
- package/dist/_openapi_client/resources/annotation-queues/items.cjs +84 -0
- package/dist/_openapi_client/resources/annotation-queues/items.d.ts +207 -0
- package/dist/_openapi_client/resources/annotation-queues/items.js +80 -0
- package/dist/_openapi_client/resources/annotation-queues/runs.cjs +61 -0
- package/dist/_openapi_client/resources/annotation-queues/runs.d.ts +185 -0
- package/dist/_openapi_client/resources/annotation-queues/runs.js +57 -0
- package/dist/_openapi_client/resources/datasets/experiment-runs.cjs +1 -1
- package/dist/_openapi_client/resources/datasets/experiment-runs.js +1 -1
- package/dist/_openapi_client/resources/index.cjs +3 -1
- package/dist/_openapi_client/resources/index.d.ts +1 -0
- package/dist/_openapi_client/resources/index.js +1 -0
- package/dist/_openapi_client/resources/info.cjs +3 -1
- package/dist/_openapi_client/resources/info.d.ts +16 -22
- package/dist/_openapi_client/resources/info.js +3 -1
- package/dist/_openapi_client/resources/issues.cjs +2 -2
- package/dist/_openapi_client/resources/issues.js +2 -2
- package/dist/_openapi_client/resources/online-evaluators.cjs +7 -7
- package/dist/_openapi_client/resources/online-evaluators.d.ts +8 -0
- package/dist/_openapi_client/resources/online-evaluators.js +7 -7
- package/dist/_openapi_client/resources/public/runs.cjs +2 -2
- package/dist/_openapi_client/resources/public/runs.d.ts +1 -1
- package/dist/_openapi_client/resources/public/runs.js +2 -2
- package/dist/_openapi_client/resources/runs/runs.cjs +3 -3
- package/dist/_openapi_client/resources/runs/runs.d.ts +9 -4
- package/dist/_openapi_client/resources/runs/runs.js +3 -3
- package/dist/_openapi_client/resources/runs/share.cjs +2 -2
- package/dist/_openapi_client/resources/runs/share.js +2 -2
- package/dist/_openapi_client/resources/sandboxes/boxes.cjs +10 -10
- package/dist/_openapi_client/resources/sandboxes/boxes.d.ts +40 -0
- package/dist/_openapi_client/resources/sandboxes/boxes.js +10 -10
- package/dist/_openapi_client/resources/sandboxes/registries.cjs +5 -5
- package/dist/_openapi_client/resources/sandboxes/registries.js +5 -5
- package/dist/_openapi_client/resources/sandboxes/sandboxes.d.ts +17 -0
- package/dist/_openapi_client/resources/sandboxes/snapshots.cjs +4 -4
- package/dist/_openapi_client/resources/sandboxes/snapshots.d.ts +12 -0
- package/dist/_openapi_client/resources/sandboxes/snapshots.js +4 -4
- package/dist/_openapi_client/resources/threads.cjs +3 -3
- package/dist/_openapi_client/resources/threads.d.ts +2 -1
- package/dist/_openapi_client/resources/threads.js +3 -3
- package/dist/_openapi_client/resources/traces.cjs +2 -2
- package/dist/_openapi_client/resources/traces.d.ts +1 -1
- package/dist/_openapi_client/resources/traces.js +2 -2
- package/dist/client.cjs +57 -21
- package/dist/client.d.ts +58 -22
- package/dist/client.js +57 -21
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/sandbox/command_handle.cjs +4 -4
- package/dist/sandbox/command_handle.js +5 -5
- package/dist/sandbox/errors.cjs +16 -1
- package/dist/sandbox/errors.d.ts +11 -0
- package/dist/sandbox/errors.js +14 -0
- package/dist/sandbox/index.cjs +2 -1
- package/dist/sandbox/index.d.ts +1 -1
- package/dist/sandbox/index.js +1 -1
- package/dist/sandbox/sandbox.cjs +33 -17
- package/dist/sandbox/sandbox.d.ts +2 -1
- package/dist/sandbox/sandbox.js +35 -19
- package/dist/sandbox/ws_execute.cjs +87 -13
- package/dist/sandbox/ws_execute.d.ts +10 -0
- package/dist/sandbox/ws_execute.js +83 -13
- package/package.json +1 -1
- package/dist/utils/constants.cjs +0 -4
- package/dist/utils/constants.d.ts +0 -1
- package/dist/utils/constants.js +0 -1
package/dist/sandbox/errors.js
CHANGED
|
@@ -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
|
*
|
package/dist/sandbox/index.cjs
CHANGED
|
@@ -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; } });
|
package/dist/sandbox/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/sandbox/index.js
CHANGED
|
@@ -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
|
package/dist/sandbox/sandbox.cjs
CHANGED
|
@@ -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:
|
|
203
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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)
|
|
265
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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.
|
package/dist/sandbox/sandbox.js
CHANGED
|
@@ -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:
|
|
200
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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)
|
|
262
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
147
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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
package/dist/utils/constants.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const _MIN_BACKEND_VERSION = "0.16.14rc1";
|
package/dist/utils/constants.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const _MIN_BACKEND_VERSION = "0.16.14rc1";
|