tensorlake 0.4.50 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9,10 +9,11 @@ var __export = (target, all) => {
9
9
  };
10
10
 
11
11
  // src/defaults.ts
12
- var API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TIMEOUT_MS, MAX_RETRIES, RETRY_BACKOFF_MS, RETRYABLE_STATUS_CODES;
12
+ var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TIMEOUT_MS, MAX_RETRIES, RETRY_BACKOFF_MS, RETRYABLE_STATUS_CODES;
13
13
  var init_defaults = __esm({
14
14
  "src/defaults.ts"() {
15
15
  "use strict";
16
+ SDK_VERSION = "0.4.49";
16
17
  API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
17
18
  API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
18
19
  NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
@@ -115,6 +116,12 @@ import {
115
116
  fetch as undiciFetch,
116
117
  setGlobalDispatcher
117
118
  } from "undici";
119
+ function withTraceId(value, traceId) {
120
+ if (value == null) {
121
+ return { traceId };
122
+ }
123
+ return Object.assign(value, { traceId });
124
+ }
118
125
  function hasHeader(headers, name) {
119
126
  const lowered = name.toLowerCase();
120
127
  return Object.keys(headers).some((key) => key.toLowerCase() === lowered);
@@ -183,7 +190,7 @@ var init_http = __esm({
183
190
  allowH2: true
184
191
  })
185
192
  );
186
- HttpClient = class {
193
+ HttpClient = class _HttpClient {
187
194
  baseUrl;
188
195
  headers;
189
196
  maxRetries;
@@ -196,7 +203,9 @@ var init_http = __esm({
196
203
  this.maxRetries = options.maxRetries ?? MAX_RETRIES;
197
204
  this.retryBackoffMs = options.retryBackoffMs ?? RETRY_BACKOFF_MS;
198
205
  this.timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
199
- this.headers = {};
206
+ this.headers = {
207
+ "User-Agent": `tensorlake-typescript-sdk/${SDK_VERSION}`
208
+ };
200
209
  if (options.apiKey) {
201
210
  this.headers["Authorization"] = `Bearer ${options.apiKey}`;
202
211
  }
@@ -225,8 +234,8 @@ var init_http = __esm({
225
234
  signal: options?.signal
226
235
  });
227
236
  const text = await response.text();
228
- if (!text) return void 0;
229
- return JSON.parse(text);
237
+ const data = text ? JSON.parse(text) : void 0;
238
+ return withTraceId(data, response.traceId);
230
239
  }
231
240
  /** Make a request returning raw bytes. */
232
241
  async requestBytes(method, path2, options) {
@@ -240,7 +249,7 @@ var init_http = __esm({
240
249
  signal: options?.signal
241
250
  });
242
251
  const buffer = await response.arrayBuffer();
243
- return new Uint8Array(buffer);
252
+ return withTraceId(new Uint8Array(buffer), response.traceId);
244
253
  }
245
254
  /** Make a request and return the response body as an SSE stream. */
246
255
  async requestStream(method, path2, options) {
@@ -255,7 +264,7 @@ var init_http = __esm({
255
264
  "No response body for SSE stream"
256
265
  );
257
266
  }
258
- return response.body;
267
+ return withTraceId(response.body, response.traceId);
259
268
  }
260
269
  /** Make a request and return the raw Response. */
261
270
  async requestResponse(method, path2, options) {
@@ -268,7 +277,7 @@ var init_http = __esm({
268
277
  headers["Content-Type"] = "application/json";
269
278
  }
270
279
  const body = hasJsonBody ? JSON.stringify(options?.json) : normalizeRequestBody(options?.body);
271
- return this.doFetch(
280
+ const { response, traceId } = await this.doFetch(
272
281
  method,
273
282
  path2,
274
283
  body,
@@ -276,9 +285,18 @@ var init_http = __esm({
276
285
  options?.signal,
277
286
  options?.allowHttpErrors ?? false
278
287
  );
288
+ return withTraceId(response, traceId);
289
+ }
290
+ static makeTraceparent() {
291
+ const randomHex = (bytes) => Array.from(crypto.getRandomValues(new Uint8Array(bytes))).map((b) => b.toString(16).padStart(2, "0")).join("");
292
+ const traceId = randomHex(16);
293
+ const spanId = randomHex(8);
294
+ return { traceparent: `00-${traceId}-${spanId}-01`, traceId };
279
295
  }
280
296
  async doFetch(method, path2, body, headers, signal, allowHttpErrors = false) {
281
297
  const url = `${this.baseUrl}${path2}`;
298
+ const { traceparent, traceId } = _HttpClient.makeTraceparent();
299
+ headers["traceparent"] = traceparent;
282
300
  let lastError;
283
301
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
284
302
  if (attempt > 0) {
@@ -299,7 +317,7 @@ var init_http = __esm({
299
317
  signal: combinedSignal
300
318
  });
301
319
  clearTimeout(timeoutId);
302
- if (response.ok) return response;
320
+ if (response.ok) return { response, traceId };
303
321
  if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < this.maxRetries) {
304
322
  lastError = new RemoteAPIError(
305
323
  response.status,
@@ -308,7 +326,7 @@ var init_http = __esm({
308
326
  continue;
309
327
  }
310
328
  if (allowHttpErrors) {
311
- return response;
329
+ return { response, traceId };
312
330
  }
313
331
  const errorBody = await response.text().catch(() => "");
314
332
  throwMappedError(response.status, errorBody, path2);
@@ -378,20 +396,20 @@ var SandboxStatus, SnapshotStatus, ProcessStatus, StdinMode, OutputMode, Contain
378
396
  var init_models = __esm({
379
397
  "src/models.ts"() {
380
398
  "use strict";
381
- SandboxStatus = /* @__PURE__ */ ((SandboxStatus2) => {
382
- SandboxStatus2["PENDING"] = "pending";
383
- SandboxStatus2["RUNNING"] = "running";
384
- SandboxStatus2["SNAPSHOTTING"] = "snapshotting";
385
- SandboxStatus2["SUSPENDING"] = "suspending";
386
- SandboxStatus2["SUSPENDED"] = "suspended";
387
- SandboxStatus2["TERMINATED"] = "terminated";
388
- return SandboxStatus2;
399
+ SandboxStatus = /* @__PURE__ */ ((SandboxStatus3) => {
400
+ SandboxStatus3["PENDING"] = "pending";
401
+ SandboxStatus3["RUNNING"] = "running";
402
+ SandboxStatus3["SNAPSHOTTING"] = "snapshotting";
403
+ SandboxStatus3["SUSPENDING"] = "suspending";
404
+ SandboxStatus3["SUSPENDED"] = "suspended";
405
+ SandboxStatus3["TERMINATED"] = "terminated";
406
+ return SandboxStatus3;
389
407
  })(SandboxStatus || {});
390
- SnapshotStatus = /* @__PURE__ */ ((SnapshotStatus2) => {
391
- SnapshotStatus2["IN_PROGRESS"] = "in_progress";
392
- SnapshotStatus2["COMPLETED"] = "completed";
393
- SnapshotStatus2["FAILED"] = "failed";
394
- return SnapshotStatus2;
408
+ SnapshotStatus = /* @__PURE__ */ ((SnapshotStatus3) => {
409
+ SnapshotStatus3["IN_PROGRESS"] = "in_progress";
410
+ SnapshotStatus3["COMPLETED"] = "completed";
411
+ SnapshotStatus3["FAILED"] = "failed";
412
+ return SnapshotStatus3;
395
413
  })(SnapshotStatus || {});
396
414
  ProcessStatus = /* @__PURE__ */ ((ProcessStatus2) => {
397
415
  ProcessStatus2["RUNNING"] = "running";
@@ -3140,6 +3158,7 @@ var init_sandbox = __esm({
3140
3158
  };
3141
3159
  Sandbox = class {
3142
3160
  sandboxId;
3161
+ traceId = null;
3143
3162
  http;
3144
3163
  baseUrl;
3145
3164
  wsHeaders;
@@ -3177,9 +3196,126 @@ var init_sandbox = __esm({
3177
3196
  this.ownsSandbox = true;
3178
3197
  this.lifecycleClient = client;
3179
3198
  }
3199
+ // --- Static factory methods ---
3200
+ /**
3201
+ * Create a new sandbox and return a connected, running handle.
3202
+ *
3203
+ * Covers both fresh sandbox creation and restore-from-snapshot (set
3204
+ * `snapshotId`). Blocks until the sandbox is `Running`.
3205
+ */
3206
+ static async create(options) {
3207
+ const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3208
+ const client = new SandboxClient2(
3209
+ options,
3210
+ /* _internal */
3211
+ true
3212
+ );
3213
+ const sandbox = await client.createAndConnect(options);
3214
+ sandbox.lifecycleClient = client;
3215
+ return sandbox;
3216
+ }
3217
+ /**
3218
+ * Attach to an existing sandbox and return a connected handle.
3219
+ *
3220
+ * Verifies the sandbox exists via a server GET call, then returns a handle
3221
+ * in whatever state the sandbox is in. Does **not** auto-resume a suspended
3222
+ * sandbox — call `sandbox.resume()` explicitly.
3223
+ */
3224
+ static async connect(options) {
3225
+ const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3226
+ const client = new SandboxClient2(
3227
+ options,
3228
+ /* _internal */
3229
+ true
3230
+ );
3231
+ await client.get(options.sandboxId);
3232
+ const sandbox = client.connect(options.sandboxId, options.proxyUrl, options.routingHint);
3233
+ sandbox.lifecycleClient = client;
3234
+ return sandbox;
3235
+ }
3236
+ // --- Static snapshot management ---
3237
+ /** Get information about a snapshot by ID. No sandbox handle needed. */
3238
+ static async getSnapshot(snapshotId, options) {
3239
+ const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3240
+ const client = new SandboxClient2(
3241
+ options,
3242
+ /* _internal */
3243
+ true
3244
+ );
3245
+ return client.getSnapshot(snapshotId);
3246
+ }
3247
+ /** Delete a snapshot by ID. No sandbox handle needed. */
3248
+ static async deleteSnapshot(snapshotId, options) {
3249
+ const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3250
+ const client = new SandboxClient2(
3251
+ options,
3252
+ /* _internal */
3253
+ true
3254
+ );
3255
+ await client.deleteSnapshot(snapshotId);
3256
+ }
3257
+ // --- Instance lifecycle methods ---
3258
+ requireLifecycleClient(operation) {
3259
+ if (!this.lifecycleClient) {
3260
+ throw new SandboxError(
3261
+ `Cannot ${operation}: no lifecycle client available. Use Sandbox.create() or Sandbox.connect() to get a lifecycle-aware handle.`
3262
+ );
3263
+ }
3264
+ return this.lifecycleClient;
3265
+ }
3266
+ /**
3267
+ * Suspend this sandbox.
3268
+ *
3269
+ * By default blocks until the sandbox is fully `Suspended`. Pass
3270
+ * `{ wait: false }` for fire-and-return.
3271
+ */
3272
+ async suspend(options) {
3273
+ const client = this.requireLifecycleClient("suspend");
3274
+ await client.suspend(this.sandboxId, options);
3275
+ }
3276
+ /**
3277
+ * Resume this sandbox.
3278
+ *
3279
+ * By default blocks until the sandbox is `Running` and routable. Pass
3280
+ * `{ wait: false }` for fire-and-return.
3281
+ */
3282
+ async resume(options) {
3283
+ const client = this.requireLifecycleClient("resume");
3284
+ await client.resume(this.sandboxId, options);
3285
+ }
3286
+ /**
3287
+ * Create a snapshot of this sandbox's filesystem and wait for it to
3288
+ * be committed.
3289
+ *
3290
+ * By default blocks until the snapshot artifact is ready and returns
3291
+ * the completed `SnapshotInfo`. Pass `{ wait: false }` to fire-and-return
3292
+ * (returns `undefined`).
3293
+ */
3294
+ async checkpoint(options) {
3295
+ const client = this.requireLifecycleClient("checkpoint");
3296
+ if (options?.wait === false) {
3297
+ await client.snapshot(this.sandboxId, { contentMode: options.contentMode });
3298
+ return void 0;
3299
+ }
3300
+ return client.snapshotAndWait(this.sandboxId, {
3301
+ timeout: options?.timeout,
3302
+ pollInterval: options?.pollInterval,
3303
+ contentMode: options?.contentMode
3304
+ });
3305
+ }
3306
+ /**
3307
+ * List snapshots taken from this sandbox.
3308
+ */
3309
+ async listSnapshots() {
3310
+ const client = this.requireLifecycleClient("listSnapshots");
3311
+ const all = await client.listSnapshots();
3312
+ return all.filter((s) => s.sandboxId === this.sandboxId);
3313
+ }
3314
+ /** Close the HTTP client. The sandbox keeps running. */
3180
3315
  close() {
3181
3316
  this.http.close();
3182
3317
  }
3318
+ /** Terminate the sandbox and release all resources. */
3183
3319
  async terminate() {
3184
3320
  const client = this.lifecycleClient;
3185
3321
  this.ownsSandbox = false;
@@ -3232,6 +3368,14 @@ var init_sandbox = __esm({
3232
3368
  };
3233
3369
  }
3234
3370
  // --- Process management ---
3371
+ /**
3372
+ * Start a process in the sandbox without waiting for it to exit.
3373
+ *
3374
+ * Returns a `ProcessInfo` with the assigned `pid`. Use `getProcess()` to
3375
+ * poll status, or `followStdout()` / `followOutput()` to stream output
3376
+ * until the process exits. Use `run()` instead to block until completion
3377
+ * and get combined output in one call.
3378
+ */
3235
3379
  async startProcess(command, options) {
3236
3380
  const payload = { command };
3237
3381
  if (options?.args != null) payload.args = options.args;
@@ -3253,6 +3397,7 @@ var init_sandbox = __esm({
3253
3397
  );
3254
3398
  return fromSnakeKeys(raw);
3255
3399
  }
3400
+ /** List all processes (running and exited) tracked by the sandbox daemon. */
3256
3401
  async listProcesses() {
3257
3402
  const raw = await this.http.requestJson(
3258
3403
  "GET",
@@ -3260,6 +3405,7 @@ var init_sandbox = __esm({
3260
3405
  );
3261
3406
  return (raw.processes ?? []).map((p) => fromSnakeKeys(p));
3262
3407
  }
3408
+ /** Get current status and metadata for a process by PID. */
3263
3409
  async getProcess(pid) {
3264
3410
  const raw = await this.http.requestJson(
3265
3411
  "GET",
@@ -3267,9 +3413,11 @@ var init_sandbox = __esm({
3267
3413
  );
3268
3414
  return fromSnakeKeys(raw);
3269
3415
  }
3416
+ /** Send SIGKILL to a process. */
3270
3417
  async killProcess(pid) {
3271
3418
  await this.http.requestJson("DELETE", `/api/v1/processes/${pid}`);
3272
3419
  }
3420
+ /** Send an arbitrary signal to a process (e.g. `15` for SIGTERM, `9` for SIGKILL). */
3273
3421
  async sendSignal(pid, signal) {
3274
3422
  const raw = await this.http.requestJson(
3275
3423
  "POST",
@@ -3279,15 +3427,18 @@ var init_sandbox = __esm({
3279
3427
  return fromSnakeKeys(raw);
3280
3428
  }
3281
3429
  // --- Process I/O ---
3430
+ /** Write bytes to a process's stdin. The process must have been started with `stdinMode: StdinMode.PIPE`. */
3282
3431
  async writeStdin(pid, data) {
3283
3432
  await this.http.requestBytes("POST", `/api/v1/processes/${pid}/stdin`, {
3284
3433
  body: data,
3285
3434
  contentType: "application/octet-stream"
3286
3435
  });
3287
3436
  }
3437
+ /** Close a process's stdin pipe, signalling EOF to the process. */
3288
3438
  async closeStdin(pid) {
3289
3439
  await this.http.requestJson("POST", `/api/v1/processes/${pid}/stdin/close`);
3290
3440
  }
3441
+ /** Return all captured stdout lines produced so far by a process. */
3291
3442
  async getStdout(pid) {
3292
3443
  const raw = await this.http.requestJson(
3293
3444
  "GET",
@@ -3295,6 +3446,7 @@ var init_sandbox = __esm({
3295
3446
  );
3296
3447
  return fromSnakeKeys(raw);
3297
3448
  }
3449
+ /** Return all captured stderr lines produced so far by a process. */
3298
3450
  async getStderr(pid) {
3299
3451
  const raw = await this.http.requestJson(
3300
3452
  "GET",
@@ -3302,6 +3454,7 @@ var init_sandbox = __esm({
3302
3454
  );
3303
3455
  return fromSnakeKeys(raw);
3304
3456
  }
3457
+ /** Return all captured stdout+stderr lines produced so far by a process. */
3305
3458
  async getOutput(pid) {
3306
3459
  const raw = await this.http.requestJson(
3307
3460
  "GET",
@@ -3310,6 +3463,7 @@ var init_sandbox = __esm({
3310
3463
  return fromSnakeKeys(raw);
3311
3464
  }
3312
3465
  // --- Streaming (SSE) ---
3466
+ /** Stream stdout events from a process until it exits. Yields one `OutputEvent` per line. */
3313
3467
  async *followStdout(pid, options) {
3314
3468
  const stream = await this.http.requestStream(
3315
3469
  "GET",
@@ -3323,6 +3477,7 @@ var init_sandbox = __esm({
3323
3477
  yield fromSnakeKeys(raw);
3324
3478
  }
3325
3479
  }
3480
+ /** Stream stderr events from a process until it exits. Yields one `OutputEvent` per line. */
3326
3481
  async *followStderr(pid, options) {
3327
3482
  const stream = await this.http.requestStream(
3328
3483
  "GET",
@@ -3336,6 +3491,7 @@ var init_sandbox = __esm({
3336
3491
  yield fromSnakeKeys(raw);
3337
3492
  }
3338
3493
  }
3494
+ /** Stream combined stdout+stderr events from a process until it exits. Yields one `OutputEvent` per line. */
3339
3495
  async *followOutput(pid, options) {
3340
3496
  const stream = await this.http.requestStream(
3341
3497
  "GET",
@@ -3350,12 +3506,14 @@ var init_sandbox = __esm({
3350
3506
  }
3351
3507
  }
3352
3508
  // --- File operations ---
3509
+ /** Read a file from the sandbox and return its raw bytes. */
3353
3510
  async readFile(path2) {
3354
3511
  return this.http.requestBytes(
3355
3512
  "GET",
3356
3513
  `/api/v1/files?path=${encodeURIComponent(path2)}`
3357
3514
  );
3358
3515
  }
3516
+ /** Write raw bytes to a file in the sandbox, creating it if it does not exist. */
3359
3517
  async writeFile(path2, content) {
3360
3518
  await this.http.requestBytes(
3361
3519
  "PUT",
@@ -3363,12 +3521,14 @@ var init_sandbox = __esm({
3363
3521
  { body: content, contentType: "application/octet-stream" }
3364
3522
  );
3365
3523
  }
3524
+ /** Delete a file from the sandbox. */
3366
3525
  async deleteFile(path2) {
3367
3526
  await this.http.requestJson(
3368
3527
  "DELETE",
3369
3528
  `/api/v1/files?path=${encodeURIComponent(path2)}`
3370
3529
  );
3371
3530
  }
3531
+ /** List the contents of a directory in the sandbox. */
3372
3532
  async listDirectory(path2) {
3373
3533
  const raw = await this.http.requestJson(
3374
3534
  "GET",
@@ -3377,6 +3537,7 @@ var init_sandbox = __esm({
3377
3537
  return fromSnakeKeys(raw);
3378
3538
  }
3379
3539
  // --- PTY ---
3540
+ /** Create an interactive PTY session. Returns a `sessionId` and `token` for WebSocket connection via `connectPty()`. */
3380
3541
  async createPtySession(options) {
3381
3542
  const payload = {
3382
3543
  command: options.command,
@@ -3393,6 +3554,7 @@ var init_sandbox = __esm({
3393
3554
  );
3394
3555
  return fromSnakeKeys(raw);
3395
3556
  }
3557
+ /** Create a PTY session and connect to it immediately. Cleans up the session if the WebSocket connection fails. */
3396
3558
  async createPty(options) {
3397
3559
  const { onData, onExit, ...createOptions } = options;
3398
3560
  const session = await this.createPtySession(createOptions);
@@ -3406,6 +3568,7 @@ var init_sandbox = __esm({
3406
3568
  throw error;
3407
3569
  }
3408
3570
  }
3571
+ /** Attach to an existing PTY session by ID and token and return a connected `Pty` handle. */
3409
3572
  async connectPty(sessionId, token, options) {
3410
3573
  const wsUrl = new URL(this.ptyWsUrl(sessionId, token));
3411
3574
  const authToken = wsUrl.searchParams.get("token") ?? token;
@@ -3430,6 +3593,7 @@ var init_sandbox = __esm({
3430
3593
  await pty.connect();
3431
3594
  return pty;
3432
3595
  }
3596
+ /** Open a TCP tunnel to a port inside the sandbox and return the local listener. */
3433
3597
  async createTunnel(remotePort, options) {
3434
3598
  return TcpTunnel.listen({
3435
3599
  baseUrl: this.baseUrl,
@@ -3440,6 +3604,7 @@ var init_sandbox = __esm({
3440
3604
  connectTimeout: options?.connectTimeout
3441
3605
  });
3442
3606
  }
3607
+ /** Connect to a sandbox VNC session for programmatic desktop control. */
3443
3608
  async connectDesktop(options) {
3444
3609
  return Desktop.connect({
3445
3610
  baseUrl: this.baseUrl,
@@ -3462,6 +3627,7 @@ var init_sandbox = __esm({
3462
3627
  return `${wsBase}/api/v1/pty/${sessionId}/ws?token=${token}`;
3463
3628
  }
3464
3629
  // --- Health ---
3630
+ /** Check the sandbox daemon health. */
3465
3631
  async health() {
3466
3632
  const raw = await this.http.requestJson(
3467
3633
  "GET",
@@ -3469,6 +3635,7 @@ var init_sandbox = __esm({
3469
3635
  );
3470
3636
  return fromSnakeKeys(raw);
3471
3637
  }
3638
+ /** Get sandbox daemon info (version, uptime, process counts). */
3472
3639
  async info() {
3473
3640
  const raw = await this.http.requestJson(
3474
3641
  "GET",
@@ -3481,6 +3648,10 @@ var init_sandbox = __esm({
3481
3648
  });
3482
3649
 
3483
3650
  // src/client.ts
3651
+ var client_exports = {};
3652
+ __export(client_exports, {
3653
+ SandboxClient: () => SandboxClient
3654
+ });
3484
3655
  function sleep2(ms) {
3485
3656
  return new Promise((resolve) => setTimeout(resolve, ms));
3486
3657
  }
@@ -3517,7 +3688,13 @@ var init_client = __esm({
3517
3688
  projectId;
3518
3689
  namespace;
3519
3690
  local;
3520
- constructor(options) {
3691
+ /** @internal Pass `true` to suppress the deprecation warning when used by `Sandbox.create()` / `Sandbox.connect()`. */
3692
+ constructor(options, _internal = false) {
3693
+ if (!_internal) {
3694
+ console.warn(
3695
+ "[tensorlake] SandboxClient is deprecated; use Sandbox.create() / Sandbox.connect() instead."
3696
+ );
3697
+ }
3521
3698
  this.apiUrl = options?.apiUrl ?? API_URL;
3522
3699
  this.apiKey = options?.apiKey ?? API_KEY;
3523
3700
  this.organizationId = options?.organizationId;
@@ -3557,6 +3734,7 @@ var init_client = __esm({
3557
3734
  return lifecyclePath(subpath, this.local, this.namespace);
3558
3735
  }
3559
3736
  // --- Sandbox CRUD ---
3737
+ /** Create a new sandbox. Returns immediately; the sandbox may still be starting. Use `createAndConnect()` for a blocking, ready-to-use handle. */
3560
3738
  async create(options) {
3561
3739
  const body = {
3562
3740
  resources: {
@@ -3583,8 +3761,10 @@ var init_client = __esm({
3583
3761
  this.path("sandboxes"),
3584
3762
  { body }
3585
3763
  );
3586
- return fromSnakeKeys(raw, "sandboxId");
3764
+ const result = fromSnakeKeys(raw, "sandboxId");
3765
+ return Object.assign(result, { traceId: raw.traceId });
3587
3766
  }
3767
+ /** Get current state and metadata for a sandbox by ID. */
3588
3768
  async get(sandboxId) {
3589
3769
  const raw = await this.http.requestJson(
3590
3770
  "GET",
@@ -3592,6 +3772,7 @@ var init_client = __esm({
3592
3772
  );
3593
3773
  return fromSnakeKeys(raw, "sandboxId");
3594
3774
  }
3775
+ /** List all sandboxes in the namespace. */
3595
3776
  async list() {
3596
3777
  const raw = await this.http.requestJson(
3597
3778
  "GET",
@@ -3601,6 +3782,7 @@ var init_client = __esm({
3601
3782
  (s) => fromSnakeKeys(s, "sandboxId")
3602
3783
  );
3603
3784
  }
3785
+ /** Update sandbox properties such as name, exposed ports, and proxy auth settings. */
3604
3786
  async update(sandboxId, options) {
3605
3787
  const body = {};
3606
3788
  if (options.name != null) body.name = options.name;
@@ -3620,6 +3802,7 @@ var init_client = __esm({
3620
3802
  );
3621
3803
  return fromSnakeKeys(raw, "sandboxId");
3622
3804
  }
3805
+ /** Get the current proxy port settings for a sandbox. */
3623
3806
  async getPortAccess(sandboxId) {
3624
3807
  const info = await this.get(sandboxId);
3625
3808
  return {
@@ -3628,6 +3811,7 @@ var init_client = __esm({
3628
3811
  sandboxUrl: info.sandboxUrl
3629
3812
  };
3630
3813
  }
3814
+ /** Add one or more user ports to the sandbox proxy allowlist. */
3631
3815
  async exposePorts(sandboxId, ports, options) {
3632
3816
  const requestedPorts = normalizeUserPorts(ports);
3633
3817
  const current = await this.getPortAccess(sandboxId);
@@ -3640,6 +3824,7 @@ var init_client = __esm({
3640
3824
  exposedPorts: desiredPorts
3641
3825
  });
3642
3826
  }
3827
+ /** Remove one or more user ports from the sandbox proxy allowlist. */
3643
3828
  async unexposePorts(sandboxId, ports) {
3644
3829
  const requestedPorts = normalizeUserPorts(ports);
3645
3830
  const current = await this.getPortAccess(sandboxId);
@@ -3650,32 +3835,98 @@ var init_client = __esm({
3650
3835
  exposedPorts: desiredPorts
3651
3836
  });
3652
3837
  }
3838
+ /** Terminate and delete a sandbox. */
3653
3839
  async delete(sandboxId) {
3654
3840
  await this.http.requestJson(
3655
3841
  "DELETE",
3656
3842
  this.path(`sandboxes/${sandboxId}`)
3657
3843
  );
3658
3844
  }
3659
- async suspend(sandboxId) {
3845
+ /**
3846
+ * Suspend a named sandbox, preserving its state for later resume.
3847
+ *
3848
+ * Only sandboxes created with a `name` can be suspended; ephemeral sandboxes
3849
+ * cannot. By default blocks until the sandbox is fully `Suspended`. Pass
3850
+ * `{ wait: false }` to return immediately after the request is sent
3851
+ * (fire-and-return); the server processes the suspend asynchronously.
3852
+ *
3853
+ * @param sandboxId - ID or name of the sandbox.
3854
+ * @param options.wait - If `true` (default), poll until `Suspended`. Pass `false` to fire-and-return.
3855
+ * @param options.timeout - Max seconds to wait when `wait=true` (default 300).
3856
+ * @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
3857
+ * @throws {SandboxError} If `wait=true` and the sandbox does not reach `Suspended` within `timeout`.
3858
+ */
3859
+ async suspend(sandboxId, options) {
3660
3860
  await this.http.requestResponse(
3661
3861
  "POST",
3662
3862
  this.path(`sandboxes/${sandboxId}/suspend`)
3663
3863
  );
3864
+ if (options?.wait === false) return;
3865
+ const timeout = options?.timeout ?? 300;
3866
+ const pollInterval = options?.pollInterval ?? 1;
3867
+ const deadline = Date.now() + timeout * 1e3;
3868
+ while (Date.now() < deadline) {
3869
+ const info = await this.get(sandboxId);
3870
+ if (info.status === "suspended" /* SUSPENDED */) return;
3871
+ if (info.status === "terminated" /* TERMINATED */) {
3872
+ throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for suspend`);
3873
+ }
3874
+ await sleep2(pollInterval * 1e3);
3875
+ }
3876
+ throw new SandboxError(`Sandbox ${sandboxId} did not suspend within ${timeout}s`);
3664
3877
  }
3665
- async resume(sandboxId) {
3878
+ /**
3879
+ * Resume a suspended sandbox and bring it back to `Running`.
3880
+ *
3881
+ * By default blocks until the sandbox is `Running` and routable. Pass
3882
+ * `{ wait: false }` to return immediately after the request is sent
3883
+ * (fire-and-return); the server processes the resume asynchronously.
3884
+ *
3885
+ * @param sandboxId - ID or name of the sandbox.
3886
+ * @param options.wait - If `true` (default), poll until `Running`. Pass `false` to fire-and-return.
3887
+ * @param options.timeout - Max seconds to wait when `wait=true` (default 300).
3888
+ * @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
3889
+ * @throws {SandboxError} If `wait=true` and the sandbox does not reach `Running` within `timeout`.
3890
+ */
3891
+ async resume(sandboxId, options) {
3666
3892
  await this.http.requestResponse(
3667
3893
  "POST",
3668
3894
  this.path(`sandboxes/${sandboxId}/resume`)
3669
3895
  );
3896
+ if (options?.wait === false) return;
3897
+ const timeout = options?.timeout ?? 300;
3898
+ const pollInterval = options?.pollInterval ?? 1;
3899
+ const deadline = Date.now() + timeout * 1e3;
3900
+ while (Date.now() < deadline) {
3901
+ const info = await this.get(sandboxId);
3902
+ if (info.status === "running" /* RUNNING */) return;
3903
+ if (info.status === "terminated" /* TERMINATED */) {
3904
+ throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for resume`);
3905
+ }
3906
+ await sleep2(pollInterval * 1e3);
3907
+ }
3908
+ throw new SandboxError(`Sandbox ${sandboxId} did not resume within ${timeout}s`);
3670
3909
  }
3910
+ /** Claim a warm sandbox from a pool, creating one if no warm containers are available. */
3671
3911
  async claim(poolId) {
3672
3912
  const raw = await this.http.requestJson(
3673
3913
  "POST",
3674
3914
  this.path(`sandbox-pools/${poolId}/sandboxes`)
3675
3915
  );
3676
- return fromSnakeKeys(raw, "sandboxId");
3916
+ const result = fromSnakeKeys(raw, "sandboxId");
3917
+ return Object.assign(result, { traceId: raw.traceId });
3677
3918
  }
3678
3919
  // --- Snapshots ---
3920
+ /**
3921
+ * Request a snapshot of a running sandbox's filesystem.
3922
+ *
3923
+ * This call **returns immediately** with a `snapshotId` and `in_progress`
3924
+ * status — the snapshot is created asynchronously. Poll `getSnapshot()` until
3925
+ * `completed` or `failed`, or use `snapshotAndWait()` to block automatically.
3926
+ *
3927
+ * @param options.contentMode - `"filesystem_only"` for cold-boot snapshots (e.g. image builds).
3928
+ * Omit to use the server default (full VM snapshot).
3929
+ */
3679
3930
  async snapshot(sandboxId, options) {
3680
3931
  const requestOptions = options?.contentMode != null ? { body: { snapshot_content_mode: options.contentMode } } : void 0;
3681
3932
  const raw = await this.http.requestJson(
@@ -3685,6 +3936,7 @@ var init_client = __esm({
3685
3936
  );
3686
3937
  return fromSnakeKeys(raw, "snapshotId");
3687
3938
  }
3939
+ /** Get current status and metadata for a snapshot by ID. */
3688
3940
  async getSnapshot(snapshotId) {
3689
3941
  const raw = await this.http.requestJson(
3690
3942
  "GET",
@@ -3692,6 +3944,7 @@ var init_client = __esm({
3692
3944
  );
3693
3945
  return fromSnakeKeys(raw, "snapshotId");
3694
3946
  }
3947
+ /** List all snapshots in the namespace. */
3695
3948
  async listSnapshots() {
3696
3949
  const raw = await this.http.requestJson(
3697
3950
  "GET",
@@ -3701,12 +3954,26 @@ var init_client = __esm({
3701
3954
  (s) => fromSnakeKeys(s, "snapshotId")
3702
3955
  );
3703
3956
  }
3957
+ /** Delete a snapshot by ID. */
3704
3958
  async deleteSnapshot(snapshotId) {
3705
3959
  await this.http.requestJson(
3706
3960
  "DELETE",
3707
3961
  this.path(`snapshots/${snapshotId}`)
3708
3962
  );
3709
3963
  }
3964
+ /**
3965
+ * Create a snapshot and block until it is committed.
3966
+ *
3967
+ * Combines `snapshot()` with polling `getSnapshot()` until `completed`.
3968
+ * Prefer `sandbox.checkpoint()` on a `Sandbox` handle for the same behavior
3969
+ * without managing the client separately.
3970
+ *
3971
+ * @param sandboxId - ID of the running sandbox to snapshot.
3972
+ * @param options.timeout - Max seconds to wait (default 300).
3973
+ * @param options.pollInterval - Seconds between status polls (default 1).
3974
+ * @param options.contentMode - Content mode passed through to `snapshot()`.
3975
+ * @throws {SandboxError} If the snapshot fails or `timeout` elapses.
3976
+ */
3710
3977
  async snapshotAndWait(sandboxId, options) {
3711
3978
  const timeout = options?.timeout ?? 300;
3712
3979
  const pollInterval = options?.pollInterval ?? 1;
@@ -3729,6 +3996,7 @@ var init_client = __esm({
3729
3996
  );
3730
3997
  }
3731
3998
  // --- Pools ---
3999
+ /** Create a new sandbox pool with warm pre-booted containers. */
3732
4000
  async createPool(options) {
3733
4001
  const body = {
3734
4002
  image: options.image,
@@ -3750,6 +4018,7 @@ var init_client = __esm({
3750
4018
  );
3751
4019
  return fromSnakeKeys(raw, "poolId");
3752
4020
  }
4021
+ /** Get current state and metadata for a sandbox pool by ID. */
3753
4022
  async getPool(poolId) {
3754
4023
  const raw = await this.http.requestJson(
3755
4024
  "GET",
@@ -3757,6 +4026,7 @@ var init_client = __esm({
3757
4026
  );
3758
4027
  return fromSnakeKeys(raw, "poolId");
3759
4028
  }
4029
+ /** List all sandbox pools in the namespace. */
3760
4030
  async listPools() {
3761
4031
  const raw = await this.http.requestJson(
3762
4032
  "GET",
@@ -3766,6 +4036,7 @@ var init_client = __esm({
3766
4036
  (p) => fromSnakeKeys(p, "poolId")
3767
4037
  );
3768
4038
  }
4039
+ /** Replace the configuration of an existing sandbox pool. */
3769
4040
  async updatePool(poolId, options) {
3770
4041
  const body = {
3771
4042
  image: options.image,
@@ -3787,6 +4058,7 @@ var init_client = __esm({
3787
4058
  );
3788
4059
  return fromSnakeKeys(raw, "poolId");
3789
4060
  }
4061
+ /** Delete a sandbox pool. Fails if the pool has active containers. */
3790
4062
  async deletePool(poolId) {
3791
4063
  await this.http.requestJson(
3792
4064
  "DELETE",
@@ -3794,6 +4066,7 @@ var init_client = __esm({
3794
4066
  );
3795
4067
  }
3796
4068
  // --- Connect ---
4069
+ /** Return a `Sandbox` handle for an existing running sandbox without verifying it exists. */
3797
4070
  connect(identifier, proxyUrl, routingHint) {
3798
4071
  const resolvedProxy = proxyUrl ?? resolveProxyUrl(this.apiUrl);
3799
4072
  return new Sandbox({
@@ -3805,6 +4078,15 @@ var init_client = __esm({
3805
4078
  routingHint
3806
4079
  });
3807
4080
  }
4081
+ /**
4082
+ * Create a sandbox, wait for it to reach `Running`, and return a connected handle.
4083
+ *
4084
+ * Blocks until the sandbox is ready or `startupTimeout` elapses. The returned
4085
+ * `Sandbox` auto-terminates when `terminate()` is called.
4086
+ *
4087
+ * @param options.startupTimeout - Max seconds to wait for `Running` status (default 60).
4088
+ * @throws {SandboxError} If the sandbox terminates during startup or the timeout elapses.
4089
+ */
3808
4090
  async createAndConnect(options) {
3809
4091
  const startupTimeout = options?.startupTimeout ?? 60;
3810
4092
  let result;
@@ -3816,6 +4098,7 @@ var init_client = __esm({
3816
4098
  if (result.status === "running" /* RUNNING */) {
3817
4099
  const sandbox = this.connect(result.sandboxId, options?.proxyUrl, result.routingHint);
3818
4100
  sandbox._setOwner(this);
4101
+ sandbox.traceId = result.traceId;
3819
4102
  return sandbox;
3820
4103
  }
3821
4104
  const deadline = Date.now() + startupTimeout * 1e3;
@@ -3824,6 +4107,7 @@ var init_client = __esm({
3824
4107
  if (info.status === "running" /* RUNNING */) {
3825
4108
  const sandbox = this.connect(result.sandboxId, options?.proxyUrl, info.routingHint);
3826
4109
  sandbox._setOwner(this);
4110
+ sandbox.traceId = result.traceId;
3827
4111
  return sandbox;
3828
4112
  }
3829
4113
  if (info.status === "terminated" /* TERMINATED */) {