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.
@@ -79,10 +79,11 @@ var init_models = __esm({
79
79
  });
80
80
 
81
81
  // src/defaults.ts
82
- var API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TIMEOUT_MS, MAX_RETRIES, RETRY_BACKOFF_MS, RETRYABLE_STATUS_CODES;
82
+ var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TIMEOUT_MS, MAX_RETRIES, RETRY_BACKOFF_MS, RETRYABLE_STATUS_CODES;
83
83
  var init_defaults = __esm({
84
84
  "src/defaults.ts"() {
85
85
  "use strict";
86
+ SDK_VERSION = "0.4.49";
86
87
  API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
87
88
  API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
88
89
  NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
@@ -156,6 +157,12 @@ var init_errors = __esm({
156
157
  });
157
158
 
158
159
  // src/http.ts
160
+ function withTraceId(value, traceId) {
161
+ if (value == null) {
162
+ return { traceId };
163
+ }
164
+ return Object.assign(value, { traceId });
165
+ }
159
166
  function hasHeader(headers, name) {
160
167
  const lowered = name.toLowerCase();
161
168
  return Object.keys(headers).some((key) => key.toLowerCase() === lowered);
@@ -225,7 +232,7 @@ var init_http = __esm({
225
232
  allowH2: true
226
233
  })
227
234
  );
228
- HttpClient = class {
235
+ HttpClient = class _HttpClient {
229
236
  baseUrl;
230
237
  headers;
231
238
  maxRetries;
@@ -238,7 +245,9 @@ var init_http = __esm({
238
245
  this.maxRetries = options.maxRetries ?? MAX_RETRIES;
239
246
  this.retryBackoffMs = options.retryBackoffMs ?? RETRY_BACKOFF_MS;
240
247
  this.timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
241
- this.headers = {};
248
+ this.headers = {
249
+ "User-Agent": `tensorlake-typescript-sdk/${SDK_VERSION}`
250
+ };
242
251
  if (options.apiKey) {
243
252
  this.headers["Authorization"] = `Bearer ${options.apiKey}`;
244
253
  }
@@ -267,8 +276,8 @@ var init_http = __esm({
267
276
  signal: options?.signal
268
277
  });
269
278
  const text = await response.text();
270
- if (!text) return void 0;
271
- return JSON.parse(text);
279
+ const data = text ? JSON.parse(text) : void 0;
280
+ return withTraceId(data, response.traceId);
272
281
  }
273
282
  /** Make a request returning raw bytes. */
274
283
  async requestBytes(method, path2, options) {
@@ -282,7 +291,7 @@ var init_http = __esm({
282
291
  signal: options?.signal
283
292
  });
284
293
  const buffer = await response.arrayBuffer();
285
- return new Uint8Array(buffer);
294
+ return withTraceId(new Uint8Array(buffer), response.traceId);
286
295
  }
287
296
  /** Make a request and return the response body as an SSE stream. */
288
297
  async requestStream(method, path2, options) {
@@ -297,7 +306,7 @@ var init_http = __esm({
297
306
  "No response body for SSE stream"
298
307
  );
299
308
  }
300
- return response.body;
309
+ return withTraceId(response.body, response.traceId);
301
310
  }
302
311
  /** Make a request and return the raw Response. */
303
312
  async requestResponse(method, path2, options) {
@@ -310,7 +319,7 @@ var init_http = __esm({
310
319
  headers["Content-Type"] = "application/json";
311
320
  }
312
321
  const body = hasJsonBody ? JSON.stringify(options?.json) : normalizeRequestBody(options?.body);
313
- return this.doFetch(
322
+ const { response, traceId } = await this.doFetch(
314
323
  method,
315
324
  path2,
316
325
  body,
@@ -318,9 +327,18 @@ var init_http = __esm({
318
327
  options?.signal,
319
328
  options?.allowHttpErrors ?? false
320
329
  );
330
+ return withTraceId(response, traceId);
331
+ }
332
+ static makeTraceparent() {
333
+ const randomHex = (bytes) => Array.from(crypto.getRandomValues(new Uint8Array(bytes))).map((b) => b.toString(16).padStart(2, "0")).join("");
334
+ const traceId = randomHex(16);
335
+ const spanId = randomHex(8);
336
+ return { traceparent: `00-${traceId}-${spanId}-01`, traceId };
321
337
  }
322
338
  async doFetch(method, path2, body, headers, signal, allowHttpErrors = false) {
323
339
  const url = `${this.baseUrl}${path2}`;
340
+ const { traceparent, traceId } = _HttpClient.makeTraceparent();
341
+ headers["traceparent"] = traceparent;
324
342
  let lastError;
325
343
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
326
344
  if (attempt > 0) {
@@ -341,7 +359,7 @@ var init_http = __esm({
341
359
  signal: combinedSignal
342
360
  });
343
361
  clearTimeout(timeoutId);
344
- if (response.ok) return response;
362
+ if (response.ok) return { response, traceId };
345
363
  if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < this.maxRetries) {
346
364
  lastError = new RemoteAPIError(
347
365
  response.status,
@@ -350,7 +368,7 @@ var init_http = __esm({
350
368
  continue;
351
369
  }
352
370
  if (allowHttpErrors) {
353
- return response;
371
+ return { response, traceId };
354
372
  }
355
373
  const errorBody = await response.text().catch(() => "");
356
374
  throwMappedError(response.status, errorBody, path2);
@@ -3097,6 +3115,7 @@ var init_sandbox = __esm({
3097
3115
  };
3098
3116
  Sandbox = class {
3099
3117
  sandboxId;
3118
+ traceId = null;
3100
3119
  http;
3101
3120
  baseUrl;
3102
3121
  wsHeaders;
@@ -3134,9 +3153,126 @@ var init_sandbox = __esm({
3134
3153
  this.ownsSandbox = true;
3135
3154
  this.lifecycleClient = client;
3136
3155
  }
3156
+ // --- Static factory methods ---
3157
+ /**
3158
+ * Create a new sandbox and return a connected, running handle.
3159
+ *
3160
+ * Covers both fresh sandbox creation and restore-from-snapshot (set
3161
+ * `snapshotId`). Blocks until the sandbox is `Running`.
3162
+ */
3163
+ static async create(options) {
3164
+ const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3165
+ const client = new SandboxClient2(
3166
+ options,
3167
+ /* _internal */
3168
+ true
3169
+ );
3170
+ const sandbox = await client.createAndConnect(options);
3171
+ sandbox.lifecycleClient = client;
3172
+ return sandbox;
3173
+ }
3174
+ /**
3175
+ * Attach to an existing sandbox and return a connected handle.
3176
+ *
3177
+ * Verifies the sandbox exists via a server GET call, then returns a handle
3178
+ * in whatever state the sandbox is in. Does **not** auto-resume a suspended
3179
+ * sandbox — call `sandbox.resume()` explicitly.
3180
+ */
3181
+ static async connect(options) {
3182
+ const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3183
+ const client = new SandboxClient2(
3184
+ options,
3185
+ /* _internal */
3186
+ true
3187
+ );
3188
+ await client.get(options.sandboxId);
3189
+ const sandbox = client.connect(options.sandboxId, options.proxyUrl, options.routingHint);
3190
+ sandbox.lifecycleClient = client;
3191
+ return sandbox;
3192
+ }
3193
+ // --- Static snapshot management ---
3194
+ /** Get information about a snapshot by ID. No sandbox handle needed. */
3195
+ static async getSnapshot(snapshotId, options) {
3196
+ const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3197
+ const client = new SandboxClient2(
3198
+ options,
3199
+ /* _internal */
3200
+ true
3201
+ );
3202
+ return client.getSnapshot(snapshotId);
3203
+ }
3204
+ /** Delete a snapshot by ID. No sandbox handle needed. */
3205
+ static async deleteSnapshot(snapshotId, options) {
3206
+ const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3207
+ const client = new SandboxClient2(
3208
+ options,
3209
+ /* _internal */
3210
+ true
3211
+ );
3212
+ await client.deleteSnapshot(snapshotId);
3213
+ }
3214
+ // --- Instance lifecycle methods ---
3215
+ requireLifecycleClient(operation) {
3216
+ if (!this.lifecycleClient) {
3217
+ throw new SandboxError(
3218
+ `Cannot ${operation}: no lifecycle client available. Use Sandbox.create() or Sandbox.connect() to get a lifecycle-aware handle.`
3219
+ );
3220
+ }
3221
+ return this.lifecycleClient;
3222
+ }
3223
+ /**
3224
+ * Suspend this sandbox.
3225
+ *
3226
+ * By default blocks until the sandbox is fully `Suspended`. Pass
3227
+ * `{ wait: false }` for fire-and-return.
3228
+ */
3229
+ async suspend(options) {
3230
+ const client = this.requireLifecycleClient("suspend");
3231
+ await client.suspend(this.sandboxId, options);
3232
+ }
3233
+ /**
3234
+ * Resume this sandbox.
3235
+ *
3236
+ * By default blocks until the sandbox is `Running` and routable. Pass
3237
+ * `{ wait: false }` for fire-and-return.
3238
+ */
3239
+ async resume(options) {
3240
+ const client = this.requireLifecycleClient("resume");
3241
+ await client.resume(this.sandboxId, options);
3242
+ }
3243
+ /**
3244
+ * Create a snapshot of this sandbox's filesystem and wait for it to
3245
+ * be committed.
3246
+ *
3247
+ * By default blocks until the snapshot artifact is ready and returns
3248
+ * the completed `SnapshotInfo`. Pass `{ wait: false }` to fire-and-return
3249
+ * (returns `undefined`).
3250
+ */
3251
+ async checkpoint(options) {
3252
+ const client = this.requireLifecycleClient("checkpoint");
3253
+ if (options?.wait === false) {
3254
+ await client.snapshot(this.sandboxId, { contentMode: options.contentMode });
3255
+ return void 0;
3256
+ }
3257
+ return client.snapshotAndWait(this.sandboxId, {
3258
+ timeout: options?.timeout,
3259
+ pollInterval: options?.pollInterval,
3260
+ contentMode: options?.contentMode
3261
+ });
3262
+ }
3263
+ /**
3264
+ * List snapshots taken from this sandbox.
3265
+ */
3266
+ async listSnapshots() {
3267
+ const client = this.requireLifecycleClient("listSnapshots");
3268
+ const all = await client.listSnapshots();
3269
+ return all.filter((s) => s.sandboxId === this.sandboxId);
3270
+ }
3271
+ /** Close the HTTP client. The sandbox keeps running. */
3137
3272
  close() {
3138
3273
  this.http.close();
3139
3274
  }
3275
+ /** Terminate the sandbox and release all resources. */
3140
3276
  async terminate() {
3141
3277
  const client = this.lifecycleClient;
3142
3278
  this.ownsSandbox = false;
@@ -3189,6 +3325,14 @@ var init_sandbox = __esm({
3189
3325
  };
3190
3326
  }
3191
3327
  // --- Process management ---
3328
+ /**
3329
+ * Start a process in the sandbox without waiting for it to exit.
3330
+ *
3331
+ * Returns a `ProcessInfo` with the assigned `pid`. Use `getProcess()` to
3332
+ * poll status, or `followStdout()` / `followOutput()` to stream output
3333
+ * until the process exits. Use `run()` instead to block until completion
3334
+ * and get combined output in one call.
3335
+ */
3192
3336
  async startProcess(command, options) {
3193
3337
  const payload = { command };
3194
3338
  if (options?.args != null) payload.args = options.args;
@@ -3210,6 +3354,7 @@ var init_sandbox = __esm({
3210
3354
  );
3211
3355
  return fromSnakeKeys(raw);
3212
3356
  }
3357
+ /** List all processes (running and exited) tracked by the sandbox daemon. */
3213
3358
  async listProcesses() {
3214
3359
  const raw = await this.http.requestJson(
3215
3360
  "GET",
@@ -3217,6 +3362,7 @@ var init_sandbox = __esm({
3217
3362
  );
3218
3363
  return (raw.processes ?? []).map((p) => fromSnakeKeys(p));
3219
3364
  }
3365
+ /** Get current status and metadata for a process by PID. */
3220
3366
  async getProcess(pid) {
3221
3367
  const raw = await this.http.requestJson(
3222
3368
  "GET",
@@ -3224,9 +3370,11 @@ var init_sandbox = __esm({
3224
3370
  );
3225
3371
  return fromSnakeKeys(raw);
3226
3372
  }
3373
+ /** Send SIGKILL to a process. */
3227
3374
  async killProcess(pid) {
3228
3375
  await this.http.requestJson("DELETE", `/api/v1/processes/${pid}`);
3229
3376
  }
3377
+ /** Send an arbitrary signal to a process (e.g. `15` for SIGTERM, `9` for SIGKILL). */
3230
3378
  async sendSignal(pid, signal) {
3231
3379
  const raw = await this.http.requestJson(
3232
3380
  "POST",
@@ -3236,15 +3384,18 @@ var init_sandbox = __esm({
3236
3384
  return fromSnakeKeys(raw);
3237
3385
  }
3238
3386
  // --- Process I/O ---
3387
+ /** Write bytes to a process's stdin. The process must have been started with `stdinMode: StdinMode.PIPE`. */
3239
3388
  async writeStdin(pid, data) {
3240
3389
  await this.http.requestBytes("POST", `/api/v1/processes/${pid}/stdin`, {
3241
3390
  body: data,
3242
3391
  contentType: "application/octet-stream"
3243
3392
  });
3244
3393
  }
3394
+ /** Close a process's stdin pipe, signalling EOF to the process. */
3245
3395
  async closeStdin(pid) {
3246
3396
  await this.http.requestJson("POST", `/api/v1/processes/${pid}/stdin/close`);
3247
3397
  }
3398
+ /** Return all captured stdout lines produced so far by a process. */
3248
3399
  async getStdout(pid) {
3249
3400
  const raw = await this.http.requestJson(
3250
3401
  "GET",
@@ -3252,6 +3403,7 @@ var init_sandbox = __esm({
3252
3403
  );
3253
3404
  return fromSnakeKeys(raw);
3254
3405
  }
3406
+ /** Return all captured stderr lines produced so far by a process. */
3255
3407
  async getStderr(pid) {
3256
3408
  const raw = await this.http.requestJson(
3257
3409
  "GET",
@@ -3259,6 +3411,7 @@ var init_sandbox = __esm({
3259
3411
  );
3260
3412
  return fromSnakeKeys(raw);
3261
3413
  }
3414
+ /** Return all captured stdout+stderr lines produced so far by a process. */
3262
3415
  async getOutput(pid) {
3263
3416
  const raw = await this.http.requestJson(
3264
3417
  "GET",
@@ -3267,6 +3420,7 @@ var init_sandbox = __esm({
3267
3420
  return fromSnakeKeys(raw);
3268
3421
  }
3269
3422
  // --- Streaming (SSE) ---
3423
+ /** Stream stdout events from a process until it exits. Yields one `OutputEvent` per line. */
3270
3424
  async *followStdout(pid, options) {
3271
3425
  const stream = await this.http.requestStream(
3272
3426
  "GET",
@@ -3280,6 +3434,7 @@ var init_sandbox = __esm({
3280
3434
  yield fromSnakeKeys(raw);
3281
3435
  }
3282
3436
  }
3437
+ /** Stream stderr events from a process until it exits. Yields one `OutputEvent` per line. */
3283
3438
  async *followStderr(pid, options) {
3284
3439
  const stream = await this.http.requestStream(
3285
3440
  "GET",
@@ -3293,6 +3448,7 @@ var init_sandbox = __esm({
3293
3448
  yield fromSnakeKeys(raw);
3294
3449
  }
3295
3450
  }
3451
+ /** Stream combined stdout+stderr events from a process until it exits. Yields one `OutputEvent` per line. */
3296
3452
  async *followOutput(pid, options) {
3297
3453
  const stream = await this.http.requestStream(
3298
3454
  "GET",
@@ -3307,12 +3463,14 @@ var init_sandbox = __esm({
3307
3463
  }
3308
3464
  }
3309
3465
  // --- File operations ---
3466
+ /** Read a file from the sandbox and return its raw bytes. */
3310
3467
  async readFile(path2) {
3311
3468
  return this.http.requestBytes(
3312
3469
  "GET",
3313
3470
  `/api/v1/files?path=${encodeURIComponent(path2)}`
3314
3471
  );
3315
3472
  }
3473
+ /** Write raw bytes to a file in the sandbox, creating it if it does not exist. */
3316
3474
  async writeFile(path2, content) {
3317
3475
  await this.http.requestBytes(
3318
3476
  "PUT",
@@ -3320,12 +3478,14 @@ var init_sandbox = __esm({
3320
3478
  { body: content, contentType: "application/octet-stream" }
3321
3479
  );
3322
3480
  }
3481
+ /** Delete a file from the sandbox. */
3323
3482
  async deleteFile(path2) {
3324
3483
  await this.http.requestJson(
3325
3484
  "DELETE",
3326
3485
  `/api/v1/files?path=${encodeURIComponent(path2)}`
3327
3486
  );
3328
3487
  }
3488
+ /** List the contents of a directory in the sandbox. */
3329
3489
  async listDirectory(path2) {
3330
3490
  const raw = await this.http.requestJson(
3331
3491
  "GET",
@@ -3334,6 +3494,7 @@ var init_sandbox = __esm({
3334
3494
  return fromSnakeKeys(raw);
3335
3495
  }
3336
3496
  // --- PTY ---
3497
+ /** Create an interactive PTY session. Returns a `sessionId` and `token` for WebSocket connection via `connectPty()`. */
3337
3498
  async createPtySession(options) {
3338
3499
  const payload = {
3339
3500
  command: options.command,
@@ -3350,6 +3511,7 @@ var init_sandbox = __esm({
3350
3511
  );
3351
3512
  return fromSnakeKeys(raw);
3352
3513
  }
3514
+ /** Create a PTY session and connect to it immediately. Cleans up the session if the WebSocket connection fails. */
3353
3515
  async createPty(options) {
3354
3516
  const { onData, onExit, ...createOptions } = options;
3355
3517
  const session = await this.createPtySession(createOptions);
@@ -3363,6 +3525,7 @@ var init_sandbox = __esm({
3363
3525
  throw error;
3364
3526
  }
3365
3527
  }
3528
+ /** Attach to an existing PTY session by ID and token and return a connected `Pty` handle. */
3366
3529
  async connectPty(sessionId, token, options) {
3367
3530
  const wsUrl = new URL(this.ptyWsUrl(sessionId, token));
3368
3531
  const authToken = wsUrl.searchParams.get("token") ?? token;
@@ -3387,6 +3550,7 @@ var init_sandbox = __esm({
3387
3550
  await pty.connect();
3388
3551
  return pty;
3389
3552
  }
3553
+ /** Open a TCP tunnel to a port inside the sandbox and return the local listener. */
3390
3554
  async createTunnel(remotePort, options) {
3391
3555
  return TcpTunnel.listen({
3392
3556
  baseUrl: this.baseUrl,
@@ -3397,6 +3561,7 @@ var init_sandbox = __esm({
3397
3561
  connectTimeout: options?.connectTimeout
3398
3562
  });
3399
3563
  }
3564
+ /** Connect to a sandbox VNC session for programmatic desktop control. */
3400
3565
  async connectDesktop(options) {
3401
3566
  return Desktop.connect({
3402
3567
  baseUrl: this.baseUrl,
@@ -3419,6 +3584,7 @@ var init_sandbox = __esm({
3419
3584
  return `${wsBase}/api/v1/pty/${sessionId}/ws?token=${token}`;
3420
3585
  }
3421
3586
  // --- Health ---
3587
+ /** Check the sandbox daemon health. */
3422
3588
  async health() {
3423
3589
  const raw = await this.http.requestJson(
3424
3590
  "GET",
@@ -3426,6 +3592,7 @@ var init_sandbox = __esm({
3426
3592
  );
3427
3593
  return fromSnakeKeys(raw);
3428
3594
  }
3595
+ /** Get sandbox daemon info (version, uptime, process counts). */
3429
3596
  async info() {
3430
3597
  const raw = await this.http.requestJson(
3431
3598
  "GET",
@@ -3438,6 +3605,10 @@ var init_sandbox = __esm({
3438
3605
  });
3439
3606
 
3440
3607
  // src/client.ts
3608
+ var client_exports = {};
3609
+ __export(client_exports, {
3610
+ SandboxClient: () => SandboxClient
3611
+ });
3441
3612
  function sleep2(ms) {
3442
3613
  return new Promise((resolve) => setTimeout(resolve, ms));
3443
3614
  }
@@ -3474,7 +3645,13 @@ var init_client = __esm({
3474
3645
  projectId;
3475
3646
  namespace;
3476
3647
  local;
3477
- constructor(options) {
3648
+ /** @internal Pass `true` to suppress the deprecation warning when used by `Sandbox.create()` / `Sandbox.connect()`. */
3649
+ constructor(options, _internal = false) {
3650
+ if (!_internal) {
3651
+ console.warn(
3652
+ "[tensorlake] SandboxClient is deprecated; use Sandbox.create() / Sandbox.connect() instead."
3653
+ );
3654
+ }
3478
3655
  this.apiUrl = options?.apiUrl ?? API_URL;
3479
3656
  this.apiKey = options?.apiKey ?? API_KEY;
3480
3657
  this.organizationId = options?.organizationId;
@@ -3514,6 +3691,7 @@ var init_client = __esm({
3514
3691
  return lifecyclePath(subpath, this.local, this.namespace);
3515
3692
  }
3516
3693
  // --- Sandbox CRUD ---
3694
+ /** Create a new sandbox. Returns immediately; the sandbox may still be starting. Use `createAndConnect()` for a blocking, ready-to-use handle. */
3517
3695
  async create(options) {
3518
3696
  const body = {
3519
3697
  resources: {
@@ -3540,8 +3718,10 @@ var init_client = __esm({
3540
3718
  this.path("sandboxes"),
3541
3719
  { body }
3542
3720
  );
3543
- return fromSnakeKeys(raw, "sandboxId");
3721
+ const result = fromSnakeKeys(raw, "sandboxId");
3722
+ return Object.assign(result, { traceId: raw.traceId });
3544
3723
  }
3724
+ /** Get current state and metadata for a sandbox by ID. */
3545
3725
  async get(sandboxId) {
3546
3726
  const raw = await this.http.requestJson(
3547
3727
  "GET",
@@ -3549,6 +3729,7 @@ var init_client = __esm({
3549
3729
  );
3550
3730
  return fromSnakeKeys(raw, "sandboxId");
3551
3731
  }
3732
+ /** List all sandboxes in the namespace. */
3552
3733
  async list() {
3553
3734
  const raw = await this.http.requestJson(
3554
3735
  "GET",
@@ -3558,6 +3739,7 @@ var init_client = __esm({
3558
3739
  (s) => fromSnakeKeys(s, "sandboxId")
3559
3740
  );
3560
3741
  }
3742
+ /** Update sandbox properties such as name, exposed ports, and proxy auth settings. */
3561
3743
  async update(sandboxId, options) {
3562
3744
  const body = {};
3563
3745
  if (options.name != null) body.name = options.name;
@@ -3577,6 +3759,7 @@ var init_client = __esm({
3577
3759
  );
3578
3760
  return fromSnakeKeys(raw, "sandboxId");
3579
3761
  }
3762
+ /** Get the current proxy port settings for a sandbox. */
3580
3763
  async getPortAccess(sandboxId) {
3581
3764
  const info = await this.get(sandboxId);
3582
3765
  return {
@@ -3585,6 +3768,7 @@ var init_client = __esm({
3585
3768
  sandboxUrl: info.sandboxUrl
3586
3769
  };
3587
3770
  }
3771
+ /** Add one or more user ports to the sandbox proxy allowlist. */
3588
3772
  async exposePorts(sandboxId, ports, options) {
3589
3773
  const requestedPorts = normalizeUserPorts(ports);
3590
3774
  const current = await this.getPortAccess(sandboxId);
@@ -3597,6 +3781,7 @@ var init_client = __esm({
3597
3781
  exposedPorts: desiredPorts
3598
3782
  });
3599
3783
  }
3784
+ /** Remove one or more user ports from the sandbox proxy allowlist. */
3600
3785
  async unexposePorts(sandboxId, ports) {
3601
3786
  const requestedPorts = normalizeUserPorts(ports);
3602
3787
  const current = await this.getPortAccess(sandboxId);
@@ -3607,32 +3792,98 @@ var init_client = __esm({
3607
3792
  exposedPorts: desiredPorts
3608
3793
  });
3609
3794
  }
3795
+ /** Terminate and delete a sandbox. */
3610
3796
  async delete(sandboxId) {
3611
3797
  await this.http.requestJson(
3612
3798
  "DELETE",
3613
3799
  this.path(`sandboxes/${sandboxId}`)
3614
3800
  );
3615
3801
  }
3616
- async suspend(sandboxId) {
3802
+ /**
3803
+ * Suspend a named sandbox, preserving its state for later resume.
3804
+ *
3805
+ * Only sandboxes created with a `name` can be suspended; ephemeral sandboxes
3806
+ * cannot. By default blocks until the sandbox is fully `Suspended`. Pass
3807
+ * `{ wait: false }` to return immediately after the request is sent
3808
+ * (fire-and-return); the server processes the suspend asynchronously.
3809
+ *
3810
+ * @param sandboxId - ID or name of the sandbox.
3811
+ * @param options.wait - If `true` (default), poll until `Suspended`. Pass `false` to fire-and-return.
3812
+ * @param options.timeout - Max seconds to wait when `wait=true` (default 300).
3813
+ * @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
3814
+ * @throws {SandboxError} If `wait=true` and the sandbox does not reach `Suspended` within `timeout`.
3815
+ */
3816
+ async suspend(sandboxId, options) {
3617
3817
  await this.http.requestResponse(
3618
3818
  "POST",
3619
3819
  this.path(`sandboxes/${sandboxId}/suspend`)
3620
3820
  );
3821
+ if (options?.wait === false) return;
3822
+ const timeout = options?.timeout ?? 300;
3823
+ const pollInterval = options?.pollInterval ?? 1;
3824
+ const deadline = Date.now() + timeout * 1e3;
3825
+ while (Date.now() < deadline) {
3826
+ const info = await this.get(sandboxId);
3827
+ if (info.status === "suspended" /* SUSPENDED */) return;
3828
+ if (info.status === "terminated" /* TERMINATED */) {
3829
+ throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for suspend`);
3830
+ }
3831
+ await sleep2(pollInterval * 1e3);
3832
+ }
3833
+ throw new SandboxError(`Sandbox ${sandboxId} did not suspend within ${timeout}s`);
3621
3834
  }
3622
- async resume(sandboxId) {
3835
+ /**
3836
+ * Resume a suspended sandbox and bring it back to `Running`.
3837
+ *
3838
+ * By default blocks until the sandbox is `Running` and routable. Pass
3839
+ * `{ wait: false }` to return immediately after the request is sent
3840
+ * (fire-and-return); the server processes the resume asynchronously.
3841
+ *
3842
+ * @param sandboxId - ID or name of the sandbox.
3843
+ * @param options.wait - If `true` (default), poll until `Running`. Pass `false` to fire-and-return.
3844
+ * @param options.timeout - Max seconds to wait when `wait=true` (default 300).
3845
+ * @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
3846
+ * @throws {SandboxError} If `wait=true` and the sandbox does not reach `Running` within `timeout`.
3847
+ */
3848
+ async resume(sandboxId, options) {
3623
3849
  await this.http.requestResponse(
3624
3850
  "POST",
3625
3851
  this.path(`sandboxes/${sandboxId}/resume`)
3626
3852
  );
3853
+ if (options?.wait === false) return;
3854
+ const timeout = options?.timeout ?? 300;
3855
+ const pollInterval = options?.pollInterval ?? 1;
3856
+ const deadline = Date.now() + timeout * 1e3;
3857
+ while (Date.now() < deadline) {
3858
+ const info = await this.get(sandboxId);
3859
+ if (info.status === "running" /* RUNNING */) return;
3860
+ if (info.status === "terminated" /* TERMINATED */) {
3861
+ throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for resume`);
3862
+ }
3863
+ await sleep2(pollInterval * 1e3);
3864
+ }
3865
+ throw new SandboxError(`Sandbox ${sandboxId} did not resume within ${timeout}s`);
3627
3866
  }
3867
+ /** Claim a warm sandbox from a pool, creating one if no warm containers are available. */
3628
3868
  async claim(poolId) {
3629
3869
  const raw = await this.http.requestJson(
3630
3870
  "POST",
3631
3871
  this.path(`sandbox-pools/${poolId}/sandboxes`)
3632
3872
  );
3633
- return fromSnakeKeys(raw, "sandboxId");
3873
+ const result = fromSnakeKeys(raw, "sandboxId");
3874
+ return Object.assign(result, { traceId: raw.traceId });
3634
3875
  }
3635
3876
  // --- Snapshots ---
3877
+ /**
3878
+ * Request a snapshot of a running sandbox's filesystem.
3879
+ *
3880
+ * This call **returns immediately** with a `snapshotId` and `in_progress`
3881
+ * status — the snapshot is created asynchronously. Poll `getSnapshot()` until
3882
+ * `completed` or `failed`, or use `snapshotAndWait()` to block automatically.
3883
+ *
3884
+ * @param options.contentMode - `"filesystem_only"` for cold-boot snapshots (e.g. image builds).
3885
+ * Omit to use the server default (full VM snapshot).
3886
+ */
3636
3887
  async snapshot(sandboxId, options) {
3637
3888
  const requestOptions = options?.contentMode != null ? { body: { snapshot_content_mode: options.contentMode } } : void 0;
3638
3889
  const raw = await this.http.requestJson(
@@ -3642,6 +3893,7 @@ var init_client = __esm({
3642
3893
  );
3643
3894
  return fromSnakeKeys(raw, "snapshotId");
3644
3895
  }
3896
+ /** Get current status and metadata for a snapshot by ID. */
3645
3897
  async getSnapshot(snapshotId) {
3646
3898
  const raw = await this.http.requestJson(
3647
3899
  "GET",
@@ -3649,6 +3901,7 @@ var init_client = __esm({
3649
3901
  );
3650
3902
  return fromSnakeKeys(raw, "snapshotId");
3651
3903
  }
3904
+ /** List all snapshots in the namespace. */
3652
3905
  async listSnapshots() {
3653
3906
  const raw = await this.http.requestJson(
3654
3907
  "GET",
@@ -3658,12 +3911,26 @@ var init_client = __esm({
3658
3911
  (s) => fromSnakeKeys(s, "snapshotId")
3659
3912
  );
3660
3913
  }
3914
+ /** Delete a snapshot by ID. */
3661
3915
  async deleteSnapshot(snapshotId) {
3662
3916
  await this.http.requestJson(
3663
3917
  "DELETE",
3664
3918
  this.path(`snapshots/${snapshotId}`)
3665
3919
  );
3666
3920
  }
3921
+ /**
3922
+ * Create a snapshot and block until it is committed.
3923
+ *
3924
+ * Combines `snapshot()` with polling `getSnapshot()` until `completed`.
3925
+ * Prefer `sandbox.checkpoint()` on a `Sandbox` handle for the same behavior
3926
+ * without managing the client separately.
3927
+ *
3928
+ * @param sandboxId - ID of the running sandbox to snapshot.
3929
+ * @param options.timeout - Max seconds to wait (default 300).
3930
+ * @param options.pollInterval - Seconds between status polls (default 1).
3931
+ * @param options.contentMode - Content mode passed through to `snapshot()`.
3932
+ * @throws {SandboxError} If the snapshot fails or `timeout` elapses.
3933
+ */
3667
3934
  async snapshotAndWait(sandboxId, options) {
3668
3935
  const timeout = options?.timeout ?? 300;
3669
3936
  const pollInterval = options?.pollInterval ?? 1;
@@ -3686,6 +3953,7 @@ var init_client = __esm({
3686
3953
  );
3687
3954
  }
3688
3955
  // --- Pools ---
3956
+ /** Create a new sandbox pool with warm pre-booted containers. */
3689
3957
  async createPool(options) {
3690
3958
  const body = {
3691
3959
  image: options.image,
@@ -3707,6 +3975,7 @@ var init_client = __esm({
3707
3975
  );
3708
3976
  return fromSnakeKeys(raw, "poolId");
3709
3977
  }
3978
+ /** Get current state and metadata for a sandbox pool by ID. */
3710
3979
  async getPool(poolId) {
3711
3980
  const raw = await this.http.requestJson(
3712
3981
  "GET",
@@ -3714,6 +3983,7 @@ var init_client = __esm({
3714
3983
  );
3715
3984
  return fromSnakeKeys(raw, "poolId");
3716
3985
  }
3986
+ /** List all sandbox pools in the namespace. */
3717
3987
  async listPools() {
3718
3988
  const raw = await this.http.requestJson(
3719
3989
  "GET",
@@ -3723,6 +3993,7 @@ var init_client = __esm({
3723
3993
  (p) => fromSnakeKeys(p, "poolId")
3724
3994
  );
3725
3995
  }
3996
+ /** Replace the configuration of an existing sandbox pool. */
3726
3997
  async updatePool(poolId, options) {
3727
3998
  const body = {
3728
3999
  image: options.image,
@@ -3744,6 +4015,7 @@ var init_client = __esm({
3744
4015
  );
3745
4016
  return fromSnakeKeys(raw, "poolId");
3746
4017
  }
4018
+ /** Delete a sandbox pool. Fails if the pool has active containers. */
3747
4019
  async deletePool(poolId) {
3748
4020
  await this.http.requestJson(
3749
4021
  "DELETE",
@@ -3751,6 +4023,7 @@ var init_client = __esm({
3751
4023
  );
3752
4024
  }
3753
4025
  // --- Connect ---
4026
+ /** Return a `Sandbox` handle for an existing running sandbox without verifying it exists. */
3754
4027
  connect(identifier, proxyUrl, routingHint) {
3755
4028
  const resolvedProxy = proxyUrl ?? resolveProxyUrl(this.apiUrl);
3756
4029
  return new Sandbox({
@@ -3762,6 +4035,15 @@ var init_client = __esm({
3762
4035
  routingHint
3763
4036
  });
3764
4037
  }
4038
+ /**
4039
+ * Create a sandbox, wait for it to reach `Running`, and return a connected handle.
4040
+ *
4041
+ * Blocks until the sandbox is ready or `startupTimeout` elapses. The returned
4042
+ * `Sandbox` auto-terminates when `terminate()` is called.
4043
+ *
4044
+ * @param options.startupTimeout - Max seconds to wait for `Running` status (default 60).
4045
+ * @throws {SandboxError} If the sandbox terminates during startup or the timeout elapses.
4046
+ */
3765
4047
  async createAndConnect(options) {
3766
4048
  const startupTimeout = options?.startupTimeout ?? 60;
3767
4049
  let result;
@@ -3773,6 +4055,7 @@ var init_client = __esm({
3773
4055
  if (result.status === "running" /* RUNNING */) {
3774
4056
  const sandbox = this.connect(result.sandboxId, options?.proxyUrl, result.routingHint);
3775
4057
  sandbox._setOwner(this);
4058
+ sandbox.traceId = result.traceId;
3776
4059
  return sandbox;
3777
4060
  }
3778
4061
  const deadline = Date.now() + startupTimeout * 1e3;
@@ -3781,6 +4064,7 @@ var init_client = __esm({
3781
4064
  if (info.status === "running" /* RUNNING */) {
3782
4065
  const sandbox = this.connect(result.sandboxId, options?.proxyUrl, info.routingHint);
3783
4066
  sandbox._setOwner(this);
4067
+ sandbox.traceId = result.traceId;
3784
4068
  return sandbox;
3785
4069
  }
3786
4070
  if (info.status === "terminated" /* TERMINATED */) {