@tensorlakeai/tensorlake 0.5.158 → 0.5.159

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.d.cts CHANGED
@@ -86,6 +86,8 @@ interface NativeSandboxClient {
86
86
  deleteSandbox(sandboxId: string): Promise<string>;
87
87
  suspendSandbox(sandboxId: string): Promise<string>;
88
88
  resumeSandbox(sandboxId: string): Promise<string>;
89
+ suspendSandboxAndWait(sandboxId: string, waitSec: number): Promise<boolean>;
90
+ resumeSandboxAndWait(sandboxId: string, waitSec: number): Promise<boolean>;
89
91
  attachFileSystem(sandboxId: string, fileSystemId: string, mountPath: string, readOnly?: boolean | null, prefetch?: boolean | null, snapshotId?: string | null, owner?: string | null): Promise<TracedJson>;
90
92
  detachFileSystem(sandboxId: string, mountPath: string): Promise<TracedJson>;
91
93
  createSnapshot(sandboxId: string, snapshotType?: string | null): Promise<TracedJson>;
@@ -308,6 +310,8 @@ interface ClaimSandboxOptions {
308
310
  fileSystems?: FileSystemMount[];
309
311
  }
310
312
  interface CreateSandboxOptions {
313
+ /** Allow proxy traffic to wake a suspended sandbox (default true). False requires a named sandbox. */
314
+ autoResume?: boolean;
311
315
  /** Optional sandbox image name. GPU sandboxes require a CAS image; when omitted with a GPU request, Tensorlake uses the server's configured GPU default. */
312
316
  image?: string;
313
317
  /** CPU count. Defaults to 1 for fresh creates; snapshot restores inherit it when omitted. */
@@ -328,6 +332,10 @@ interface CreateSandboxOptions {
328
332
  allowOut?: string[];
329
333
  denyOut?: string[];
330
334
  snapshotId?: string;
335
+ /** Whether exposed user ports are reachable without Tensorlake auth. Defaults to false for fresh creates; snapshot restores inherit it when omitted. */
336
+ allowUnauthenticatedAccess?: boolean;
337
+ /** User ports routable through the sandbox proxy. Port 9501 is reserved. Snapshot restores inherit ports when omitted; [] clears them. Does not enable unauthenticated access. */
338
+ exposedPorts?: number[];
331
339
  /** Optional name for the sandbox. Named sandboxes support suspend/resume. When absent the sandbox is ephemeral. */
332
340
  name?: string;
333
341
  /** File systems to mount on fresh creation or warm-pool claim, each at its own absolute, unique guest mount path. */
@@ -377,6 +385,8 @@ interface SandboxResourceResizeInfo {
377
385
  }
378
386
  /** Wait options apply only to resource updates and are ignored for other properties. */
379
387
  interface UpdateSandboxOptions extends ResourceResizeWaitOptions {
388
+ /** Change wake-on-traffic policy without waking or suspending the sandbox. Omit to keep it unchanged; false requires a named sandbox. Cannot be combined with resource resizing. */
389
+ autoResume?: boolean;
380
390
  /** Whole live vCPU target; omitted retains the current allocation. */
381
391
  cpus?: number;
382
392
  /** Memory target in MiB, with the same units as create; never rounded by the SDK. */
@@ -472,6 +482,8 @@ interface CopySandboxResponse {
472
482
  sandboxes: CopiedSandboxResponse[];
473
483
  }
474
484
  interface SandboxInfo {
485
+ /** Whether proxy traffic may wake this sandbox; defaults to true on older servers. */
486
+ autoResume: boolean;
475
487
  /**
476
488
  * The runtime the server chose for this sandbox: `firecracker`,
477
489
  * `cloud-hypervisor` or `gvisor`. Informational only; it is never sent and
@@ -1581,6 +1593,10 @@ declare class SandboxClient {
1581
1593
  * cannot. By default blocks until the sandbox is fully `Suspended`. Pass
1582
1594
  * `{ wait: false }` to return immediately after the request is sent
1583
1595
  * (fire-and-return); the server processes the suspend asynchronously.
1596
+ *
1597
+ * The server answers as soon as the suspend completes. When its wait runs
1598
+ * out first, or the server does not support waiting, the rest of `timeout`
1599
+ * is spent polling every `pollInterval` seconds.
1584
1600
  */
1585
1601
  suspend(sandboxId: string, options?: SuspendResumeOptions): Promise<void>;
1586
1602
  /**
@@ -1589,8 +1605,18 @@ declare class SandboxClient {
1589
1605
  * By default blocks until the sandbox is `Running` and routable. Pass
1590
1606
  * `{ wait: false }` to return immediately after the request is sent
1591
1607
  * (fire-and-return); the server processes the resume asynchronously.
1608
+ *
1609
+ * The server answers as soon as the sandbox runs. When its wait runs out
1610
+ * first, or the server does not support waiting, the rest of `timeout` is
1611
+ * spent polling every `pollInterval` seconds.
1592
1612
  */
1593
1613
  resume(sandboxId: string, options?: SuspendResumeOptions): Promise<void>;
1614
+ /**
1615
+ * Send a suspend or resume. With `wait`, the server waits for `target` and
1616
+ * answers whether it was reached; when its wait runs out first, the rest of
1617
+ * the timeout is spent polling.
1618
+ */
1619
+ private changeLifecycle;
1594
1620
  /**
1595
1621
  * Attach a registered file system to a running sandbox at `mountPath`.
1596
1622
  *
@@ -2798,6 +2824,8 @@ declare class RemoteAPIError extends SandboxError {
2798
2824
  readonly statusCode: number;
2799
2825
  /** Original response body, retained for compatibility and diagnostics. */
2800
2826
  readonly responseMessage: string;
2827
+ /** Machine-readable API error code, including proxy policy failures. */
2828
+ readonly code?: string;
2801
2829
  readonly sandboxId?: string;
2802
2830
  /** Server reason, including unknown future reasons. */
2803
2831
  readonly reason?: string;
package/dist/index.d.ts CHANGED
@@ -86,6 +86,8 @@ interface NativeSandboxClient {
86
86
  deleteSandbox(sandboxId: string): Promise<string>;
87
87
  suspendSandbox(sandboxId: string): Promise<string>;
88
88
  resumeSandbox(sandboxId: string): Promise<string>;
89
+ suspendSandboxAndWait(sandboxId: string, waitSec: number): Promise<boolean>;
90
+ resumeSandboxAndWait(sandboxId: string, waitSec: number): Promise<boolean>;
89
91
  attachFileSystem(sandboxId: string, fileSystemId: string, mountPath: string, readOnly?: boolean | null, prefetch?: boolean | null, snapshotId?: string | null, owner?: string | null): Promise<TracedJson>;
90
92
  detachFileSystem(sandboxId: string, mountPath: string): Promise<TracedJson>;
91
93
  createSnapshot(sandboxId: string, snapshotType?: string | null): Promise<TracedJson>;
@@ -308,6 +310,8 @@ interface ClaimSandboxOptions {
308
310
  fileSystems?: FileSystemMount[];
309
311
  }
310
312
  interface CreateSandboxOptions {
313
+ /** Allow proxy traffic to wake a suspended sandbox (default true). False requires a named sandbox. */
314
+ autoResume?: boolean;
311
315
  /** Optional sandbox image name. GPU sandboxes require a CAS image; when omitted with a GPU request, Tensorlake uses the server's configured GPU default. */
312
316
  image?: string;
313
317
  /** CPU count. Defaults to 1 for fresh creates; snapshot restores inherit it when omitted. */
@@ -328,6 +332,10 @@ interface CreateSandboxOptions {
328
332
  allowOut?: string[];
329
333
  denyOut?: string[];
330
334
  snapshotId?: string;
335
+ /** Whether exposed user ports are reachable without Tensorlake auth. Defaults to false for fresh creates; snapshot restores inherit it when omitted. */
336
+ allowUnauthenticatedAccess?: boolean;
337
+ /** User ports routable through the sandbox proxy. Port 9501 is reserved. Snapshot restores inherit ports when omitted; [] clears them. Does not enable unauthenticated access. */
338
+ exposedPorts?: number[];
331
339
  /** Optional name for the sandbox. Named sandboxes support suspend/resume. When absent the sandbox is ephemeral. */
332
340
  name?: string;
333
341
  /** File systems to mount on fresh creation or warm-pool claim, each at its own absolute, unique guest mount path. */
@@ -377,6 +385,8 @@ interface SandboxResourceResizeInfo {
377
385
  }
378
386
  /** Wait options apply only to resource updates and are ignored for other properties. */
379
387
  interface UpdateSandboxOptions extends ResourceResizeWaitOptions {
388
+ /** Change wake-on-traffic policy without waking or suspending the sandbox. Omit to keep it unchanged; false requires a named sandbox. Cannot be combined with resource resizing. */
389
+ autoResume?: boolean;
380
390
  /** Whole live vCPU target; omitted retains the current allocation. */
381
391
  cpus?: number;
382
392
  /** Memory target in MiB, with the same units as create; never rounded by the SDK. */
@@ -472,6 +482,8 @@ interface CopySandboxResponse {
472
482
  sandboxes: CopiedSandboxResponse[];
473
483
  }
474
484
  interface SandboxInfo {
485
+ /** Whether proxy traffic may wake this sandbox; defaults to true on older servers. */
486
+ autoResume: boolean;
475
487
  /**
476
488
  * The runtime the server chose for this sandbox: `firecracker`,
477
489
  * `cloud-hypervisor` or `gvisor`. Informational only; it is never sent and
@@ -1581,6 +1593,10 @@ declare class SandboxClient {
1581
1593
  * cannot. By default blocks until the sandbox is fully `Suspended`. Pass
1582
1594
  * `{ wait: false }` to return immediately after the request is sent
1583
1595
  * (fire-and-return); the server processes the suspend asynchronously.
1596
+ *
1597
+ * The server answers as soon as the suspend completes. When its wait runs
1598
+ * out first, or the server does not support waiting, the rest of `timeout`
1599
+ * is spent polling every `pollInterval` seconds.
1584
1600
  */
1585
1601
  suspend(sandboxId: string, options?: SuspendResumeOptions): Promise<void>;
1586
1602
  /**
@@ -1589,8 +1605,18 @@ declare class SandboxClient {
1589
1605
  * By default blocks until the sandbox is `Running` and routable. Pass
1590
1606
  * `{ wait: false }` to return immediately after the request is sent
1591
1607
  * (fire-and-return); the server processes the resume asynchronously.
1608
+ *
1609
+ * The server answers as soon as the sandbox runs. When its wait runs out
1610
+ * first, or the server does not support waiting, the rest of `timeout` is
1611
+ * spent polling every `pollInterval` seconds.
1592
1612
  */
1593
1613
  resume(sandboxId: string, options?: SuspendResumeOptions): Promise<void>;
1614
+ /**
1615
+ * Send a suspend or resume. With `wait`, the server waits for `target` and
1616
+ * answers whether it was reached; when its wait runs out first, the rest of
1617
+ * the timeout is spent polling.
1618
+ */
1619
+ private changeLifecycle;
1594
1620
  /**
1595
1621
  * Attach a registered file system to a running sandbox at `mountPath`.
1596
1622
  *
@@ -2798,6 +2824,8 @@ declare class RemoteAPIError extends SandboxError {
2798
2824
  readonly statusCode: number;
2799
2825
  /** Original response body, retained for compatibility and diagnostics. */
2800
2826
  readonly responseMessage: string;
2827
+ /** Machine-readable API error code, including proxy policy failures. */
2828
+ readonly code?: string;
2801
2829
  readonly sandboxId?: string;
2802
2830
  /** Server reason, including unknown future reasons. */
2803
2831
  readonly reason?: string;
package/dist/index.js CHANGED
@@ -46,7 +46,7 @@ var init_defaults = __esm({
46
46
  "src/defaults.ts"() {
47
47
  "use strict";
48
48
  init_esm_shims();
49
- SDK_VERSION = "0.5.158";
49
+ SDK_VERSION = "0.5.159";
50
50
  API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
51
51
  API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
52
52
  NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
@@ -181,13 +181,17 @@ var init_errors = __esm({
181
181
  statusCode;
182
182
  /** Original response body, retained for compatibility and diagnostics. */
183
183
  responseMessage;
184
+ /** Machine-readable API error code, including proxy policy failures. */
185
+ code;
184
186
  errorDetails;
185
187
  constructor(statusCode, message) {
186
188
  let failure;
189
+ let code;
187
190
  try {
188
191
  const payload = JSON.parse(message);
189
192
  if (payload && typeof payload === "object" && !Array.isArray(payload)) {
190
193
  const record = payload;
194
+ if (typeof record.code === "string") code = record.code;
191
195
  if (typeof record.sandbox_id === "string" && record.sandbox_id.trim() && (record.status === "failed" || record.status === "terminated"))
192
196
  failure = record;
193
197
  }
@@ -213,6 +217,7 @@ var init_errors = __esm({
213
217
  this.name = "RemoteAPIError";
214
218
  this.statusCode = statusCode;
215
219
  this.responseMessage = message;
220
+ this.code = code;
216
221
  }
217
222
  };
218
223
  RequestNotFinishedError = class extends Error {
@@ -509,7 +514,7 @@ var init_native_worker_client = __esm({
509
514
  }
510
515
  };
511
516
  runtimeKey = /* @__PURE__ */ Symbol.for(
512
- `tensorlake.native.worker.${false ? "source" : "0.5.158"}.v1`
517
+ `tensorlake.native.worker.${false ? "source" : "0.5.159"}.v1`
513
518
  );
514
519
  runtimes = globalThis;
515
520
  }
@@ -4930,6 +4935,12 @@ var init_client = __esm({
4930
4935
  if (options?.timeoutSecs != null) body.timeout_secs = options.timeoutSecs;
4931
4936
  if (options?.entrypoint != null) body.entrypoint = options.entrypoint;
4932
4937
  if (options?.snapshotId != null) body.snapshot_id = options.snapshotId;
4938
+ if (options?.allowUnauthenticatedAccess != null) {
4939
+ body.allow_unauthenticated_access = options.allowUnauthenticatedAccess;
4940
+ }
4941
+ if (options?.exposedPorts != null) {
4942
+ body.exposed_ports = normalizeUserPorts(options.exposedPorts);
4943
+ }
4933
4944
  if (options?.name != null) body.name = options.name;
4934
4945
  if (options?.fileSystems != null && options.fileSystems.length > 0) {
4935
4946
  body.file_systems = options.fileSystems.map(fileSystemMountToWire);
@@ -4941,6 +4952,7 @@ var init_client = __esm({
4941
4952
  deny_out: options?.denyOut ?? []
4942
4953
  };
4943
4954
  }
4955
+ if (options?.autoResume != null) body.auto_resume = options.autoResume;
4944
4956
  if (options?.maxPendingSecs != null) {
4945
4957
  if (!Number.isInteger(options.maxPendingSecs) || options.maxPendingSecs < 0) {
4946
4958
  throw new SandboxError("maxPendingSecs must be a non-negative integer");
@@ -5057,13 +5069,14 @@ var init_client = __esm({
5057
5069
  const resizing = Object.keys(resources).length > 0;
5058
5070
  if (resizing) {
5059
5071
  validateResizeWait(options);
5060
- if (options.name != null || options.allowUnauthenticatedAccess != null || options.exposedPorts != null || options.network !== void 0) {
5072
+ if (options.name != null || options.allowUnauthenticatedAccess != null || options.exposedPorts != null || options.autoResume != null || options.network !== void 0) {
5061
5073
  throw new SandboxError(
5062
5074
  "resources cannot be combined with other sandbox update fields"
5063
5075
  );
5064
5076
  }
5065
5077
  body.resources = resources;
5066
5078
  }
5079
+ if (options.autoResume != null) body.auto_resume = options.autoResume;
5067
5080
  if (options.name != null) body.name = options.name;
5068
5081
  if (options.allowUnauthenticatedAccess != null) {
5069
5082
  body.allow_unauthenticated_access = options.allowUnauthenticatedAccess;
@@ -5158,28 +5171,19 @@ var init_client = __esm({
5158
5171
  * cannot. By default blocks until the sandbox is fully `Suspended`. Pass
5159
5172
  * `{ wait: false }` to return immediately after the request is sent
5160
5173
  * (fire-and-return); the server processes the suspend asynchronously.
5174
+ *
5175
+ * The server answers as soon as the suspend completes. When its wait runs
5176
+ * out first, or the server does not support waiting, the rest of `timeout`
5177
+ * is spent polling every `pollInterval` seconds.
5161
5178
  */
5162
5179
  async suspend(sandboxId, options) {
5163
- await callNative(() => this.native.suspendSandbox(sandboxId), {
5180
+ await this.changeLifecycle(
5164
5181
  sandboxId,
5165
- notFoundKind: "sandbox"
5166
- });
5167
- if (options?.wait === false) return;
5168
- const timeout = options?.timeout ?? 300;
5169
- const pollInterval = options?.pollInterval ?? 1;
5170
- const deadline = Date.now() + timeout * 1e3;
5171
- while (Date.now() < deadline) {
5172
- const info = await this.get(sandboxId);
5173
- if (info.status === "suspended" /* SUSPENDED */) return;
5174
- if (info.status === "terminated" /* TERMINATED */) {
5175
- throw new SandboxError(
5176
- `Sandbox ${sandboxId} terminated while waiting for suspend`
5177
- );
5178
- }
5179
- await sleep2(pollInterval * 1e3);
5180
- }
5181
- throw new SandboxError(
5182
- `Sandbox ${sandboxId} did not suspend within ${timeout}s`
5182
+ "suspend",
5183
+ "suspended" /* SUSPENDED */,
5184
+ () => this.native.suspendSandbox(sandboxId),
5185
+ (waitSec) => this.native.suspendSandboxAndWait(sandboxId, waitSec),
5186
+ options
5183
5187
  );
5184
5188
  }
5185
5189
  /**
@@ -5188,28 +5192,58 @@ var init_client = __esm({
5188
5192
  * By default blocks until the sandbox is `Running` and routable. Pass
5189
5193
  * `{ wait: false }` to return immediately after the request is sent
5190
5194
  * (fire-and-return); the server processes the resume asynchronously.
5195
+ *
5196
+ * The server answers as soon as the sandbox runs. When its wait runs out
5197
+ * first, or the server does not support waiting, the rest of `timeout` is
5198
+ * spent polling every `pollInterval` seconds.
5191
5199
  */
5192
5200
  async resume(sandboxId, options) {
5193
- await callNative(() => this.native.resumeSandbox(sandboxId), {
5201
+ await this.changeLifecycle(
5194
5202
  sandboxId,
5195
- notFoundKind: "sandbox"
5196
- });
5197
- if (options?.wait === false) return;
5203
+ "resume",
5204
+ "running" /* RUNNING */,
5205
+ () => this.native.resumeSandbox(sandboxId),
5206
+ (waitSec) => this.native.resumeSandboxAndWait(sandboxId, waitSec),
5207
+ options
5208
+ );
5209
+ }
5210
+ /**
5211
+ * Send a suspend or resume. With `wait`, the server waits for `target` and
5212
+ * answers whether it was reached; when its wait runs out first, the rest of
5213
+ * the timeout is spent polling.
5214
+ */
5215
+ async changeLifecycle(sandboxId, action, target, send, sendAndWait, options) {
5216
+ const context = { sandboxId, notFoundKind: "sandbox" };
5217
+ if (options?.wait === false) {
5218
+ await callNative(send, context);
5219
+ return;
5220
+ }
5198
5221
  const timeout = options?.timeout ?? 300;
5199
5222
  const pollInterval = options?.pollInterval ?? 1;
5200
5223
  const deadline = Date.now() + timeout * 1e3;
5224
+ let settled;
5225
+ try {
5226
+ settled = await callNative(() => sendAndWait(timeout), context);
5227
+ } catch (error) {
5228
+ if (!(error instanceof RemoteAPIError) || error.statusCode !== 409) {
5229
+ throw error;
5230
+ }
5231
+ if ((await this.get(sandboxId)).status === target) return;
5232
+ throw error;
5233
+ }
5234
+ if (settled) return;
5201
5235
  while (Date.now() < deadline) {
5202
5236
  const info = await this.get(sandboxId);
5203
- if (info.status === "running" /* RUNNING */) return;
5237
+ if (info.status === target) return;
5204
5238
  if (info.status === "terminated" /* TERMINATED */) {
5205
5239
  throw new SandboxError(
5206
- `Sandbox ${sandboxId} terminated while waiting for resume`
5240
+ `Sandbox ${sandboxId} terminated while waiting for ${action}`
5207
5241
  );
5208
5242
  }
5209
5243
  await sleep2(pollInterval * 1e3);
5210
5244
  }
5211
5245
  throw new SandboxError(
5212
- `Sandbox ${sandboxId} did not resume within ${timeout}s`
5246
+ `Sandbox ${sandboxId} did not ${action} within ${timeout}s`
5213
5247
  );
5214
5248
  }
5215
5249
  /**
@@ -5486,6 +5520,11 @@ var init_client = __esm({
5486
5520
  * claim that is still starting is then waited on the same way.
5487
5521
  */
5488
5522
  async createAndConnect(options) {
5523
+ if (options?.poolId != null && (options.allowUnauthenticatedAccess != null || options.exposedPorts != null)) {
5524
+ throw new SandboxError(
5525
+ "allowUnauthenticatedAccess and exposedPorts cannot be combined with poolId: pool claims inherit access settings from the pool."
5526
+ );
5527
+ }
5489
5528
  const opStart = nowMs();
5490
5529
  const requestTimeout = options?.requestTimeout ?? options?.startupTimeout ?? this.requestTimeoutMs / 1e3;
5491
5530
  const requestClient = this.withRequestTimeout(requestTimeout);
@@ -5498,6 +5537,9 @@ var init_client = __esm({
5498
5537
  const requestedName = options?.poolId != null ? null : options?.name ?? null;
5499
5538
  const createStart = nowMs();
5500
5539
  let result;
5540
+ if (options?.poolId != null && options.autoResume != null) {
5541
+ throw new SandboxError("autoResume cannot be set when claiming from a pool");
5542
+ }
5501
5543
  if (options?.poolId != null) {
5502
5544
  result = await requestClient.claim(options.poolId, {
5503
5545
  fileSystems: options.fileSystems