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.
@@ -1,7 +1,12 @@
1
+ var __defProp = Object.defineProperty;
1
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
2
3
  var __esm = (fn, res) => function __init() {
3
4
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
5
  };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
5
10
 
6
11
  // src/models.ts
7
12
  function snakeToCamel(str) {
@@ -52,10 +57,11 @@ var init_models = __esm({
52
57
  });
53
58
 
54
59
  // src/defaults.ts
55
- var API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TIMEOUT_MS, MAX_RETRIES, RETRY_BACKOFF_MS, RETRYABLE_STATUS_CODES;
60
+ var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TIMEOUT_MS, MAX_RETRIES, RETRY_BACKOFF_MS, RETRYABLE_STATUS_CODES;
56
61
  var init_defaults = __esm({
57
62
  "src/defaults.ts"() {
58
63
  "use strict";
64
+ SDK_VERSION = "0.4.49";
59
65
  API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
60
66
  API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
61
67
  NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
@@ -134,6 +140,12 @@ import {
134
140
  fetch as undiciFetch,
135
141
  setGlobalDispatcher
136
142
  } from "undici";
143
+ function withTraceId(value, traceId) {
144
+ if (value == null) {
145
+ return { traceId };
146
+ }
147
+ return Object.assign(value, { traceId });
148
+ }
137
149
  function hasHeader(headers, name) {
138
150
  const lowered = name.toLowerCase();
139
151
  return Object.keys(headers).some((key) => key.toLowerCase() === lowered);
@@ -202,7 +214,7 @@ var init_http = __esm({
202
214
  allowH2: true
203
215
  })
204
216
  );
205
- HttpClient = class {
217
+ HttpClient = class _HttpClient {
206
218
  baseUrl;
207
219
  headers;
208
220
  maxRetries;
@@ -215,7 +227,9 @@ var init_http = __esm({
215
227
  this.maxRetries = options.maxRetries ?? MAX_RETRIES;
216
228
  this.retryBackoffMs = options.retryBackoffMs ?? RETRY_BACKOFF_MS;
217
229
  this.timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
218
- this.headers = {};
230
+ this.headers = {
231
+ "User-Agent": `tensorlake-typescript-sdk/${SDK_VERSION}`
232
+ };
219
233
  if (options.apiKey) {
220
234
  this.headers["Authorization"] = `Bearer ${options.apiKey}`;
221
235
  }
@@ -244,8 +258,8 @@ var init_http = __esm({
244
258
  signal: options?.signal
245
259
  });
246
260
  const text = await response.text();
247
- if (!text) return void 0;
248
- return JSON.parse(text);
261
+ const data = text ? JSON.parse(text) : void 0;
262
+ return withTraceId(data, response.traceId);
249
263
  }
250
264
  /** Make a request returning raw bytes. */
251
265
  async requestBytes(method, path2, options) {
@@ -259,7 +273,7 @@ var init_http = __esm({
259
273
  signal: options?.signal
260
274
  });
261
275
  const buffer = await response.arrayBuffer();
262
- return new Uint8Array(buffer);
276
+ return withTraceId(new Uint8Array(buffer), response.traceId);
263
277
  }
264
278
  /** Make a request and return the response body as an SSE stream. */
265
279
  async requestStream(method, path2, options) {
@@ -274,7 +288,7 @@ var init_http = __esm({
274
288
  "No response body for SSE stream"
275
289
  );
276
290
  }
277
- return response.body;
291
+ return withTraceId(response.body, response.traceId);
278
292
  }
279
293
  /** Make a request and return the raw Response. */
280
294
  async requestResponse(method, path2, options) {
@@ -287,7 +301,7 @@ var init_http = __esm({
287
301
  headers["Content-Type"] = "application/json";
288
302
  }
289
303
  const body = hasJsonBody ? JSON.stringify(options?.json) : normalizeRequestBody(options?.body);
290
- return this.doFetch(
304
+ const { response, traceId } = await this.doFetch(
291
305
  method,
292
306
  path2,
293
307
  body,
@@ -295,9 +309,18 @@ var init_http = __esm({
295
309
  options?.signal,
296
310
  options?.allowHttpErrors ?? false
297
311
  );
312
+ return withTraceId(response, traceId);
313
+ }
314
+ static makeTraceparent() {
315
+ const randomHex = (bytes) => Array.from(crypto.getRandomValues(new Uint8Array(bytes))).map((b) => b.toString(16).padStart(2, "0")).join("");
316
+ const traceId = randomHex(16);
317
+ const spanId = randomHex(8);
318
+ return { traceparent: `00-${traceId}-${spanId}-01`, traceId };
298
319
  }
299
320
  async doFetch(method, path2, body, headers, signal, allowHttpErrors = false) {
300
321
  const url = `${this.baseUrl}${path2}`;
322
+ const { traceparent, traceId } = _HttpClient.makeTraceparent();
323
+ headers["traceparent"] = traceparent;
301
324
  let lastError;
302
325
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
303
326
  if (attempt > 0) {
@@ -318,7 +341,7 @@ var init_http = __esm({
318
341
  signal: combinedSignal
319
342
  });
320
343
  clearTimeout(timeoutId);
321
- if (response.ok) return response;
344
+ if (response.ok) return { response, traceId };
322
345
  if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < this.maxRetries) {
323
346
  lastError = new RemoteAPIError(
324
347
  response.status,
@@ -327,7 +350,7 @@ var init_http = __esm({
327
350
  continue;
328
351
  }
329
352
  if (allowHttpErrors) {
330
- return response;
353
+ return { response, traceId };
331
354
  }
332
355
  const errorBody = await response.text().catch(() => "");
333
356
  throwMappedError(response.status, errorBody, path2);
@@ -3074,6 +3097,7 @@ var init_sandbox = __esm({
3074
3097
  };
3075
3098
  Sandbox = class {
3076
3099
  sandboxId;
3100
+ traceId = null;
3077
3101
  http;
3078
3102
  baseUrl;
3079
3103
  wsHeaders;
@@ -3111,9 +3135,126 @@ var init_sandbox = __esm({
3111
3135
  this.ownsSandbox = true;
3112
3136
  this.lifecycleClient = client;
3113
3137
  }
3138
+ // --- Static factory methods ---
3139
+ /**
3140
+ * Create a new sandbox and return a connected, running handle.
3141
+ *
3142
+ * Covers both fresh sandbox creation and restore-from-snapshot (set
3143
+ * `snapshotId`). Blocks until the sandbox is `Running`.
3144
+ */
3145
+ static async create(options) {
3146
+ const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3147
+ const client = new SandboxClient2(
3148
+ options,
3149
+ /* _internal */
3150
+ true
3151
+ );
3152
+ const sandbox = await client.createAndConnect(options);
3153
+ sandbox.lifecycleClient = client;
3154
+ return sandbox;
3155
+ }
3156
+ /**
3157
+ * Attach to an existing sandbox and return a connected handle.
3158
+ *
3159
+ * Verifies the sandbox exists via a server GET call, then returns a handle
3160
+ * in whatever state the sandbox is in. Does **not** auto-resume a suspended
3161
+ * sandbox — call `sandbox.resume()` explicitly.
3162
+ */
3163
+ static async connect(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
+ await client.get(options.sandboxId);
3171
+ const sandbox = client.connect(options.sandboxId, options.proxyUrl, options.routingHint);
3172
+ sandbox.lifecycleClient = client;
3173
+ return sandbox;
3174
+ }
3175
+ // --- Static snapshot management ---
3176
+ /** Get information about a snapshot by ID. No sandbox handle needed. */
3177
+ static async getSnapshot(snapshotId, options) {
3178
+ const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3179
+ const client = new SandboxClient2(
3180
+ options,
3181
+ /* _internal */
3182
+ true
3183
+ );
3184
+ return client.getSnapshot(snapshotId);
3185
+ }
3186
+ /** Delete a snapshot by ID. No sandbox handle needed. */
3187
+ static async deleteSnapshot(snapshotId, options) {
3188
+ const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3189
+ const client = new SandboxClient2(
3190
+ options,
3191
+ /* _internal */
3192
+ true
3193
+ );
3194
+ await client.deleteSnapshot(snapshotId);
3195
+ }
3196
+ // --- Instance lifecycle methods ---
3197
+ requireLifecycleClient(operation) {
3198
+ if (!this.lifecycleClient) {
3199
+ throw new SandboxError(
3200
+ `Cannot ${operation}: no lifecycle client available. Use Sandbox.create() or Sandbox.connect() to get a lifecycle-aware handle.`
3201
+ );
3202
+ }
3203
+ return this.lifecycleClient;
3204
+ }
3205
+ /**
3206
+ * Suspend this sandbox.
3207
+ *
3208
+ * By default blocks until the sandbox is fully `Suspended`. Pass
3209
+ * `{ wait: false }` for fire-and-return.
3210
+ */
3211
+ async suspend(options) {
3212
+ const client = this.requireLifecycleClient("suspend");
3213
+ await client.suspend(this.sandboxId, options);
3214
+ }
3215
+ /**
3216
+ * Resume this sandbox.
3217
+ *
3218
+ * By default blocks until the sandbox is `Running` and routable. Pass
3219
+ * `{ wait: false }` for fire-and-return.
3220
+ */
3221
+ async resume(options) {
3222
+ const client = this.requireLifecycleClient("resume");
3223
+ await client.resume(this.sandboxId, options);
3224
+ }
3225
+ /**
3226
+ * Create a snapshot of this sandbox's filesystem and wait for it to
3227
+ * be committed.
3228
+ *
3229
+ * By default blocks until the snapshot artifact is ready and returns
3230
+ * the completed `SnapshotInfo`. Pass `{ wait: false }` to fire-and-return
3231
+ * (returns `undefined`).
3232
+ */
3233
+ async checkpoint(options) {
3234
+ const client = this.requireLifecycleClient("checkpoint");
3235
+ if (options?.wait === false) {
3236
+ await client.snapshot(this.sandboxId, { contentMode: options.contentMode });
3237
+ return void 0;
3238
+ }
3239
+ return client.snapshotAndWait(this.sandboxId, {
3240
+ timeout: options?.timeout,
3241
+ pollInterval: options?.pollInterval,
3242
+ contentMode: options?.contentMode
3243
+ });
3244
+ }
3245
+ /**
3246
+ * List snapshots taken from this sandbox.
3247
+ */
3248
+ async listSnapshots() {
3249
+ const client = this.requireLifecycleClient("listSnapshots");
3250
+ const all = await client.listSnapshots();
3251
+ return all.filter((s) => s.sandboxId === this.sandboxId);
3252
+ }
3253
+ /** Close the HTTP client. The sandbox keeps running. */
3114
3254
  close() {
3115
3255
  this.http.close();
3116
3256
  }
3257
+ /** Terminate the sandbox and release all resources. */
3117
3258
  async terminate() {
3118
3259
  const client = this.lifecycleClient;
3119
3260
  this.ownsSandbox = false;
@@ -3166,6 +3307,14 @@ var init_sandbox = __esm({
3166
3307
  };
3167
3308
  }
3168
3309
  // --- Process management ---
3310
+ /**
3311
+ * Start a process in the sandbox without waiting for it to exit.
3312
+ *
3313
+ * Returns a `ProcessInfo` with the assigned `pid`. Use `getProcess()` to
3314
+ * poll status, or `followStdout()` / `followOutput()` to stream output
3315
+ * until the process exits. Use `run()` instead to block until completion
3316
+ * and get combined output in one call.
3317
+ */
3169
3318
  async startProcess(command, options) {
3170
3319
  const payload = { command };
3171
3320
  if (options?.args != null) payload.args = options.args;
@@ -3187,6 +3336,7 @@ var init_sandbox = __esm({
3187
3336
  );
3188
3337
  return fromSnakeKeys(raw);
3189
3338
  }
3339
+ /** List all processes (running and exited) tracked by the sandbox daemon. */
3190
3340
  async listProcesses() {
3191
3341
  const raw = await this.http.requestJson(
3192
3342
  "GET",
@@ -3194,6 +3344,7 @@ var init_sandbox = __esm({
3194
3344
  );
3195
3345
  return (raw.processes ?? []).map((p) => fromSnakeKeys(p));
3196
3346
  }
3347
+ /** Get current status and metadata for a process by PID. */
3197
3348
  async getProcess(pid) {
3198
3349
  const raw = await this.http.requestJson(
3199
3350
  "GET",
@@ -3201,9 +3352,11 @@ var init_sandbox = __esm({
3201
3352
  );
3202
3353
  return fromSnakeKeys(raw);
3203
3354
  }
3355
+ /** Send SIGKILL to a process. */
3204
3356
  async killProcess(pid) {
3205
3357
  await this.http.requestJson("DELETE", `/api/v1/processes/${pid}`);
3206
3358
  }
3359
+ /** Send an arbitrary signal to a process (e.g. `15` for SIGTERM, `9` for SIGKILL). */
3207
3360
  async sendSignal(pid, signal) {
3208
3361
  const raw = await this.http.requestJson(
3209
3362
  "POST",
@@ -3213,15 +3366,18 @@ var init_sandbox = __esm({
3213
3366
  return fromSnakeKeys(raw);
3214
3367
  }
3215
3368
  // --- Process I/O ---
3369
+ /** Write bytes to a process's stdin. The process must have been started with `stdinMode: StdinMode.PIPE`. */
3216
3370
  async writeStdin(pid, data) {
3217
3371
  await this.http.requestBytes("POST", `/api/v1/processes/${pid}/stdin`, {
3218
3372
  body: data,
3219
3373
  contentType: "application/octet-stream"
3220
3374
  });
3221
3375
  }
3376
+ /** Close a process's stdin pipe, signalling EOF to the process. */
3222
3377
  async closeStdin(pid) {
3223
3378
  await this.http.requestJson("POST", `/api/v1/processes/${pid}/stdin/close`);
3224
3379
  }
3380
+ /** Return all captured stdout lines produced so far by a process. */
3225
3381
  async getStdout(pid) {
3226
3382
  const raw = await this.http.requestJson(
3227
3383
  "GET",
@@ -3229,6 +3385,7 @@ var init_sandbox = __esm({
3229
3385
  );
3230
3386
  return fromSnakeKeys(raw);
3231
3387
  }
3388
+ /** Return all captured stderr lines produced so far by a process. */
3232
3389
  async getStderr(pid) {
3233
3390
  const raw = await this.http.requestJson(
3234
3391
  "GET",
@@ -3236,6 +3393,7 @@ var init_sandbox = __esm({
3236
3393
  );
3237
3394
  return fromSnakeKeys(raw);
3238
3395
  }
3396
+ /** Return all captured stdout+stderr lines produced so far by a process. */
3239
3397
  async getOutput(pid) {
3240
3398
  const raw = await this.http.requestJson(
3241
3399
  "GET",
@@ -3244,6 +3402,7 @@ var init_sandbox = __esm({
3244
3402
  return fromSnakeKeys(raw);
3245
3403
  }
3246
3404
  // --- Streaming (SSE) ---
3405
+ /** Stream stdout events from a process until it exits. Yields one `OutputEvent` per line. */
3247
3406
  async *followStdout(pid, options) {
3248
3407
  const stream = await this.http.requestStream(
3249
3408
  "GET",
@@ -3257,6 +3416,7 @@ var init_sandbox = __esm({
3257
3416
  yield fromSnakeKeys(raw);
3258
3417
  }
3259
3418
  }
3419
+ /** Stream stderr events from a process until it exits. Yields one `OutputEvent` per line. */
3260
3420
  async *followStderr(pid, options) {
3261
3421
  const stream = await this.http.requestStream(
3262
3422
  "GET",
@@ -3270,6 +3430,7 @@ var init_sandbox = __esm({
3270
3430
  yield fromSnakeKeys(raw);
3271
3431
  }
3272
3432
  }
3433
+ /** Stream combined stdout+stderr events from a process until it exits. Yields one `OutputEvent` per line. */
3273
3434
  async *followOutput(pid, options) {
3274
3435
  const stream = await this.http.requestStream(
3275
3436
  "GET",
@@ -3284,12 +3445,14 @@ var init_sandbox = __esm({
3284
3445
  }
3285
3446
  }
3286
3447
  // --- File operations ---
3448
+ /** Read a file from the sandbox and return its raw bytes. */
3287
3449
  async readFile(path2) {
3288
3450
  return this.http.requestBytes(
3289
3451
  "GET",
3290
3452
  `/api/v1/files?path=${encodeURIComponent(path2)}`
3291
3453
  );
3292
3454
  }
3455
+ /** Write raw bytes to a file in the sandbox, creating it if it does not exist. */
3293
3456
  async writeFile(path2, content) {
3294
3457
  await this.http.requestBytes(
3295
3458
  "PUT",
@@ -3297,12 +3460,14 @@ var init_sandbox = __esm({
3297
3460
  { body: content, contentType: "application/octet-stream" }
3298
3461
  );
3299
3462
  }
3463
+ /** Delete a file from the sandbox. */
3300
3464
  async deleteFile(path2) {
3301
3465
  await this.http.requestJson(
3302
3466
  "DELETE",
3303
3467
  `/api/v1/files?path=${encodeURIComponent(path2)}`
3304
3468
  );
3305
3469
  }
3470
+ /** List the contents of a directory in the sandbox. */
3306
3471
  async listDirectory(path2) {
3307
3472
  const raw = await this.http.requestJson(
3308
3473
  "GET",
@@ -3311,6 +3476,7 @@ var init_sandbox = __esm({
3311
3476
  return fromSnakeKeys(raw);
3312
3477
  }
3313
3478
  // --- PTY ---
3479
+ /** Create an interactive PTY session. Returns a `sessionId` and `token` for WebSocket connection via `connectPty()`. */
3314
3480
  async createPtySession(options) {
3315
3481
  const payload = {
3316
3482
  command: options.command,
@@ -3327,6 +3493,7 @@ var init_sandbox = __esm({
3327
3493
  );
3328
3494
  return fromSnakeKeys(raw);
3329
3495
  }
3496
+ /** Create a PTY session and connect to it immediately. Cleans up the session if the WebSocket connection fails. */
3330
3497
  async createPty(options) {
3331
3498
  const { onData, onExit, ...createOptions } = options;
3332
3499
  const session = await this.createPtySession(createOptions);
@@ -3340,6 +3507,7 @@ var init_sandbox = __esm({
3340
3507
  throw error;
3341
3508
  }
3342
3509
  }
3510
+ /** Attach to an existing PTY session by ID and token and return a connected `Pty` handle. */
3343
3511
  async connectPty(sessionId, token, options) {
3344
3512
  const wsUrl = new URL(this.ptyWsUrl(sessionId, token));
3345
3513
  const authToken = wsUrl.searchParams.get("token") ?? token;
@@ -3364,6 +3532,7 @@ var init_sandbox = __esm({
3364
3532
  await pty.connect();
3365
3533
  return pty;
3366
3534
  }
3535
+ /** Open a TCP tunnel to a port inside the sandbox and return the local listener. */
3367
3536
  async createTunnel(remotePort, options) {
3368
3537
  return TcpTunnel.listen({
3369
3538
  baseUrl: this.baseUrl,
@@ -3374,6 +3543,7 @@ var init_sandbox = __esm({
3374
3543
  connectTimeout: options?.connectTimeout
3375
3544
  });
3376
3545
  }
3546
+ /** Connect to a sandbox VNC session for programmatic desktop control. */
3377
3547
  async connectDesktop(options) {
3378
3548
  return Desktop.connect({
3379
3549
  baseUrl: this.baseUrl,
@@ -3396,6 +3566,7 @@ var init_sandbox = __esm({
3396
3566
  return `${wsBase}/api/v1/pty/${sessionId}/ws?token=${token}`;
3397
3567
  }
3398
3568
  // --- Health ---
3569
+ /** Check the sandbox daemon health. */
3399
3570
  async health() {
3400
3571
  const raw = await this.http.requestJson(
3401
3572
  "GET",
@@ -3403,6 +3574,7 @@ var init_sandbox = __esm({
3403
3574
  );
3404
3575
  return fromSnakeKeys(raw);
3405
3576
  }
3577
+ /** Get sandbox daemon info (version, uptime, process counts). */
3406
3578
  async info() {
3407
3579
  const raw = await this.http.requestJson(
3408
3580
  "GET",
@@ -3415,6 +3587,10 @@ var init_sandbox = __esm({
3415
3587
  });
3416
3588
 
3417
3589
  // src/client.ts
3590
+ var client_exports = {};
3591
+ __export(client_exports, {
3592
+ SandboxClient: () => SandboxClient
3593
+ });
3418
3594
  function sleep2(ms) {
3419
3595
  return new Promise((resolve) => setTimeout(resolve, ms));
3420
3596
  }
@@ -3451,7 +3627,13 @@ var init_client = __esm({
3451
3627
  projectId;
3452
3628
  namespace;
3453
3629
  local;
3454
- constructor(options) {
3630
+ /** @internal Pass `true` to suppress the deprecation warning when used by `Sandbox.create()` / `Sandbox.connect()`. */
3631
+ constructor(options, _internal = false) {
3632
+ if (!_internal) {
3633
+ console.warn(
3634
+ "[tensorlake] SandboxClient is deprecated; use Sandbox.create() / Sandbox.connect() instead."
3635
+ );
3636
+ }
3455
3637
  this.apiUrl = options?.apiUrl ?? API_URL;
3456
3638
  this.apiKey = options?.apiKey ?? API_KEY;
3457
3639
  this.organizationId = options?.organizationId;
@@ -3491,6 +3673,7 @@ var init_client = __esm({
3491
3673
  return lifecyclePath(subpath, this.local, this.namespace);
3492
3674
  }
3493
3675
  // --- Sandbox CRUD ---
3676
+ /** Create a new sandbox. Returns immediately; the sandbox may still be starting. Use `createAndConnect()` for a blocking, ready-to-use handle. */
3494
3677
  async create(options) {
3495
3678
  const body = {
3496
3679
  resources: {
@@ -3517,8 +3700,10 @@ var init_client = __esm({
3517
3700
  this.path("sandboxes"),
3518
3701
  { body }
3519
3702
  );
3520
- return fromSnakeKeys(raw, "sandboxId");
3703
+ const result = fromSnakeKeys(raw, "sandboxId");
3704
+ return Object.assign(result, { traceId: raw.traceId });
3521
3705
  }
3706
+ /** Get current state and metadata for a sandbox by ID. */
3522
3707
  async get(sandboxId) {
3523
3708
  const raw = await this.http.requestJson(
3524
3709
  "GET",
@@ -3526,6 +3711,7 @@ var init_client = __esm({
3526
3711
  );
3527
3712
  return fromSnakeKeys(raw, "sandboxId");
3528
3713
  }
3714
+ /** List all sandboxes in the namespace. */
3529
3715
  async list() {
3530
3716
  const raw = await this.http.requestJson(
3531
3717
  "GET",
@@ -3535,6 +3721,7 @@ var init_client = __esm({
3535
3721
  (s) => fromSnakeKeys(s, "sandboxId")
3536
3722
  );
3537
3723
  }
3724
+ /** Update sandbox properties such as name, exposed ports, and proxy auth settings. */
3538
3725
  async update(sandboxId, options) {
3539
3726
  const body = {};
3540
3727
  if (options.name != null) body.name = options.name;
@@ -3554,6 +3741,7 @@ var init_client = __esm({
3554
3741
  );
3555
3742
  return fromSnakeKeys(raw, "sandboxId");
3556
3743
  }
3744
+ /** Get the current proxy port settings for a sandbox. */
3557
3745
  async getPortAccess(sandboxId) {
3558
3746
  const info = await this.get(sandboxId);
3559
3747
  return {
@@ -3562,6 +3750,7 @@ var init_client = __esm({
3562
3750
  sandboxUrl: info.sandboxUrl
3563
3751
  };
3564
3752
  }
3753
+ /** Add one or more user ports to the sandbox proxy allowlist. */
3565
3754
  async exposePorts(sandboxId, ports, options) {
3566
3755
  const requestedPorts = normalizeUserPorts(ports);
3567
3756
  const current = await this.getPortAccess(sandboxId);
@@ -3574,6 +3763,7 @@ var init_client = __esm({
3574
3763
  exposedPorts: desiredPorts
3575
3764
  });
3576
3765
  }
3766
+ /** Remove one or more user ports from the sandbox proxy allowlist. */
3577
3767
  async unexposePorts(sandboxId, ports) {
3578
3768
  const requestedPorts = normalizeUserPorts(ports);
3579
3769
  const current = await this.getPortAccess(sandboxId);
@@ -3584,32 +3774,98 @@ var init_client = __esm({
3584
3774
  exposedPorts: desiredPorts
3585
3775
  });
3586
3776
  }
3777
+ /** Terminate and delete a sandbox. */
3587
3778
  async delete(sandboxId) {
3588
3779
  await this.http.requestJson(
3589
3780
  "DELETE",
3590
3781
  this.path(`sandboxes/${sandboxId}`)
3591
3782
  );
3592
3783
  }
3593
- async suspend(sandboxId) {
3784
+ /**
3785
+ * Suspend a named sandbox, preserving its state for later resume.
3786
+ *
3787
+ * Only sandboxes created with a `name` can be suspended; ephemeral sandboxes
3788
+ * cannot. By default blocks until the sandbox is fully `Suspended`. Pass
3789
+ * `{ wait: false }` to return immediately after the request is sent
3790
+ * (fire-and-return); the server processes the suspend asynchronously.
3791
+ *
3792
+ * @param sandboxId - ID or name of the sandbox.
3793
+ * @param options.wait - If `true` (default), poll until `Suspended`. Pass `false` to fire-and-return.
3794
+ * @param options.timeout - Max seconds to wait when `wait=true` (default 300).
3795
+ * @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
3796
+ * @throws {SandboxError} If `wait=true` and the sandbox does not reach `Suspended` within `timeout`.
3797
+ */
3798
+ async suspend(sandboxId, options) {
3594
3799
  await this.http.requestResponse(
3595
3800
  "POST",
3596
3801
  this.path(`sandboxes/${sandboxId}/suspend`)
3597
3802
  );
3803
+ if (options?.wait === false) return;
3804
+ const timeout = options?.timeout ?? 300;
3805
+ const pollInterval = options?.pollInterval ?? 1;
3806
+ const deadline = Date.now() + timeout * 1e3;
3807
+ while (Date.now() < deadline) {
3808
+ const info = await this.get(sandboxId);
3809
+ if (info.status === "suspended" /* SUSPENDED */) return;
3810
+ if (info.status === "terminated" /* TERMINATED */) {
3811
+ throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for suspend`);
3812
+ }
3813
+ await sleep2(pollInterval * 1e3);
3814
+ }
3815
+ throw new SandboxError(`Sandbox ${sandboxId} did not suspend within ${timeout}s`);
3598
3816
  }
3599
- async resume(sandboxId) {
3817
+ /**
3818
+ * Resume a suspended sandbox and bring it back to `Running`.
3819
+ *
3820
+ * By default blocks until the sandbox is `Running` and routable. Pass
3821
+ * `{ wait: false }` to return immediately after the request is sent
3822
+ * (fire-and-return); the server processes the resume asynchronously.
3823
+ *
3824
+ * @param sandboxId - ID or name of the sandbox.
3825
+ * @param options.wait - If `true` (default), poll until `Running`. Pass `false` to fire-and-return.
3826
+ * @param options.timeout - Max seconds to wait when `wait=true` (default 300).
3827
+ * @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
3828
+ * @throws {SandboxError} If `wait=true` and the sandbox does not reach `Running` within `timeout`.
3829
+ */
3830
+ async resume(sandboxId, options) {
3600
3831
  await this.http.requestResponse(
3601
3832
  "POST",
3602
3833
  this.path(`sandboxes/${sandboxId}/resume`)
3603
3834
  );
3835
+ if (options?.wait === false) return;
3836
+ const timeout = options?.timeout ?? 300;
3837
+ const pollInterval = options?.pollInterval ?? 1;
3838
+ const deadline = Date.now() + timeout * 1e3;
3839
+ while (Date.now() < deadline) {
3840
+ const info = await this.get(sandboxId);
3841
+ if (info.status === "running" /* RUNNING */) return;
3842
+ if (info.status === "terminated" /* TERMINATED */) {
3843
+ throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for resume`);
3844
+ }
3845
+ await sleep2(pollInterval * 1e3);
3846
+ }
3847
+ throw new SandboxError(`Sandbox ${sandboxId} did not resume within ${timeout}s`);
3604
3848
  }
3849
+ /** Claim a warm sandbox from a pool, creating one if no warm containers are available. */
3605
3850
  async claim(poolId) {
3606
3851
  const raw = await this.http.requestJson(
3607
3852
  "POST",
3608
3853
  this.path(`sandbox-pools/${poolId}/sandboxes`)
3609
3854
  );
3610
- return fromSnakeKeys(raw, "sandboxId");
3855
+ const result = fromSnakeKeys(raw, "sandboxId");
3856
+ return Object.assign(result, { traceId: raw.traceId });
3611
3857
  }
3612
3858
  // --- Snapshots ---
3859
+ /**
3860
+ * Request a snapshot of a running sandbox's filesystem.
3861
+ *
3862
+ * This call **returns immediately** with a `snapshotId` and `in_progress`
3863
+ * status — the snapshot is created asynchronously. Poll `getSnapshot()` until
3864
+ * `completed` or `failed`, or use `snapshotAndWait()` to block automatically.
3865
+ *
3866
+ * @param options.contentMode - `"filesystem_only"` for cold-boot snapshots (e.g. image builds).
3867
+ * Omit to use the server default (full VM snapshot).
3868
+ */
3613
3869
  async snapshot(sandboxId, options) {
3614
3870
  const requestOptions = options?.contentMode != null ? { body: { snapshot_content_mode: options.contentMode } } : void 0;
3615
3871
  const raw = await this.http.requestJson(
@@ -3619,6 +3875,7 @@ var init_client = __esm({
3619
3875
  );
3620
3876
  return fromSnakeKeys(raw, "snapshotId");
3621
3877
  }
3878
+ /** Get current status and metadata for a snapshot by ID. */
3622
3879
  async getSnapshot(snapshotId) {
3623
3880
  const raw = await this.http.requestJson(
3624
3881
  "GET",
@@ -3626,6 +3883,7 @@ var init_client = __esm({
3626
3883
  );
3627
3884
  return fromSnakeKeys(raw, "snapshotId");
3628
3885
  }
3886
+ /** List all snapshots in the namespace. */
3629
3887
  async listSnapshots() {
3630
3888
  const raw = await this.http.requestJson(
3631
3889
  "GET",
@@ -3635,12 +3893,26 @@ var init_client = __esm({
3635
3893
  (s) => fromSnakeKeys(s, "snapshotId")
3636
3894
  );
3637
3895
  }
3896
+ /** Delete a snapshot by ID. */
3638
3897
  async deleteSnapshot(snapshotId) {
3639
3898
  await this.http.requestJson(
3640
3899
  "DELETE",
3641
3900
  this.path(`snapshots/${snapshotId}`)
3642
3901
  );
3643
3902
  }
3903
+ /**
3904
+ * Create a snapshot and block until it is committed.
3905
+ *
3906
+ * Combines `snapshot()` with polling `getSnapshot()` until `completed`.
3907
+ * Prefer `sandbox.checkpoint()` on a `Sandbox` handle for the same behavior
3908
+ * without managing the client separately.
3909
+ *
3910
+ * @param sandboxId - ID of the running sandbox to snapshot.
3911
+ * @param options.timeout - Max seconds to wait (default 300).
3912
+ * @param options.pollInterval - Seconds between status polls (default 1).
3913
+ * @param options.contentMode - Content mode passed through to `snapshot()`.
3914
+ * @throws {SandboxError} If the snapshot fails or `timeout` elapses.
3915
+ */
3644
3916
  async snapshotAndWait(sandboxId, options) {
3645
3917
  const timeout = options?.timeout ?? 300;
3646
3918
  const pollInterval = options?.pollInterval ?? 1;
@@ -3663,6 +3935,7 @@ var init_client = __esm({
3663
3935
  );
3664
3936
  }
3665
3937
  // --- Pools ---
3938
+ /** Create a new sandbox pool with warm pre-booted containers. */
3666
3939
  async createPool(options) {
3667
3940
  const body = {
3668
3941
  image: options.image,
@@ -3684,6 +3957,7 @@ var init_client = __esm({
3684
3957
  );
3685
3958
  return fromSnakeKeys(raw, "poolId");
3686
3959
  }
3960
+ /** Get current state and metadata for a sandbox pool by ID. */
3687
3961
  async getPool(poolId) {
3688
3962
  const raw = await this.http.requestJson(
3689
3963
  "GET",
@@ -3691,6 +3965,7 @@ var init_client = __esm({
3691
3965
  );
3692
3966
  return fromSnakeKeys(raw, "poolId");
3693
3967
  }
3968
+ /** List all sandbox pools in the namespace. */
3694
3969
  async listPools() {
3695
3970
  const raw = await this.http.requestJson(
3696
3971
  "GET",
@@ -3700,6 +3975,7 @@ var init_client = __esm({
3700
3975
  (p) => fromSnakeKeys(p, "poolId")
3701
3976
  );
3702
3977
  }
3978
+ /** Replace the configuration of an existing sandbox pool. */
3703
3979
  async updatePool(poolId, options) {
3704
3980
  const body = {
3705
3981
  image: options.image,
@@ -3721,6 +3997,7 @@ var init_client = __esm({
3721
3997
  );
3722
3998
  return fromSnakeKeys(raw, "poolId");
3723
3999
  }
4000
+ /** Delete a sandbox pool. Fails if the pool has active containers. */
3724
4001
  async deletePool(poolId) {
3725
4002
  await this.http.requestJson(
3726
4003
  "DELETE",
@@ -3728,6 +4005,7 @@ var init_client = __esm({
3728
4005
  );
3729
4006
  }
3730
4007
  // --- Connect ---
4008
+ /** Return a `Sandbox` handle for an existing running sandbox without verifying it exists. */
3731
4009
  connect(identifier, proxyUrl, routingHint) {
3732
4010
  const resolvedProxy = proxyUrl ?? resolveProxyUrl(this.apiUrl);
3733
4011
  return new Sandbox({
@@ -3739,6 +4017,15 @@ var init_client = __esm({
3739
4017
  routingHint
3740
4018
  });
3741
4019
  }
4020
+ /**
4021
+ * Create a sandbox, wait for it to reach `Running`, and return a connected handle.
4022
+ *
4023
+ * Blocks until the sandbox is ready or `startupTimeout` elapses. The returned
4024
+ * `Sandbox` auto-terminates when `terminate()` is called.
4025
+ *
4026
+ * @param options.startupTimeout - Max seconds to wait for `Running` status (default 60).
4027
+ * @throws {SandboxError} If the sandbox terminates during startup or the timeout elapses.
4028
+ */
3742
4029
  async createAndConnect(options) {
3743
4030
  const startupTimeout = options?.startupTimeout ?? 60;
3744
4031
  let result;
@@ -3750,6 +4037,7 @@ var init_client = __esm({
3750
4037
  if (result.status === "running" /* RUNNING */) {
3751
4038
  const sandbox = this.connect(result.sandboxId, options?.proxyUrl, result.routingHint);
3752
4039
  sandbox._setOwner(this);
4040
+ sandbox.traceId = result.traceId;
3753
4041
  return sandbox;
3754
4042
  }
3755
4043
  const deadline = Date.now() + startupTimeout * 1e3;
@@ -3758,6 +4046,7 @@ var init_client = __esm({
3758
4046
  if (info.status === "running" /* RUNNING */) {
3759
4047
  const sandbox = this.connect(result.sandboxId, options?.proxyUrl, info.routingHint);
3760
4048
  sandbox._setOwner(this);
4049
+ sandbox.traceId = result.traceId;
3761
4050
  return sandbox;
3762
4051
  }
3763
4052
  if (info.status === "terminated" /* TERMINATED */) {