tensorlake 0.5.5 → 0.5.7

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
@@ -13,7 +13,7 @@ var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TI
13
13
  var init_defaults = __esm({
14
14
  "src/defaults.ts"() {
15
15
  "use strict";
16
- SDK_VERSION = "0.4.49";
16
+ SDK_VERSION = "0.5.7";
17
17
  API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
18
18
  API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
19
19
  NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
@@ -218,6 +218,9 @@ var init_http = __esm({
218
218
  if (options.hostHeader) {
219
219
  this.headers["Host"] = options.hostHeader;
220
220
  }
221
+ if (options.sandboxIdHeader) {
222
+ this.headers["X-Tensorlake-Sandbox-Id"] = options.sandboxIdHeader;
223
+ }
221
224
  if (options.routingHint) {
222
225
  this.headers["X-Tensorlake-Route-Hint"] = options.routingHint;
223
226
  }
@@ -2929,21 +2932,36 @@ function resolveProxyTarget(proxyUrl, sandboxId) {
2929
2932
  if (host === "localhost" || host === "127.0.0.1") {
2930
2933
  return {
2931
2934
  baseUrl: trimTrailingSlashes(proxyUrl),
2932
- hostHeader: `${sandboxId}.local`
2935
+ hostHeader: `${sandboxId}.local`,
2936
+ sandboxIdHeader: void 0
2933
2937
  };
2934
2938
  }
2935
2939
  const port = parsed.port ? `:${parsed.port}` : "";
2936
2940
  return {
2937
- baseUrl: `${parsed.protocol}//${sandboxId}.${host}${port}`,
2938
- hostHeader: void 0
2941
+ baseUrl: `${parsed.protocol}//${host}${port}`,
2942
+ hostHeader: void 0,
2943
+ sandboxIdHeader: sandboxId
2939
2944
  };
2940
2945
  } catch {
2941
2946
  return {
2942
2947
  baseUrl: `${trimTrailingSlashes(proxyUrl)}/${sandboxId}`,
2943
- hostHeader: void 0
2948
+ hostHeader: void 0,
2949
+ sandboxIdHeader: void 0
2944
2950
  };
2945
2951
  }
2946
2952
  }
2953
+ function resolveSandboxLifecycleUrl(apiUrl) {
2954
+ if (isLocalhost(apiUrl)) return apiUrl;
2955
+ try {
2956
+ const parsed = new URL(apiUrl);
2957
+ if (parsed.hostname.startsWith("api.")) {
2958
+ parsed.hostname = "sandbox." + parsed.hostname.slice(4);
2959
+ return parsed.toString().replace(/\/$/, "");
2960
+ }
2961
+ } catch {
2962
+ }
2963
+ return apiUrl;
2964
+ }
2947
2965
  function lifecyclePath(path2, isLocal, namespace) {
2948
2966
  if (isLocal) {
2949
2967
  return `/v1/namespaces/${namespace}/${path2}`;
@@ -3170,7 +3188,7 @@ var init_sandbox = __esm({
3170
3188
  this.sandboxId = options.sandboxId;
3171
3189
  this.lifecycleIdentifier = options.sandboxId;
3172
3190
  const proxyUrl = options.proxyUrl ?? SANDBOX_PROXY_URL;
3173
- const { baseUrl, hostHeader } = resolveProxyTarget(proxyUrl, options.sandboxId);
3191
+ const { baseUrl, hostHeader, sandboxIdHeader } = resolveProxyTarget(proxyUrl, options.sandboxId);
3174
3192
  this.baseUrl = baseUrl;
3175
3193
  this.wsHeaders = {};
3176
3194
  if (options.apiKey) {
@@ -3185,12 +3203,16 @@ var init_sandbox = __esm({
3185
3203
  if (hostHeader) {
3186
3204
  this.wsHeaders.Host = hostHeader;
3187
3205
  }
3206
+ if (sandboxIdHeader) {
3207
+ this.wsHeaders["X-Tensorlake-Sandbox-Id"] = sandboxIdHeader;
3208
+ }
3188
3209
  this.http = new HttpClient({
3189
3210
  baseUrl,
3190
3211
  apiKey: options.apiKey,
3191
3212
  organizationId: options.organizationId,
3192
3213
  projectId: options.projectId,
3193
3214
  hostHeader,
3215
+ sandboxIdHeader,
3194
3216
  routingHint: options.routingHint
3195
3217
  });
3196
3218
  }
@@ -3231,8 +3253,8 @@ var init_sandbox = __esm({
3231
3253
  /**
3232
3254
  * Attach to an existing sandbox and return a connected handle.
3233
3255
  *
3234
- * Verifies the sandbox exists via a server GET call, then returns a handle
3235
- * in whatever state the sandbox is in. Does **not** auto-resume a suspended
3256
+ * Returns immediately without contacting the server. Call `sandbox.info()`
3257
+ * to fetch the current state on demand. Does **not** auto-resume a suspended
3236
3258
  * sandbox — call `sandbox.resume()` explicitly.
3237
3259
  */
3238
3260
  static async connect(options) {
@@ -3242,15 +3264,12 @@ var init_sandbox = __esm({
3242
3264
  /* _internal */
3243
3265
  true
3244
3266
  );
3245
- const info = await client.get(options.sandboxId);
3246
3267
  const sandbox = client.connect(
3247
- info.sandboxId,
3268
+ options.sandboxId,
3248
3269
  options.proxyUrl,
3249
- options.routingHint ?? info.routingHint
3270
+ options.routingHint
3250
3271
  );
3251
3272
  sandbox.lifecycleClient = client;
3252
- sandbox._setLifecycleIdentifier(info.sandboxId);
3253
- sandbox._setName(info.name ?? null);
3254
3273
  return sandbox;
3255
3274
  }
3256
3275
  // --- Static snapshot management ---
@@ -3274,6 +3293,26 @@ var init_sandbox = __esm({
3274
3293
  );
3275
3294
  await client.deleteSnapshot(snapshotId);
3276
3295
  }
3296
+ /** List all sandboxes. No sandbox handle needed. */
3297
+ static async list(options) {
3298
+ const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3299
+ const client = new SandboxClient2(
3300
+ options,
3301
+ /* _internal */
3302
+ true
3303
+ );
3304
+ return client.list();
3305
+ }
3306
+ /** List all snapshots in the project. No sandbox handle needed. */
3307
+ static async listSnapshots(options) {
3308
+ const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3309
+ const client = new SandboxClient2(
3310
+ options,
3311
+ /* _internal */
3312
+ true
3313
+ );
3314
+ return client.listSnapshots();
3315
+ }
3277
3316
  // --- Instance lifecycle methods ---
3278
3317
  requireLifecycleClient(operation) {
3279
3318
  if (!this.lifecycleClient) {
@@ -3283,6 +3322,14 @@ var init_sandbox = __esm({
3283
3322
  }
3284
3323
  return this.lifecycleClient;
3285
3324
  }
3325
+ /** Fetch the current sandbox information from the server on demand. */
3326
+ async info() {
3327
+ const client = this.requireLifecycleClient("info");
3328
+ const info = await client.get(this.lifecycleIdentifier);
3329
+ this._setLifecycleIdentifier(info.sandboxId);
3330
+ this._setName(info.name ?? null);
3331
+ return info;
3332
+ }
3286
3333
  /**
3287
3334
  * Fetch the current sandbox status from the server.
3288
3335
  *
@@ -3390,6 +3437,7 @@ var init_sandbox = __esm({
3390
3437
  "/api/v1/processes/run",
3391
3438
  { json: body }
3392
3439
  );
3440
+ const traceId = sseStream.traceId;
3393
3441
  const stdoutLines = [];
3394
3442
  const stderrLines = [];
3395
3443
  let exitCode = -1;
@@ -3408,11 +3456,10 @@ var init_sandbox = __esm({
3408
3456
  }
3409
3457
  }
3410
3458
  }
3411
- return {
3412
- exitCode,
3413
- stdout: stdoutLines.join("\n"),
3414
- stderr: stderrLines.join("\n")
3415
- };
3459
+ return Object.assign(
3460
+ { exitCode, stdout: stdoutLines.join("\n"), stderr: stderrLines.join("\n") },
3461
+ { traceId }
3462
+ );
3416
3463
  }
3417
3464
  // --- Process management ---
3418
3465
  /**
@@ -3442,7 +3489,7 @@ var init_sandbox = __esm({
3442
3489
  "/api/v1/processes",
3443
3490
  { body: payload }
3444
3491
  );
3445
- return fromSnakeKeys(raw);
3492
+ return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3446
3493
  }
3447
3494
  /** List all processes (running and exited) tracked by the sandbox daemon. */
3448
3495
  async listProcesses() {
@@ -3459,7 +3506,7 @@ var init_sandbox = __esm({
3459
3506
  "GET",
3460
3507
  `/api/v1/processes/${pid}`
3461
3508
  );
3462
- return fromSnakeKeys(raw);
3509
+ return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3463
3510
  }
3464
3511
  /** Send SIGKILL to a process. */
3465
3512
  async killProcess(pid) {
@@ -3472,7 +3519,7 @@ var init_sandbox = __esm({
3472
3519
  `/api/v1/processes/${pid}/signal`,
3473
3520
  { body: { signal } }
3474
3521
  );
3475
- return fromSnakeKeys(raw);
3522
+ return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3476
3523
  }
3477
3524
  // --- Process I/O ---
3478
3525
  /** Write bytes to a process's stdin. The process must have been started with `stdinMode: StdinMode.PIPE`. */
@@ -3492,7 +3539,7 @@ var init_sandbox = __esm({
3492
3539
  "GET",
3493
3540
  `/api/v1/processes/${pid}/stdout`
3494
3541
  );
3495
- return fromSnakeKeys(raw);
3542
+ return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3496
3543
  }
3497
3544
  /** Return all captured stderr lines produced so far by a process. */
3498
3545
  async getStderr(pid) {
@@ -3500,7 +3547,7 @@ var init_sandbox = __esm({
3500
3547
  "GET",
3501
3548
  `/api/v1/processes/${pid}/stderr`
3502
3549
  );
3503
- return fromSnakeKeys(raw);
3550
+ return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3504
3551
  }
3505
3552
  /** Return all captured stdout+stderr lines produced so far by a process. */
3506
3553
  async getOutput(pid) {
@@ -3508,7 +3555,7 @@ var init_sandbox = __esm({
3508
3555
  "GET",
3509
3556
  `/api/v1/processes/${pid}/output`
3510
3557
  );
3511
- return fromSnakeKeys(raw);
3558
+ return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3512
3559
  }
3513
3560
  // --- Streaming (SSE) ---
3514
3561
  /** Stream stdout events from a process until it exits. Yields one `OutputEvent` per line. */
@@ -3582,7 +3629,7 @@ var init_sandbox = __esm({
3582
3629
  "GET",
3583
3630
  `/api/v1/files/list?path=${encodeURIComponent(path2)}`
3584
3631
  );
3585
- return fromSnakeKeys(raw);
3632
+ return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3586
3633
  }
3587
3634
  // --- PTY ---
3588
3635
  /** Create an interactive PTY session. Returns a `sessionId` and `token` for WebSocket connection via `connectPty()`. */
@@ -3600,7 +3647,7 @@ var init_sandbox = __esm({
3600
3647
  "/api/v1/pty",
3601
3648
  { body: payload }
3602
3649
  );
3603
- return fromSnakeKeys(raw);
3650
+ return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3604
3651
  }
3605
3652
  /** Create a PTY session and connect to it immediately. Cleans up the session if the WebSocket connection fails. */
3606
3653
  async createPty(options) {
@@ -3654,15 +3701,53 @@ var init_sandbox = __esm({
3654
3701
  }
3655
3702
  /** Connect to a sandbox VNC session for programmatic desktop control. */
3656
3703
  async connectDesktop(options) {
3704
+ const port = options?.port ?? 5901;
3705
+ const connectTimeout = options?.connectTimeout ?? 10;
3706
+ const startMs = Date.now();
3707
+ const deadlineMs = startMs + connectTimeout * 1e3;
3708
+ await this.waitForPortReady(port, deadlineMs);
3709
+ const remainingSecs = Math.max(0.1, (deadlineMs - Date.now()) / 1e3);
3657
3710
  return Desktop.connect({
3658
3711
  baseUrl: this.baseUrl,
3659
3712
  wsHeaders: this.wsHeaders,
3660
- port: options?.port,
3713
+ port,
3661
3714
  password: options?.password,
3662
3715
  shared: options?.shared,
3663
- connectTimeout: options?.connectTimeout
3716
+ connectTimeout: remainingSecs
3664
3717
  });
3665
3718
  }
3719
+ /**
3720
+ * Poll the in-sandbox daemon until `127.0.0.1:port` accepts a TCP connection.
3721
+ * Uses `bash`'s `/dev/tcp` builtin via `processes/run` — no extra deps in
3722
+ * the sandbox image. `bash` is present on every image we ship.
3723
+ */
3724
+ async waitForPortReady(port, deadlineMs) {
3725
+ const probeIntervalMs = 250;
3726
+ const probeProcessTimeoutSecs = 2;
3727
+ let lastError;
3728
+ while (Date.now() < deadlineMs) {
3729
+ try {
3730
+ const result = await this.run("/bin/bash", {
3731
+ args: ["-c", `exec 3<>/dev/tcp/127.0.0.1/${port}`],
3732
+ timeout: probeProcessTimeoutSecs
3733
+ });
3734
+ if (result.exitCode === 0) {
3735
+ return;
3736
+ }
3737
+ } catch (error) {
3738
+ lastError = error;
3739
+ }
3740
+ const remainingMs = deadlineMs - Date.now();
3741
+ if (remainingMs <= 0) break;
3742
+ await new Promise(
3743
+ (resolve) => setTimeout(resolve, Math.min(probeIntervalMs, remainingMs))
3744
+ );
3745
+ }
3746
+ const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
3747
+ throw new SandboxError(
3748
+ `port ${port} did not become reachable inside sandbox within the connect timeout${detail}`
3749
+ );
3750
+ }
3666
3751
  ptyWsUrl(sessionId, token) {
3667
3752
  let wsBase;
3668
3753
  if (this.baseUrl.startsWith("https://")) {
@@ -3681,15 +3766,15 @@ var init_sandbox = __esm({
3681
3766
  "GET",
3682
3767
  "/api/v1/health"
3683
3768
  );
3684
- return fromSnakeKeys(raw);
3769
+ return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3685
3770
  }
3686
3771
  /** Get sandbox daemon info (version, uptime, process counts). */
3687
- async info() {
3772
+ async daemonInfo() {
3688
3773
  const raw = await this.http.requestJson(
3689
3774
  "GET",
3690
3775
  "/api/v1/info"
3691
3776
  );
3692
- return fromSnakeKeys(raw);
3777
+ return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3693
3778
  }
3694
3779
  };
3695
3780
  }
@@ -3782,7 +3867,7 @@ var init_client = __esm({
3782
3867
  this.namespace = options?.namespace ?? NAMESPACE;
3783
3868
  this.local = isLocalhost(this.apiUrl);
3784
3869
  this.http = new HttpClient({
3785
- baseUrl: this.apiUrl,
3870
+ baseUrl: resolveSandboxLifecycleUrl(this.apiUrl),
3786
3871
  apiKey: this.apiKey,
3787
3872
  organizationId: this.organizationId,
3788
3873
  projectId: this.projectId,
@@ -3850,7 +3935,7 @@ var init_client = __esm({
3850
3935
  "GET",
3851
3936
  this.path(`sandboxes/${sandboxId}`)
3852
3937
  );
3853
- return fromSnakeKeys(raw, "sandboxId");
3938
+ return Object.assign(fromSnakeKeys(raw, "sandboxId"), { traceId: raw.traceId });
3854
3939
  }
3855
3940
  /** List all sandboxes in the namespace. */
3856
3941
  async list() {
@@ -3881,7 +3966,7 @@ var init_client = __esm({
3881
3966
  this.path(`sandboxes/${sandboxId}`),
3882
3967
  { body }
3883
3968
  );
3884
- return fromSnakeKeys(raw, "sandboxId");
3969
+ return Object.assign(fromSnakeKeys(raw, "sandboxId"), { traceId: raw.traceId });
3885
3970
  }
3886
3971
  /** Get the current proxy port settings for a sandbox. */
3887
3972
  async getPortAccess(sandboxId) {
@@ -4023,7 +4108,7 @@ var init_client = __esm({
4023
4108
  "GET",
4024
4109
  this.path(`snapshots/${snapshotId}`)
4025
4110
  );
4026
- return fromSnakeKeys(raw, "snapshotId");
4111
+ return Object.assign(fromSnakeKeys(raw, "snapshotId"), { traceId: raw.traceId });
4027
4112
  }
4028
4113
  /** List all snapshots in the namespace. */
4029
4114
  async listSnapshots() {