langsmith 0.10.3 → 0.10.5
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/client.cjs +19 -4
- package/dist/client.d.ts +2 -1
- package/dist/client.js +19 -4
- package/dist/evaluation/_runner.cjs +47 -5
- package/dist/evaluation/_runner.d.ts +12 -0
- package/dist/evaluation/_runner.js +47 -5
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/sandbox/access_delegation.cjs +24 -0
- package/dist/sandbox/access_delegation.d.ts +15 -0
- package/dist/sandbox/access_delegation.js +21 -0
- package/dist/sandbox/client.cjs +70 -5
- package/dist/sandbox/client.d.ts +18 -0
- package/dist/sandbox/client.js +70 -5
- package/dist/sandbox/command_handle.cjs +34 -0
- package/dist/sandbox/command_handle.d.ts +10 -0
- package/dist/sandbox/command_handle.js +34 -0
- package/dist/sandbox/helpers.cjs +129 -0
- package/dist/sandbox/helpers.d.ts +29 -0
- package/dist/sandbox/helpers.js +123 -0
- package/dist/sandbox/index.cjs +7 -1
- package/dist/sandbox/index.d.ts +5 -1
- package/dist/sandbox/index.js +2 -0
- package/dist/sandbox/mounts.cjs +21 -3
- package/dist/sandbox/mounts.d.ts +7 -1
- package/dist/sandbox/mounts.js +21 -3
- package/dist/sandbox/proxy_config.cjs +41 -10
- package/dist/sandbox/proxy_config.d.ts +33 -6
- package/dist/sandbox/proxy_config.js +40 -10
- package/dist/sandbox/sandbox.cjs +160 -4
- package/dist/sandbox/sandbox.d.ts +71 -1
- package/dist/sandbox/sandbox.js +161 -5
- package/dist/sandbox/service_url.cjs +125 -0
- package/dist/sandbox/service_url.d.ts +50 -0
- package/dist/sandbox/service_url.js +120 -0
- package/dist/sandbox/types.d.ts +251 -8
- package/dist/sandbox/ws_execute.cjs +31 -1
- package/dist/sandbox/ws_execute.d.ts +9 -0
- package/dist/sandbox/ws_execute.js +31 -1
- package/dist/utils/error.cjs +17 -22
- package/dist/utils/error.js +17 -22
- package/dist/wrappers/openai_agents.cjs +37 -28
- package/dist/wrappers/openai_agents.d.ts +0 -1
- package/dist/wrappers/openai_agents.js +37 -28
- package/package.json +2 -2
package/dist/sandbox/sandbox.cjs
CHANGED
|
@@ -140,6 +140,26 @@ class Sandbox {
|
|
|
140
140
|
writable: true,
|
|
141
141
|
value: void 0
|
|
142
142
|
});
|
|
143
|
+
/**
|
|
144
|
+
* User, working directory and environment this sandbox's commands run
|
|
145
|
+
* with. Absent on sandboxes created before the server recorded it.
|
|
146
|
+
*/
|
|
147
|
+
Object.defineProperty(this, "run_config", {
|
|
148
|
+
enumerable: true,
|
|
149
|
+
configurable: true,
|
|
150
|
+
writable: true,
|
|
151
|
+
value: void 0
|
|
152
|
+
});
|
|
153
|
+
/**
|
|
154
|
+
* LangSmith access granted to code inside the sandbox, or undefined when it
|
|
155
|
+
* has none.
|
|
156
|
+
*/
|
|
157
|
+
Object.defineProperty(this, "access_delegation", {
|
|
158
|
+
enumerable: true,
|
|
159
|
+
configurable: true,
|
|
160
|
+
writable: true,
|
|
161
|
+
value: void 0
|
|
162
|
+
});
|
|
143
163
|
Object.defineProperty(this, "_client", {
|
|
144
164
|
enumerable: true,
|
|
145
165
|
configurable: true,
|
|
@@ -160,6 +180,8 @@ class Sandbox {
|
|
|
160
180
|
this.vCpus = data.vcpus;
|
|
161
181
|
this.mem_bytes = data.mem_bytes;
|
|
162
182
|
this.fs_capacity_bytes = data.fs_capacity_bytes;
|
|
183
|
+
this.run_config = data.run_config;
|
|
184
|
+
this.access_delegation = data.access_delegation;
|
|
163
185
|
this._client = client;
|
|
164
186
|
}
|
|
165
187
|
/**
|
|
@@ -180,8 +202,10 @@ class Sandbox {
|
|
|
180
202
|
return this.dataplane_url;
|
|
181
203
|
}
|
|
182
204
|
async run(command, options = {}) {
|
|
183
|
-
const { wait = true, onStdout, onStderr, idleTimeout, killOnDisconnect, ttlSeconds, pty, ...restOptions } = options;
|
|
205
|
+
const { wait = true, onStdout, onStderr, idleTimeout, killOnDisconnect, ttlSeconds, pty, closeInput, ...restOptions } = options;
|
|
184
206
|
const hasCallbacks = onStdout !== undefined || onStderr !== undefined;
|
|
207
|
+
(0, helpers_js_1.assertRunConfigNotCombined)(restOptions);
|
|
208
|
+
const closeStdin = (0, helpers_js_1.resolveCloseInput)(closeInput, pty ?? false);
|
|
185
209
|
if (!wait || hasCallbacks) {
|
|
186
210
|
// WebSocket required for streaming / non-blocking
|
|
187
211
|
const handle = await this._runWs(command, {
|
|
@@ -190,6 +214,7 @@ class Sandbox {
|
|
|
190
214
|
killOnDisconnect,
|
|
191
215
|
ttlSeconds,
|
|
192
216
|
pty,
|
|
217
|
+
closeInput: closeStdin,
|
|
193
218
|
onStdout,
|
|
194
219
|
onStderr,
|
|
195
220
|
});
|
|
@@ -208,6 +233,7 @@ class Sandbox {
|
|
|
208
233
|
killOnDisconnect,
|
|
209
234
|
ttlSeconds,
|
|
210
235
|
pty,
|
|
236
|
+
closeInput: closeStdin,
|
|
211
237
|
});
|
|
212
238
|
return await handle.result;
|
|
213
239
|
}
|
|
@@ -225,7 +251,7 @@ class Sandbox {
|
|
|
225
251
|
* @internal
|
|
226
252
|
*/
|
|
227
253
|
async _runWs(command, options = {}) {
|
|
228
|
-
const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout, killOnDisconnect, ttlSeconds, pty, } = options;
|
|
254
|
+
const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout, killOnDisconnect, ttlSeconds, pty, runConfig, closeInput: closeStdin = false, } = options;
|
|
229
255
|
const dataplaneUrl = this.requireDataplaneUrl();
|
|
230
256
|
const clientHeaders = this._client.getDefaultHeaders();
|
|
231
257
|
// A client-supplied command_id makes execute idempotent: the daemon does
|
|
@@ -247,6 +273,8 @@ class Sandbox {
|
|
|
247
273
|
killOnDisconnect,
|
|
248
274
|
ttlSeconds,
|
|
249
275
|
pty,
|
|
276
|
+
runConfig,
|
|
277
|
+
closeStdin,
|
|
250
278
|
openTimeout: (0, ws_execute_js_1.openTimeoutFor)(deadline),
|
|
251
279
|
...(Object.keys(clientHeaders).length > 0
|
|
252
280
|
? { headers: clientHeaders }
|
|
@@ -255,6 +283,8 @@ class Sandbox {
|
|
|
255
283
|
const handle = new command_handle_js_1.CommandHandle(stream, control, this, {
|
|
256
284
|
onStdout,
|
|
257
285
|
onStderr,
|
|
286
|
+
stdinClosed: closeStdin,
|
|
287
|
+
pty: pty ?? false,
|
|
258
288
|
});
|
|
259
289
|
try {
|
|
260
290
|
await handle._ensureStarted();
|
|
@@ -297,7 +327,7 @@ class Sandbox {
|
|
|
297
327
|
* @internal
|
|
298
328
|
*/
|
|
299
329
|
async _runHttp(command, options = {}) {
|
|
300
|
-
const { timeout = 60, env, cwd, shell = "/bin/bash" } = options;
|
|
330
|
+
const { timeout = 60, env, cwd, runConfig, shell = "/bin/bash" } = options;
|
|
301
331
|
const dataplaneUrl = this.requireDataplaneUrl();
|
|
302
332
|
const url = `${dataplaneUrl}/execute`;
|
|
303
333
|
const payload = {
|
|
@@ -308,6 +338,9 @@ class Sandbox {
|
|
|
308
338
|
if (env !== undefined) {
|
|
309
339
|
payload.env = env;
|
|
310
340
|
}
|
|
341
|
+
if (runConfig !== undefined) {
|
|
342
|
+
payload.run_config = runConfig;
|
|
343
|
+
}
|
|
311
344
|
if (cwd !== undefined) {
|
|
312
345
|
payload.cwd = cwd;
|
|
313
346
|
}
|
|
@@ -339,7 +372,7 @@ class Sandbox {
|
|
|
339
372
|
* @returns A new CommandHandle.
|
|
340
373
|
*/
|
|
341
374
|
async reconnect(commandId, options = {}) {
|
|
342
|
-
const { stdoutOffset = 0, stderrOffset = 0 } = options;
|
|
375
|
+
const { stdoutOffset = 0, stderrOffset = 0, stdinClosed = false, pty = false, } = options;
|
|
343
376
|
const dataplaneUrl = this.requireDataplaneUrl();
|
|
344
377
|
const clientHeaders = this._client.getDefaultHeaders();
|
|
345
378
|
const [stream, control] = await (0, ws_execute_js_1.reconnectWsStream)(dataplaneUrl, this._client.getApiKey(), commandId, {
|
|
@@ -353,6 +386,8 @@ class Sandbox {
|
|
|
353
386
|
commandId,
|
|
354
387
|
stdoutOffset,
|
|
355
388
|
stderrOffset,
|
|
389
|
+
stdinClosed,
|
|
390
|
+
pty,
|
|
356
391
|
});
|
|
357
392
|
}
|
|
358
393
|
/**
|
|
@@ -413,6 +448,127 @@ class Sandbox {
|
|
|
413
448
|
const buffer = await response.arrayBuffer();
|
|
414
449
|
return new Uint8Array(buffer);
|
|
415
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* Report a file's size and validators without transferring it.
|
|
453
|
+
*
|
|
454
|
+
* @param path - File path to stat.
|
|
455
|
+
* @param timeout - Request timeout in seconds.
|
|
456
|
+
* @returns The size and the `ETag` to pass to {@link readRange}.
|
|
457
|
+
*/
|
|
458
|
+
async stat(path, timeout = 60) {
|
|
459
|
+
const dataplaneUrl = this.requireDataplaneUrl();
|
|
460
|
+
const url = `${dataplaneUrl}/download?path=${encodeURIComponent(path)}`;
|
|
461
|
+
const response = await this._client._fetch(url, {
|
|
462
|
+
method: "HEAD",
|
|
463
|
+
signal: AbortSignal.timeout(timeout * 1000),
|
|
464
|
+
});
|
|
465
|
+
if (!response.ok) {
|
|
466
|
+
await (0, helpers_js_1.handleFileHttpError)(response, path, this.name);
|
|
467
|
+
}
|
|
468
|
+
return (0, helpers_js_1.fileStatFromResponse)(response);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Read part of a file, for chunked reads and resumed downloads.
|
|
472
|
+
*
|
|
473
|
+
* @param path - File path to read.
|
|
474
|
+
* @param options - The byte range, plus optional `ifRange`/`ifNoneMatch`
|
|
475
|
+
* validators. A 200 answer to a ranged request means the file changed and
|
|
476
|
+
* the server sent it whole -- restart rather than append.
|
|
477
|
+
* @returns The bytes and where they sit in the file.
|
|
478
|
+
*
|
|
479
|
+
* @example
|
|
480
|
+
* ```typescript
|
|
481
|
+
* const head = await sandbox.readRange("/big.bin", { start: 0, end: 1023 });
|
|
482
|
+
* const next = await sandbox.readRange("/big.bin", {
|
|
483
|
+
* start: head.end ?? 1024,
|
|
484
|
+
* ifRange: head.etag,
|
|
485
|
+
* });
|
|
486
|
+
* ```
|
|
487
|
+
*/
|
|
488
|
+
async readRange(path, options) {
|
|
489
|
+
const { ifRange, ifNoneMatch, timeout = 60, headers } = options;
|
|
490
|
+
const dataplaneUrl = this.requireDataplaneUrl();
|
|
491
|
+
const url = `${dataplaneUrl}/download?path=${encodeURIComponent(path)}`;
|
|
492
|
+
const requestHeaders = {
|
|
493
|
+
...(headers ?? {}),
|
|
494
|
+
Range: (0, helpers_js_1.buildRangeHeader)(options),
|
|
495
|
+
};
|
|
496
|
+
if (ifRange)
|
|
497
|
+
requestHeaders["If-Range"] = ifRange;
|
|
498
|
+
if (ifNoneMatch)
|
|
499
|
+
requestHeaders["If-None-Match"] = ifNoneMatch;
|
|
500
|
+
const response = await this._client._fetch(url, {
|
|
501
|
+
method: "GET",
|
|
502
|
+
headers: requestHeaders,
|
|
503
|
+
signal: AbortSignal.timeout(timeout * 1000),
|
|
504
|
+
});
|
|
505
|
+
if (!response.ok && response.status !== 304) {
|
|
506
|
+
await (0, helpers_js_1.handleFileHttpError)(response, path, this.name);
|
|
507
|
+
}
|
|
508
|
+
return (0, helpers_js_1.fileChunkFromResponse)(response);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Find files and directories matching a pattern.
|
|
512
|
+
*
|
|
513
|
+
* @param pattern - Match against each entry's path relative to `path`.
|
|
514
|
+
* Supports `**` for any number of segments plus `*`, `?` and `[...]`
|
|
515
|
+
* within one segment.
|
|
516
|
+
* @param path - Absolute path of the directory to search under.
|
|
517
|
+
* @param options - Result cap and request options.
|
|
518
|
+
* @returns The matches; check `truncated` before treating them as complete.
|
|
519
|
+
*/
|
|
520
|
+
async glob(pattern, path, options = {}) {
|
|
521
|
+
const body = { pattern, path };
|
|
522
|
+
if (options.limit !== undefined)
|
|
523
|
+
body.limit = options.limit;
|
|
524
|
+
return (await this._fileSearch("glob", body, options));
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* List a directory's immediate entries, without recursing.
|
|
528
|
+
*
|
|
529
|
+
* A convenience over {@link glob} with the pattern `*`.
|
|
530
|
+
*
|
|
531
|
+
* @param path - Absolute path of the directory to list.
|
|
532
|
+
* @param options - Result cap and request options.
|
|
533
|
+
* @returns The directory's files and subdirectories.
|
|
534
|
+
*/
|
|
535
|
+
async ls(path, options = {}) {
|
|
536
|
+
return this.glob("*", path, options);
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Search file contents for a literal string.
|
|
540
|
+
*
|
|
541
|
+
* @param pattern - Literal text to search for. Not a regular expression.
|
|
542
|
+
* @param path - Absolute path of the directory to search under.
|
|
543
|
+
* @param options - Optional `glob` file filter, result cap, request options.
|
|
544
|
+
* @returns The matching lines; check `truncated` for completeness.
|
|
545
|
+
*/
|
|
546
|
+
async grep(pattern, path, options = {}) {
|
|
547
|
+
const body = { pattern, path };
|
|
548
|
+
if (options.glob !== undefined)
|
|
549
|
+
body.glob = options.glob;
|
|
550
|
+
if (options.limit !== undefined)
|
|
551
|
+
body.limit = options.limit;
|
|
552
|
+
return (await this._fileSearch("grep", body, options));
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* POST one of the read-only filesystem search endpoints.
|
|
556
|
+
* @internal
|
|
557
|
+
*/
|
|
558
|
+
async _fileSearch(operation, body, options) {
|
|
559
|
+
const { timeout = 60, headers } = options;
|
|
560
|
+
const dataplaneUrl = this.requireDataplaneUrl();
|
|
561
|
+
const response = await this._client._fetch(`${dataplaneUrl}/${operation}`, {
|
|
562
|
+
method: "POST",
|
|
563
|
+
headers: { "Content-Type": "application/json", ...(headers ?? {}) },
|
|
564
|
+
body: JSON.stringify(body),
|
|
565
|
+
signal: AbortSignal.timeout(timeout * 1000),
|
|
566
|
+
});
|
|
567
|
+
if (!response.ok) {
|
|
568
|
+
await (0, helpers_js_1.handleFileHttpError)(response, String(body.path), this.name);
|
|
569
|
+
}
|
|
570
|
+
return response.json();
|
|
571
|
+
}
|
|
416
572
|
/**
|
|
417
573
|
* Create a link that downloads one file from this sandbox.
|
|
418
574
|
*
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sandbox class for interacting with a specific sandbox instance.
|
|
3
3
|
*/
|
|
4
|
-
import type {
|
|
4
|
+
import type { AccessDelegation } from "./access_delegation.js";
|
|
5
|
+
import type { CaptureSnapshotOptions, DownloadURL, ExecutionResult, FileChunk, FileStat, GenerateDownloadURLOptions, GlobOptions, GlobResult, GrepOptions, GrepResult, ReadRangeOptions, RunConfig, RunOptions, Snapshot, StartSandboxOptions } from "./types.js";
|
|
5
6
|
import { CommandHandle } from "./command_handle.js";
|
|
6
7
|
/**
|
|
7
8
|
* Represents an active sandbox for running commands and file operations.
|
|
@@ -62,6 +63,16 @@ export declare class Sandbox {
|
|
|
62
63
|
readonly mem_bytes?: number;
|
|
63
64
|
/** Root filesystem capacity in bytes. */
|
|
64
65
|
readonly fs_capacity_bytes?: number;
|
|
66
|
+
/**
|
|
67
|
+
* User, working directory and environment this sandbox's commands run
|
|
68
|
+
* with. Absent on sandboxes created before the server recorded it.
|
|
69
|
+
*/
|
|
70
|
+
readonly run_config?: RunConfig;
|
|
71
|
+
/**
|
|
72
|
+
* LangSmith access granted to code inside the sandbox, or undefined when it
|
|
73
|
+
* has none.
|
|
74
|
+
*/
|
|
75
|
+
readonly access_delegation?: AccessDelegation;
|
|
65
76
|
private _client;
|
|
66
77
|
/**
|
|
67
78
|
* Return the dataplane URL.
|
|
@@ -126,6 +137,8 @@ export declare class Sandbox {
|
|
|
126
137
|
reconnect(commandId: string, options?: {
|
|
127
138
|
stdoutOffset?: number;
|
|
128
139
|
stderrOffset?: number;
|
|
140
|
+
stdinClosed?: boolean;
|
|
141
|
+
pty?: boolean;
|
|
129
142
|
}): Promise<CommandHandle>;
|
|
130
143
|
/**
|
|
131
144
|
* Write content to a file in the sandbox.
|
|
@@ -155,6 +168,63 @@ export declare class Sandbox {
|
|
|
155
168
|
* ```
|
|
156
169
|
*/
|
|
157
170
|
read(path: string, timeout?: number): Promise<Uint8Array>;
|
|
171
|
+
/**
|
|
172
|
+
* Report a file's size and validators without transferring it.
|
|
173
|
+
*
|
|
174
|
+
* @param path - File path to stat.
|
|
175
|
+
* @param timeout - Request timeout in seconds.
|
|
176
|
+
* @returns The size and the `ETag` to pass to {@link readRange}.
|
|
177
|
+
*/
|
|
178
|
+
stat(path: string, timeout?: number): Promise<FileStat>;
|
|
179
|
+
/**
|
|
180
|
+
* Read part of a file, for chunked reads and resumed downloads.
|
|
181
|
+
*
|
|
182
|
+
* @param path - File path to read.
|
|
183
|
+
* @param options - The byte range, plus optional `ifRange`/`ifNoneMatch`
|
|
184
|
+
* validators. A 200 answer to a ranged request means the file changed and
|
|
185
|
+
* the server sent it whole -- restart rather than append.
|
|
186
|
+
* @returns The bytes and where they sit in the file.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```typescript
|
|
190
|
+
* const head = await sandbox.readRange("/big.bin", { start: 0, end: 1023 });
|
|
191
|
+
* const next = await sandbox.readRange("/big.bin", {
|
|
192
|
+
* start: head.end ?? 1024,
|
|
193
|
+
* ifRange: head.etag,
|
|
194
|
+
* });
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
readRange(path: string, options: ReadRangeOptions): Promise<FileChunk>;
|
|
198
|
+
/**
|
|
199
|
+
* Find files and directories matching a pattern.
|
|
200
|
+
*
|
|
201
|
+
* @param pattern - Match against each entry's path relative to `path`.
|
|
202
|
+
* Supports `**` for any number of segments plus `*`, `?` and `[...]`
|
|
203
|
+
* within one segment.
|
|
204
|
+
* @param path - Absolute path of the directory to search under.
|
|
205
|
+
* @param options - Result cap and request options.
|
|
206
|
+
* @returns The matches; check `truncated` before treating them as complete.
|
|
207
|
+
*/
|
|
208
|
+
glob(pattern: string, path: string, options?: GlobOptions): Promise<GlobResult>;
|
|
209
|
+
/**
|
|
210
|
+
* List a directory's immediate entries, without recursing.
|
|
211
|
+
*
|
|
212
|
+
* A convenience over {@link glob} with the pattern `*`.
|
|
213
|
+
*
|
|
214
|
+
* @param path - Absolute path of the directory to list.
|
|
215
|
+
* @param options - Result cap and request options.
|
|
216
|
+
* @returns The directory's files and subdirectories.
|
|
217
|
+
*/
|
|
218
|
+
ls(path: string, options?: GlobOptions): Promise<GlobResult>;
|
|
219
|
+
/**
|
|
220
|
+
* Search file contents for a literal string.
|
|
221
|
+
*
|
|
222
|
+
* @param pattern - Literal text to search for. Not a regular expression.
|
|
223
|
+
* @param path - Absolute path of the directory to search under.
|
|
224
|
+
* @param options - Optional `glob` file filter, result cap, request options.
|
|
225
|
+
* @returns The matching lines; check `truncated` for completeness.
|
|
226
|
+
*/
|
|
227
|
+
grep(pattern: string, path: string, options?: GrepOptions): Promise<GrepResult>;
|
|
158
228
|
/**
|
|
159
229
|
* Create a link that downloads one file from this sandbox.
|
|
160
230
|
*
|
package/dist/sandbox/sandbox.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { uuid7 } from "../uuid.js";
|
|
5
5
|
import { LangSmithDataplaneNotConfiguredError, LangSmithSandboxRetryableConnectionError, LangSmithStreamEndedBeforeStartedError, } from "./errors.js";
|
|
6
|
-
import { handleSandboxHttpError } from "./helpers.js";
|
|
6
|
+
import { assertRunConfigNotCombined, buildRangeHeader, fileChunkFromResponse, fileStatFromResponse, handleFileHttpError, handleSandboxHttpError, resolveCloseInput, } from "./helpers.js";
|
|
7
7
|
import { CommandHandle } from "./command_handle.js";
|
|
8
8
|
import { connectDeadline, isWsAvailable, openTimeoutFor, reconnectWsStream, remainingBudget, runWsStream, } from "./ws_execute.js";
|
|
9
9
|
/**
|
|
@@ -137,6 +137,26 @@ export class Sandbox {
|
|
|
137
137
|
writable: true,
|
|
138
138
|
value: void 0
|
|
139
139
|
});
|
|
140
|
+
/**
|
|
141
|
+
* User, working directory and environment this sandbox's commands run
|
|
142
|
+
* with. Absent on sandboxes created before the server recorded it.
|
|
143
|
+
*/
|
|
144
|
+
Object.defineProperty(this, "run_config", {
|
|
145
|
+
enumerable: true,
|
|
146
|
+
configurable: true,
|
|
147
|
+
writable: true,
|
|
148
|
+
value: void 0
|
|
149
|
+
});
|
|
150
|
+
/**
|
|
151
|
+
* LangSmith access granted to code inside the sandbox, or undefined when it
|
|
152
|
+
* has none.
|
|
153
|
+
*/
|
|
154
|
+
Object.defineProperty(this, "access_delegation", {
|
|
155
|
+
enumerable: true,
|
|
156
|
+
configurable: true,
|
|
157
|
+
writable: true,
|
|
158
|
+
value: void 0
|
|
159
|
+
});
|
|
140
160
|
Object.defineProperty(this, "_client", {
|
|
141
161
|
enumerable: true,
|
|
142
162
|
configurable: true,
|
|
@@ -157,6 +177,8 @@ export class Sandbox {
|
|
|
157
177
|
this.vCpus = data.vcpus;
|
|
158
178
|
this.mem_bytes = data.mem_bytes;
|
|
159
179
|
this.fs_capacity_bytes = data.fs_capacity_bytes;
|
|
180
|
+
this.run_config = data.run_config;
|
|
181
|
+
this.access_delegation = data.access_delegation;
|
|
160
182
|
this._client = client;
|
|
161
183
|
}
|
|
162
184
|
/**
|
|
@@ -177,8 +199,10 @@ export class Sandbox {
|
|
|
177
199
|
return this.dataplane_url;
|
|
178
200
|
}
|
|
179
201
|
async run(command, options = {}) {
|
|
180
|
-
const { wait = true, onStdout, onStderr, idleTimeout, killOnDisconnect, ttlSeconds, pty, ...restOptions } = options;
|
|
202
|
+
const { wait = true, onStdout, onStderr, idleTimeout, killOnDisconnect, ttlSeconds, pty, closeInput, ...restOptions } = options;
|
|
181
203
|
const hasCallbacks = onStdout !== undefined || onStderr !== undefined;
|
|
204
|
+
assertRunConfigNotCombined(restOptions);
|
|
205
|
+
const closeStdin = resolveCloseInput(closeInput, pty ?? false);
|
|
182
206
|
if (!wait || hasCallbacks) {
|
|
183
207
|
// WebSocket required for streaming / non-blocking
|
|
184
208
|
const handle = await this._runWs(command, {
|
|
@@ -187,6 +211,7 @@ export class Sandbox {
|
|
|
187
211
|
killOnDisconnect,
|
|
188
212
|
ttlSeconds,
|
|
189
213
|
pty,
|
|
214
|
+
closeInput: closeStdin,
|
|
190
215
|
onStdout,
|
|
191
216
|
onStderr,
|
|
192
217
|
});
|
|
@@ -205,6 +230,7 @@ export class Sandbox {
|
|
|
205
230
|
killOnDisconnect,
|
|
206
231
|
ttlSeconds,
|
|
207
232
|
pty,
|
|
233
|
+
closeInput: closeStdin,
|
|
208
234
|
});
|
|
209
235
|
return await handle.result;
|
|
210
236
|
}
|
|
@@ -222,7 +248,7 @@ export class Sandbox {
|
|
|
222
248
|
* @internal
|
|
223
249
|
*/
|
|
224
250
|
async _runWs(command, options = {}) {
|
|
225
|
-
const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout, killOnDisconnect, ttlSeconds, pty, } = options;
|
|
251
|
+
const { timeout = 60, env, cwd, shell = "/bin/bash", onStdout, onStderr, commandId, idleTimeout, killOnDisconnect, ttlSeconds, pty, runConfig, closeInput: closeStdin = false, } = options;
|
|
226
252
|
const dataplaneUrl = this.requireDataplaneUrl();
|
|
227
253
|
const clientHeaders = this._client.getDefaultHeaders();
|
|
228
254
|
// A client-supplied command_id makes execute idempotent: the daemon does
|
|
@@ -244,6 +270,8 @@ export class Sandbox {
|
|
|
244
270
|
killOnDisconnect,
|
|
245
271
|
ttlSeconds,
|
|
246
272
|
pty,
|
|
273
|
+
runConfig,
|
|
274
|
+
closeStdin,
|
|
247
275
|
openTimeout: openTimeoutFor(deadline),
|
|
248
276
|
...(Object.keys(clientHeaders).length > 0
|
|
249
277
|
? { headers: clientHeaders }
|
|
@@ -252,6 +280,8 @@ export class Sandbox {
|
|
|
252
280
|
const handle = new CommandHandle(stream, control, this, {
|
|
253
281
|
onStdout,
|
|
254
282
|
onStderr,
|
|
283
|
+
stdinClosed: closeStdin,
|
|
284
|
+
pty: pty ?? false,
|
|
255
285
|
});
|
|
256
286
|
try {
|
|
257
287
|
await handle._ensureStarted();
|
|
@@ -294,7 +324,7 @@ export class Sandbox {
|
|
|
294
324
|
* @internal
|
|
295
325
|
*/
|
|
296
326
|
async _runHttp(command, options = {}) {
|
|
297
|
-
const { timeout = 60, env, cwd, shell = "/bin/bash" } = options;
|
|
327
|
+
const { timeout = 60, env, cwd, runConfig, shell = "/bin/bash" } = options;
|
|
298
328
|
const dataplaneUrl = this.requireDataplaneUrl();
|
|
299
329
|
const url = `${dataplaneUrl}/execute`;
|
|
300
330
|
const payload = {
|
|
@@ -305,6 +335,9 @@ export class Sandbox {
|
|
|
305
335
|
if (env !== undefined) {
|
|
306
336
|
payload.env = env;
|
|
307
337
|
}
|
|
338
|
+
if (runConfig !== undefined) {
|
|
339
|
+
payload.run_config = runConfig;
|
|
340
|
+
}
|
|
308
341
|
if (cwd !== undefined) {
|
|
309
342
|
payload.cwd = cwd;
|
|
310
343
|
}
|
|
@@ -336,7 +369,7 @@ export class Sandbox {
|
|
|
336
369
|
* @returns A new CommandHandle.
|
|
337
370
|
*/
|
|
338
371
|
async reconnect(commandId, options = {}) {
|
|
339
|
-
const { stdoutOffset = 0, stderrOffset = 0 } = options;
|
|
372
|
+
const { stdoutOffset = 0, stderrOffset = 0, stdinClosed = false, pty = false, } = options;
|
|
340
373
|
const dataplaneUrl = this.requireDataplaneUrl();
|
|
341
374
|
const clientHeaders = this._client.getDefaultHeaders();
|
|
342
375
|
const [stream, control] = await reconnectWsStream(dataplaneUrl, this._client.getApiKey(), commandId, {
|
|
@@ -350,6 +383,8 @@ export class Sandbox {
|
|
|
350
383
|
commandId,
|
|
351
384
|
stdoutOffset,
|
|
352
385
|
stderrOffset,
|
|
386
|
+
stdinClosed,
|
|
387
|
+
pty,
|
|
353
388
|
});
|
|
354
389
|
}
|
|
355
390
|
/**
|
|
@@ -410,6 +445,127 @@ export class Sandbox {
|
|
|
410
445
|
const buffer = await response.arrayBuffer();
|
|
411
446
|
return new Uint8Array(buffer);
|
|
412
447
|
}
|
|
448
|
+
/**
|
|
449
|
+
* Report a file's size and validators without transferring it.
|
|
450
|
+
*
|
|
451
|
+
* @param path - File path to stat.
|
|
452
|
+
* @param timeout - Request timeout in seconds.
|
|
453
|
+
* @returns The size and the `ETag` to pass to {@link readRange}.
|
|
454
|
+
*/
|
|
455
|
+
async stat(path, timeout = 60) {
|
|
456
|
+
const dataplaneUrl = this.requireDataplaneUrl();
|
|
457
|
+
const url = `${dataplaneUrl}/download?path=${encodeURIComponent(path)}`;
|
|
458
|
+
const response = await this._client._fetch(url, {
|
|
459
|
+
method: "HEAD",
|
|
460
|
+
signal: AbortSignal.timeout(timeout * 1000),
|
|
461
|
+
});
|
|
462
|
+
if (!response.ok) {
|
|
463
|
+
await handleFileHttpError(response, path, this.name);
|
|
464
|
+
}
|
|
465
|
+
return fileStatFromResponse(response);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Read part of a file, for chunked reads and resumed downloads.
|
|
469
|
+
*
|
|
470
|
+
* @param path - File path to read.
|
|
471
|
+
* @param options - The byte range, plus optional `ifRange`/`ifNoneMatch`
|
|
472
|
+
* validators. A 200 answer to a ranged request means the file changed and
|
|
473
|
+
* the server sent it whole -- restart rather than append.
|
|
474
|
+
* @returns The bytes and where they sit in the file.
|
|
475
|
+
*
|
|
476
|
+
* @example
|
|
477
|
+
* ```typescript
|
|
478
|
+
* const head = await sandbox.readRange("/big.bin", { start: 0, end: 1023 });
|
|
479
|
+
* const next = await sandbox.readRange("/big.bin", {
|
|
480
|
+
* start: head.end ?? 1024,
|
|
481
|
+
* ifRange: head.etag,
|
|
482
|
+
* });
|
|
483
|
+
* ```
|
|
484
|
+
*/
|
|
485
|
+
async readRange(path, options) {
|
|
486
|
+
const { ifRange, ifNoneMatch, timeout = 60, headers } = options;
|
|
487
|
+
const dataplaneUrl = this.requireDataplaneUrl();
|
|
488
|
+
const url = `${dataplaneUrl}/download?path=${encodeURIComponent(path)}`;
|
|
489
|
+
const requestHeaders = {
|
|
490
|
+
...(headers ?? {}),
|
|
491
|
+
Range: buildRangeHeader(options),
|
|
492
|
+
};
|
|
493
|
+
if (ifRange)
|
|
494
|
+
requestHeaders["If-Range"] = ifRange;
|
|
495
|
+
if (ifNoneMatch)
|
|
496
|
+
requestHeaders["If-None-Match"] = ifNoneMatch;
|
|
497
|
+
const response = await this._client._fetch(url, {
|
|
498
|
+
method: "GET",
|
|
499
|
+
headers: requestHeaders,
|
|
500
|
+
signal: AbortSignal.timeout(timeout * 1000),
|
|
501
|
+
});
|
|
502
|
+
if (!response.ok && response.status !== 304) {
|
|
503
|
+
await handleFileHttpError(response, path, this.name);
|
|
504
|
+
}
|
|
505
|
+
return fileChunkFromResponse(response);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Find files and directories matching a pattern.
|
|
509
|
+
*
|
|
510
|
+
* @param pattern - Match against each entry's path relative to `path`.
|
|
511
|
+
* Supports `**` for any number of segments plus `*`, `?` and `[...]`
|
|
512
|
+
* within one segment.
|
|
513
|
+
* @param path - Absolute path of the directory to search under.
|
|
514
|
+
* @param options - Result cap and request options.
|
|
515
|
+
* @returns The matches; check `truncated` before treating them as complete.
|
|
516
|
+
*/
|
|
517
|
+
async glob(pattern, path, options = {}) {
|
|
518
|
+
const body = { pattern, path };
|
|
519
|
+
if (options.limit !== undefined)
|
|
520
|
+
body.limit = options.limit;
|
|
521
|
+
return (await this._fileSearch("glob", body, options));
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* List a directory's immediate entries, without recursing.
|
|
525
|
+
*
|
|
526
|
+
* A convenience over {@link glob} with the pattern `*`.
|
|
527
|
+
*
|
|
528
|
+
* @param path - Absolute path of the directory to list.
|
|
529
|
+
* @param options - Result cap and request options.
|
|
530
|
+
* @returns The directory's files and subdirectories.
|
|
531
|
+
*/
|
|
532
|
+
async ls(path, options = {}) {
|
|
533
|
+
return this.glob("*", path, options);
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Search file contents for a literal string.
|
|
537
|
+
*
|
|
538
|
+
* @param pattern - Literal text to search for. Not a regular expression.
|
|
539
|
+
* @param path - Absolute path of the directory to search under.
|
|
540
|
+
* @param options - Optional `glob` file filter, result cap, request options.
|
|
541
|
+
* @returns The matching lines; check `truncated` for completeness.
|
|
542
|
+
*/
|
|
543
|
+
async grep(pattern, path, options = {}) {
|
|
544
|
+
const body = { pattern, path };
|
|
545
|
+
if (options.glob !== undefined)
|
|
546
|
+
body.glob = options.glob;
|
|
547
|
+
if (options.limit !== undefined)
|
|
548
|
+
body.limit = options.limit;
|
|
549
|
+
return (await this._fileSearch("grep", body, options));
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* POST one of the read-only filesystem search endpoints.
|
|
553
|
+
* @internal
|
|
554
|
+
*/
|
|
555
|
+
async _fileSearch(operation, body, options) {
|
|
556
|
+
const { timeout = 60, headers } = options;
|
|
557
|
+
const dataplaneUrl = this.requireDataplaneUrl();
|
|
558
|
+
const response = await this._client._fetch(`${dataplaneUrl}/${operation}`, {
|
|
559
|
+
method: "POST",
|
|
560
|
+
headers: { "Content-Type": "application/json", ...(headers ?? {}) },
|
|
561
|
+
body: JSON.stringify(body),
|
|
562
|
+
signal: AbortSignal.timeout(timeout * 1000),
|
|
563
|
+
});
|
|
564
|
+
if (!response.ok) {
|
|
565
|
+
await handleFileHttpError(response, String(body.path), this.name);
|
|
566
|
+
}
|
|
567
|
+
return response.json();
|
|
568
|
+
}
|
|
413
569
|
/**
|
|
414
570
|
* Create a link that downloads one file from this sandbox.
|
|
415
571
|
*
|