langsmith 0.10.4 → 0.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/address.cjs +119 -0
  2. package/dist/address.d.ts +13 -0
  3. package/dist/address.js +107 -0
  4. package/dist/client.cjs +68 -22
  5. package/dist/client.d.ts +13 -0
  6. package/dist/client.js +68 -22
  7. package/dist/evaluation/_runner.cjs +47 -5
  8. package/dist/evaluation/_runner.d.ts +12 -0
  9. package/dist/evaluation/_runner.js +47 -5
  10. package/dist/experimental/otel/constants.cjs +5 -1
  11. package/dist/experimental/otel/constants.d.ts +3 -0
  12. package/dist/experimental/otel/constants.js +3 -0
  13. package/dist/experimental/otel/translator.cjs +14 -0
  14. package/dist/experimental/otel/translator.js +14 -0
  15. package/dist/index.cjs +4 -2
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.js +2 -1
  18. package/dist/run_trees.cjs +154 -24
  19. package/dist/run_trees.d.ts +13 -1
  20. package/dist/run_trees.js +154 -24
  21. package/dist/sandbox/access_delegation.cjs +24 -0
  22. package/dist/sandbox/access_delegation.d.ts +15 -0
  23. package/dist/sandbox/access_delegation.js +21 -0
  24. package/dist/sandbox/client.cjs +70 -5
  25. package/dist/sandbox/client.d.ts +18 -0
  26. package/dist/sandbox/client.js +70 -5
  27. package/dist/sandbox/command_handle.cjs +34 -0
  28. package/dist/sandbox/command_handle.d.ts +10 -0
  29. package/dist/sandbox/command_handle.js +34 -0
  30. package/dist/sandbox/errors.cjs +12 -1
  31. package/dist/sandbox/errors.d.ts +7 -0
  32. package/dist/sandbox/errors.js +10 -0
  33. package/dist/sandbox/helpers.cjs +129 -0
  34. package/dist/sandbox/helpers.d.ts +29 -0
  35. package/dist/sandbox/helpers.js +123 -0
  36. package/dist/sandbox/index.cjs +12 -1
  37. package/dist/sandbox/index.d.ts +8 -2
  38. package/dist/sandbox/index.js +4 -1
  39. package/dist/sandbox/mounts.cjs +21 -3
  40. package/dist/sandbox/mounts.d.ts +7 -1
  41. package/dist/sandbox/mounts.js +21 -3
  42. package/dist/sandbox/proxy_config.cjs +41 -10
  43. package/dist/sandbox/proxy_config.d.ts +33 -6
  44. package/dist/sandbox/proxy_config.js +40 -10
  45. package/dist/sandbox/sandbox.cjs +160 -4
  46. package/dist/sandbox/sandbox.d.ts +71 -1
  47. package/dist/sandbox/sandbox.js +161 -5
  48. package/dist/sandbox/service_url.cjs +125 -0
  49. package/dist/sandbox/service_url.d.ts +50 -0
  50. package/dist/sandbox/service_url.js +120 -0
  51. package/dist/sandbox/types.d.ts +251 -8
  52. package/dist/sandbox/verify.cjs +300 -0
  53. package/dist/sandbox/verify.d.ts +122 -0
  54. package/dist/sandbox/verify.js +296 -0
  55. package/dist/sandbox/ws_execute.cjs +31 -1
  56. package/dist/sandbox/ws_execute.d.ts +9 -0
  57. package/dist/sandbox/ws_execute.js +31 -1
  58. package/dist/schemas.d.ts +5 -0
  59. package/dist/traceable.cjs +7 -1
  60. package/dist/traceable.js +7 -1
  61. package/dist/utils/agent_addressing.cjs +92 -0
  62. package/dist/utils/agent_addressing.d.ts +30 -0
  63. package/dist/utils/agent_addressing.js +83 -0
  64. package/dist/utils/env.cjs +2 -0
  65. package/dist/utils/env.js +2 -0
  66. package/dist/utils/error.cjs +17 -22
  67. package/dist/utils/error.js +17 -22
  68. package/package.json +5 -5
@@ -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 { CaptureSnapshotOptions, DownloadURL, ExecutionResult, GenerateDownloadURLOptions, RunOptions, Snapshot, StartSandboxOptions } from "./types.js";
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
  *
@@ -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
  *