tensorlake 0.5.135 → 0.5.136

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
@@ -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.135";
49
+ SDK_VERSION = "0.5.136";
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";
@@ -79,7 +79,7 @@ function formatErrorDetails(errorDetails) {
79
79
  }
80
80
  return String(errorDetails);
81
81
  }
82
- var SandboxException, SandboxError, SandboxConnectionError, SandboxNotFoundError, PoolNotFoundError, PoolInUseError, RemoteAPIError, RequestNotFinishedError, RequestFailedError, RequestExecutionError;
82
+ var SandboxException, SandboxError, SandboxPending, SandboxConnectionError, SandboxNotFoundError, PoolNotFoundError, PoolInUseError, RemoteAPIError, RequestNotFinishedError, RequestFailedError, RequestExecutionError;
83
83
  var init_errors = __esm({
84
84
  "src/errors.ts"() {
85
85
  "use strict";
@@ -91,9 +91,27 @@ var init_errors = __esm({
91
91
  }
92
92
  };
93
93
  SandboxError = class extends SandboxException {
94
- constructor(message) {
94
+ reason;
95
+ sandboxId;
96
+ constructor(message, options) {
95
97
  super(message);
96
98
  this.name = "SandboxError";
99
+ if (options?.reason !== void 0) this.reason = options.reason;
100
+ if (options?.sandboxId !== void 0) this.sandboxId = options.sandboxId;
101
+ }
102
+ };
103
+ SandboxPending = class extends SandboxError {
104
+ pendingReason;
105
+ timeout;
106
+ constructor(sandboxId, options) {
107
+ let message = `Sandbox ${sandboxId} is still pending`;
108
+ if (options?.timeout !== void 0) message += ` after ${options.timeout}s`;
109
+ if (options?.pendingReason) message += ` (${options.pendingReason})`;
110
+ message += `; it keeps its place in the queue. Wait again with ready() or Sandbox.connect({ sandboxId: '${sandboxId}' }), or delete it to cancel.`;
111
+ super(message, { reason: options?.pendingReason, sandboxId });
112
+ this.name = "SandboxPending";
113
+ this.pendingReason = options?.pendingReason;
114
+ this.timeout = options?.timeout;
97
115
  }
98
116
  };
99
117
  SandboxConnectionError = class extends SandboxError {
@@ -104,11 +122,9 @@ var init_errors = __esm({
104
122
  }
105
123
  };
106
124
  SandboxNotFoundError = class extends SandboxError {
107
- sandboxId;
108
125
  constructor(sandboxId) {
109
- super(`Sandbox not found: ${sandboxId}`);
126
+ super(`Sandbox not found: ${sandboxId}`, { sandboxId });
110
127
  this.name = "SandboxNotFoundError";
111
- this.sandboxId = sandboxId;
112
128
  }
113
129
  };
114
130
  PoolNotFoundError = class extends SandboxError {
@@ -132,9 +148,6 @@ var init_errors = __esm({
132
148
  statusCode;
133
149
  /** Original response body, retained for compatibility and diagnostics. */
134
150
  responseMessage;
135
- sandboxId;
136
- /** Server reason, including unknown future reasons. */
137
- reason;
138
151
  errorDetails;
139
152
  constructor(statusCode, message) {
140
153
  let failure;
@@ -159,9 +172,10 @@ var init_errors = __esm({
159
172
  if (detail) displayMessage += `: ${detail}`;
160
173
  if (!reason && !detail) displayMessage = message;
161
174
  }
162
- super(`API error (status ${statusCode}): ${displayMessage}`);
163
- this.sandboxId = sandboxId;
164
- this.reason = reason;
175
+ super(`API error (status ${statusCode}): ${displayMessage}`, {
176
+ reason,
177
+ sandboxId
178
+ });
165
179
  this.errorDetails = errorDetails;
166
180
  this.name = "RemoteAPIError";
167
181
  this.statusCode = statusCode;
@@ -457,7 +471,7 @@ var init_native_worker_client = __esm({
457
471
  }
458
472
  };
459
473
  runtimeKey = /* @__PURE__ */ Symbol.for(
460
- `tensorlake.native.worker.${false ? "source" : "0.5.135"}.v1`
474
+ `tensorlake.native.worker.${false ? "source" : "0.5.136"}.v1`
461
475
  );
462
476
  runtimes = globalThis;
463
477
  }
@@ -609,6 +623,9 @@ var init_native_sandbox = __esm({
609
623
  });
610
624
 
611
625
  // src/models.ts
626
+ function isSandboxPending(status) {
627
+ return String(status) === "pending" /* PENDING */;
628
+ }
612
629
  function camelToSnake(str) {
613
630
  return str.replace(CAMEL_TO_SNAKE_RE, (ch) => "_" + ch.toLowerCase());
614
631
  }
@@ -665,7 +682,7 @@ function fromSnakeKeys(obj, idField) {
665
682
  }
666
683
  return obj;
667
684
  }
668
- var SandboxStatus, SnapshotStatus, ProcessStatus, StdinMode, OutputMode, ContainerState, CAMEL_TO_SNAKE_RE;
685
+ var SandboxStatus, SandboxPendingReason, TERMINATION_REASON_NO_CAPACITY, TERMINATION_REASON_CANCELLED, SnapshotStatus, ProcessStatus, StdinMode, OutputMode, ContainerState, CAMEL_TO_SNAKE_RE;
669
686
  var init_models = __esm({
670
687
  "src/models.ts"() {
671
688
  "use strict";
@@ -681,6 +698,19 @@ var init_models = __esm({
681
698
  SandboxStatus2["FAILED"] = "failed";
682
699
  return SandboxStatus2;
683
700
  })(SandboxStatus || {});
701
+ SandboxPendingReason = /* @__PURE__ */ ((SandboxPendingReason2) => {
702
+ SandboxPendingReason2["SCHEDULING"] = "scheduling";
703
+ SandboxPendingReason2["WAITING_FOR_CONTAINER"] = "waiting_for_container";
704
+ SandboxPendingReason2["NO_EXECUTORS_AVAILABLE"] = "no_executors_available";
705
+ SandboxPendingReason2["NO_RESOURCES_AVAILABLE"] = "no_resources_available";
706
+ SandboxPendingReason2["POOL_AT_CAPACITY"] = "pool_at_capacity";
707
+ SandboxPendingReason2["MAX_CAPACITY_REACHED"] = "max_capacity_reached";
708
+ SandboxPendingReason2["NO_EXECUTOR_FOR_SNAPSHOT_LOCATION"] = "no_executor_for_snapshot_location";
709
+ SandboxPendingReason2["UNPLACEABLE"] = "unplaceable";
710
+ return SandboxPendingReason2;
711
+ })(SandboxPendingReason || {});
712
+ TERMINATION_REASON_NO_CAPACITY = "no_capacity";
713
+ TERMINATION_REASON_CANCELLED = "cancelled";
684
714
  SnapshotStatus = /* @__PURE__ */ ((SnapshotStatus2) => {
685
715
  SnapshotStatus2["IN_PROGRESS"] = "in_progress";
686
716
  SnapshotStatus2["LOCAL_READY"] = "local_ready";
@@ -714,6 +744,64 @@ var init_models = __esm({
714
744
  }
715
745
  });
716
746
 
747
+ // src/pending-sandbox.ts
748
+ var PendingSandbox;
749
+ var init_pending_sandbox = __esm({
750
+ "src/pending-sandbox.ts"() {
751
+ "use strict";
752
+ init_esm_shims();
753
+ PendingSandbox = class {
754
+ /** @internal */
755
+ constructor(record, binding) {
756
+ this.binding = binding;
757
+ this.sandboxId = record.sandboxId;
758
+ this.name = record.name;
759
+ this.state = record.state;
760
+ this.pendingReason = record.pendingReason;
761
+ this.routingHint = record.routingHint;
762
+ this.ingressEndpoint = record.ingressEndpoint;
763
+ this.sandboxUrl = record.sandboxUrl;
764
+ this.reason = record.reason;
765
+ this.terminationReason = record.terminationReason;
766
+ this.errorDetails = record.errorDetails;
767
+ this.traceId = binding.traceId;
768
+ }
769
+ sandboxId;
770
+ name;
771
+ state;
772
+ pendingReason;
773
+ routingHint;
774
+ ingressEndpoint;
775
+ sandboxUrl;
776
+ reason;
777
+ terminationReason;
778
+ errorDetails;
779
+ /** W3C trace id of the create request. */
780
+ traceId;
781
+ /** Fetch the sandbox's current state with one `GET`. */
782
+ async status() {
783
+ return this.binding.client.get(this.sandboxId);
784
+ }
785
+ /**
786
+ * Wait for the sandbox to be running and return a connected `Sandbox`.
787
+ *
788
+ * Polls `GET /sandboxes/{id}` every `pollInterval` seconds (default 2).
789
+ * Call it again after a `SandboxPending` to keep waiting.
790
+ *
791
+ * @throws {SandboxPending} `timeout` elapsed while the sandbox was still
792
+ * pending. It keeps its place in the queue; the error carries
793
+ * `sandboxId` and `pendingReason`.
794
+ * @throws {SandboxError} The sandbox settled somewhere else: `terminated`
795
+ * or `failed` (`reason` is the server's, such as `no_capacity` or
796
+ * `cancelled`), or `suspended`.
797
+ */
798
+ async ready(options) {
799
+ return this.binding.client._ready(this.sandboxId, this.binding, options);
800
+ }
801
+ };
802
+ }
803
+ });
804
+
717
805
  // src/tunnel.ts
718
806
  import * as net from "net";
719
807
  import WebSocket from "ws";
@@ -3635,17 +3723,10 @@ var init_sandbox = __esm({
3635
3723
  this.ownsSandbox = true;
3636
3724
  this.lifecycleClient = client;
3637
3725
  }
3638
- // --- Static factory methods ---
3639
- /**
3640
- * Create a new sandbox and return a connected, running handle.
3641
- *
3642
- * Covers both fresh sandbox creation and restore-from-snapshot (set
3643
- * `snapshotId`). Blocks until the sandbox is `Running`.
3644
- *
3645
- * When `name` is already claimed, the server rejects the create with
3646
- * HTTP 409 (`RemoteAPIError`). To attach to the existing sandbox
3647
- * instead, use {@link Sandbox.getOrCreate}.
3648
- */
3726
+ /** @internal Used by PendingSandbox.ready for a non-owning handle. */
3727
+ _setLifecycleClient(client) {
3728
+ this.lifecycleClient = client;
3729
+ }
3649
3730
  static async create(options) {
3650
3731
  const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3651
3732
  const client = new SandboxClient2(
@@ -3653,6 +3734,19 @@ var init_sandbox = __esm({
3653
3734
  /* _internal */
3654
3735
  true
3655
3736
  );
3737
+ if (options?.wait === false) {
3738
+ if (options.poolId != null) {
3739
+ throw new SandboxError(
3740
+ "wait: false cannot be combined with poolId: a pool claim is answered synchronously"
3741
+ );
3742
+ }
3743
+ const requestTimeout = options.requestTimeout ?? options.startupTimeout ?? void 0;
3744
+ return client.requestPending(options, {
3745
+ proxyUrl: options.proxyUrl,
3746
+ requestTimeout,
3747
+ ownsSandbox: true
3748
+ });
3749
+ }
3656
3750
  return client.createAndConnect(options);
3657
3751
  }
3658
3752
  /**
@@ -3943,6 +4037,7 @@ var init_sandbox = __esm({
3943
4037
  */
3944
4038
  async refreshRoutingWhenRunning(deadline, pollInterval, timeout) {
3945
4039
  const client = this.requireLifecycleClient("refresh proxy routing");
4040
+ let last;
3946
4041
  while (Date.now() < deadline) {
3947
4042
  const info = await client.get(this.lifecycleIdentifier);
3948
4043
  if (info.status === "running" /* RUNNING */ && (info.sandboxUrl != null || this.proxy.hasExplicitProxyUrl())) {
@@ -3951,13 +4046,22 @@ var init_sandbox = __esm({
3951
4046
  }
3952
4047
  if (info.status === "terminated" /* TERMINATED */) {
3953
4048
  throw new SandboxError(
3954
- `Sandbox ${this.lifecycleIdentifier} terminated while refreshing proxy routing`
4049
+ `Sandbox ${this.lifecycleIdentifier} terminated while refreshing proxy routing`,
4050
+ { reason: info.terminationReason, sandboxId: info.sandboxId }
3955
4051
  );
3956
4052
  }
4053
+ last = info;
3957
4054
  await sleep(Math.min(pollInterval * 1e3, Math.max(0, deadline - Date.now())));
3958
4055
  }
4056
+ if (last?.status === "pending" /* PENDING */) {
4057
+ throw new SandboxPending(last.sandboxId, {
4058
+ pendingReason: last.pendingReason,
4059
+ timeout
4060
+ });
4061
+ }
3959
4062
  throw new SandboxError(
3960
- `Sandbox ${this.lifecycleIdentifier} did not provide refreshed proxy routing within ${timeout}s`
4063
+ `Sandbox ${this.lifecycleIdentifier} did not provide refreshed proxy routing within ${timeout}s`,
4064
+ { sandboxId: this.lifecycleIdentifier }
3961
4065
  );
3962
4066
  }
3963
4067
  /** Live-copy this running sandbox. */
@@ -4452,6 +4556,7 @@ var init_sandbox = __esm({
4452
4556
  // src/client.ts
4453
4557
  var client_exports = {};
4454
4558
  __export(client_exports, {
4559
+ DEFAULT_WAIT_POLL_INTERVAL_SEC: () => DEFAULT_WAIT_POLL_INTERVAL_SEC,
4455
4560
  SandboxClient: () => SandboxClient
4456
4561
  });
4457
4562
  function requireReadOnlySnapshotPin(fileSystemId, mountPath, readOnly, snapshotId) {
@@ -4490,6 +4595,12 @@ function gpuRequest(gpu, gpus, gpuModel) {
4490
4595
  }
4491
4596
  return [{ count: gpus, model: gpuModel }];
4492
4597
  }
4598
+ function startupFailure(sandboxId, status, options) {
4599
+ return new SandboxError(formatStartupFailureMessage(sandboxId, status, options), {
4600
+ reason: options.terminationReason,
4601
+ sandboxId
4602
+ });
4603
+ }
4493
4604
  function resolveRequestTimeoutMs(options) {
4494
4605
  if (options?.requestTimeout != null) {
4495
4606
  return secondsToMillis3(options.requestTimeout);
@@ -4550,7 +4661,7 @@ function validateUserPort(port) {
4550
4661
  function dedupeAndSortPorts(ports) {
4551
4662
  return [...new Set(ports)].sort((a, b) => a - b);
4552
4663
  }
4553
- var GPU_MODELS, SandboxClient, RESERVED_SANDBOX_MANAGEMENT_PORT;
4664
+ var DEFAULT_WAIT_POLL_INTERVAL_SEC, GPU_MODELS, SandboxClient, RESERVED_SANDBOX_MANAGEMENT_PORT;
4554
4665
  var init_client = __esm({
4555
4666
  "src/client.ts"() {
4556
4667
  "use strict";
@@ -4560,9 +4671,11 @@ var init_client = __esm({
4560
4671
  init_native_worker_client();
4561
4672
  init_native_sandbox();
4562
4673
  init_models();
4674
+ init_pending_sandbox();
4563
4675
  init_sandbox();
4564
4676
  init_sdk_timings();
4565
4677
  init_url();
4678
+ DEFAULT_WAIT_POLL_INTERVAL_SEC = 2;
4566
4679
  GPU_MODELS = /* @__PURE__ */ new Set([
4567
4680
  "A100-40GB",
4568
4681
  "A100-80GB",
@@ -4665,9 +4778,155 @@ var init_client = __esm({
4665
4778
  const { json } = await callNative(fn, context);
4666
4779
  return fromSnakeKeys(JSON.parse(json), idField);
4667
4780
  }
4668
- // --- Sandbox CRUD ---
4669
- /** Create a new sandbox. Returns immediately; the sandbox may still be starting. Use `createAndConnect()` for a blocking, ready-to-use handle. */
4670
4781
  async create(options) {
4782
+ if (options?.wait === false) {
4783
+ return this.requestPending(options, { ownsSandbox: false });
4784
+ }
4785
+ const body = _SandboxClient.buildCreateRequestBody(options);
4786
+ return this.tracedJson(
4787
+ () => this.native.createSandbox(JSON.stringify(body)),
4788
+ "sandboxId"
4789
+ );
4790
+ }
4791
+ /**
4792
+ * @internal Send the create with `wait: false` and return the pending
4793
+ * handle, bound to this client so `ready()` / `status()` work.
4794
+ */
4795
+ async requestPending(options, binding) {
4796
+ const body = _SandboxClient.buildCreateRequestBody({
4797
+ ...options,
4798
+ wait: false
4799
+ });
4800
+ const record = await this.tracedJson(
4801
+ () => this.native.createSandboxNoWait(JSON.stringify(body))
4802
+ );
4803
+ return new PendingSandbox(record, {
4804
+ client: this,
4805
+ proxyUrl: binding.proxyUrl,
4806
+ requestTimeout: binding.requestTimeout,
4807
+ requestedName: options?.name ?? null,
4808
+ ownsSandbox: binding.ownsSandbox,
4809
+ traceId: record.traceId
4810
+ });
4811
+ }
4812
+ /** @internal Implementation of `PendingSandbox.ready`. */
4813
+ async _ready(sandboxId, binding, options) {
4814
+ const budget = options?.timeout ?? this.requestTimeoutMs / 1e3;
4815
+ const observed = await this.waitForSandbox(
4816
+ sandboxId,
4817
+ budget,
4818
+ options?.pollInterval ?? DEFAULT_WAIT_POLL_INTERVAL_SEC
4819
+ );
4820
+ return this.settleWait(observed, {
4821
+ budget,
4822
+ proxyUrl: binding.proxyUrl,
4823
+ requestTimeout: binding.requestTimeout,
4824
+ cancelOnTimeout: options?.cancelOnTimeout ?? false,
4825
+ requestedName: binding.requestedName,
4826
+ ownsSandbox: binding.ownsSandbox,
4827
+ traceId: binding.traceId
4828
+ });
4829
+ }
4830
+ /**
4831
+ * Poll `get` every `pollInterval` seconds until the sandbox leaves
4832
+ * `pending` or `timeout` elapses, resolving with the last observation.
4833
+ * Never rejects for a timeout and never deletes.
4834
+ */
4835
+ async waitForSandbox(sandboxId, timeout, pollInterval) {
4836
+ if (!Number.isFinite(timeout) || timeout < 0) {
4837
+ throw new SandboxError("timeout must be a non-negative number of seconds");
4838
+ }
4839
+ if (!Number.isFinite(pollInterval) || pollInterval <= 0) {
4840
+ throw new SandboxError("pollInterval must be a positive number of seconds");
4841
+ }
4842
+ return this.tracedJson(
4843
+ () => this.native.waitForSandbox(sandboxId, timeout, pollInterval),
4844
+ "sandboxId",
4845
+ { sandboxId, notFoundKind: "sandbox" }
4846
+ );
4847
+ }
4848
+ /** Turn the last observed sandbox state into a handle or an error. */
4849
+ async settleWait(observed, options) {
4850
+ const { status, sandboxId } = observed;
4851
+ if (isSandboxPending(status)) {
4852
+ if (options.cancelOnTimeout) {
4853
+ try {
4854
+ await this.delete(sandboxId);
4855
+ } catch {
4856
+ }
4857
+ throw new SandboxError(
4858
+ `Sandbox ${sandboxId} did not start within ${options.budget}s`,
4859
+ { reason: observed.pendingReason, sandboxId }
4860
+ );
4861
+ }
4862
+ throw new SandboxPending(sandboxId, {
4863
+ pendingReason: observed.pendingReason,
4864
+ timeout: options.budget
4865
+ });
4866
+ }
4867
+ if (status === "running" /* RUNNING */) {
4868
+ const explicitProxyUrl = options.proxyUrl ?? explicitProxyUrlOverride() ?? void 0;
4869
+ const selectedProxyUrl = await this.native.selectSandboxProxyUrl(
4870
+ sandboxId,
4871
+ observed.sandboxUrl ?? null,
4872
+ observed.ingressEndpoint ?? null,
4873
+ explicitProxyUrl ?? null
4874
+ );
4875
+ const sandbox = this.connect(
4876
+ sandboxId,
4877
+ selectedProxyUrl,
4878
+ observed.routingHint,
4879
+ options.requestTimeout,
4880
+ explicitProxyUrl
4881
+ );
4882
+ if (options.ownsSandbox) sandbox._setOwner(this);
4883
+ else sandbox._setLifecycleClient(this);
4884
+ sandbox.traceId = options.traceId ?? observed.traceId;
4885
+ sandbox._setLifecycleIdentifier(sandboxId);
4886
+ sandbox._setName(observed.name ?? options.requestedName ?? null);
4887
+ return sandbox;
4888
+ }
4889
+ if (status === "terminated" /* TERMINATED */ || status === "failed" /* FAILED */) {
4890
+ throw startupFailure(sandboxId, status, {
4891
+ errorDetails: observed.errorDetails,
4892
+ terminationReason: observed.terminationReason
4893
+ });
4894
+ }
4895
+ throw new SandboxError(
4896
+ `Sandbox ${sandboxId} is ${status}, not running; ` + (status === "suspended" /* SUSPENDED */ ? "resume it to run it again" : "wait for it to settle"),
4897
+ { reason: String(status), sandboxId }
4898
+ );
4899
+ }
4900
+ /**
4901
+ * Routing for a lazy `connect` handle. A sandbox that is still pending is
4902
+ * waited on here rather than failing: readiness is a property of the
4903
+ * sandbox, not of the create call that requested it.
4904
+ */
4905
+ async resolveRoutingInfo(identifier, requestTimeout) {
4906
+ const info = await this.get(identifier);
4907
+ if (info.status !== "pending" /* PENDING */) return info;
4908
+ const budget = requestTimeout ?? this.requestTimeoutMs / 1e3;
4909
+ const observed = await this.waitForSandbox(
4910
+ info.sandboxId,
4911
+ budget,
4912
+ DEFAULT_WAIT_POLL_INTERVAL_SEC
4913
+ );
4914
+ if (isSandboxPending(observed.status)) {
4915
+ throw new SandboxPending(observed.sandboxId, {
4916
+ pendingReason: observed.pendingReason,
4917
+ timeout: budget
4918
+ });
4919
+ }
4920
+ if (observed.status === "terminated" /* TERMINATED */ || observed.status === "failed" /* FAILED */) {
4921
+ throw startupFailure(observed.sandboxId, observed.status, {
4922
+ errorDetails: observed.errorDetails,
4923
+ terminationReason: observed.terminationReason
4924
+ });
4925
+ }
4926
+ return observed;
4927
+ }
4928
+ /** @internal Build the create wire body; shared by `create` and `createAndConnect`. */
4929
+ static buildCreateRequestBody(options) {
4671
4930
  const gpuResources = gpuRequest(
4672
4931
  options?.gpu,
4673
4932
  options?.gpus,
@@ -4697,10 +4956,14 @@ var init_client = __esm({
4697
4956
  deny_out: options?.denyOut ?? []
4698
4957
  };
4699
4958
  }
4700
- return this.tracedJson(
4701
- () => this.native.createSandbox(JSON.stringify(body)),
4702
- "sandboxId"
4703
- );
4959
+ if (options?.maxPendingSecs != null) {
4960
+ if (!Number.isInteger(options.maxPendingSecs) || options.maxPendingSecs < 0) {
4961
+ throw new SandboxError("maxPendingSecs must be a non-negative integer");
4962
+ }
4963
+ body.max_pending_secs = options.maxPendingSecs;
4964
+ }
4965
+ if (options?.wait === false) body.wait = false;
4966
+ return body;
4704
4967
  }
4705
4968
  /** Get current state and metadata for a sandbox by ID. */
4706
4969
  async get(sandboxId) {
@@ -5148,7 +5411,7 @@ var init_client = __esm({
5148
5411
  organizationId: this.organizationId,
5149
5412
  projectId: this.projectId,
5150
5413
  routingHint,
5151
- resolveProxyInfo: async (currentIdentifier) => this.get(currentIdentifier),
5414
+ resolveProxyInfo: async (currentIdentifier) => this.resolveRoutingInfo(currentIdentifier, requestTimeout),
5152
5415
  requestTimeout,
5153
5416
  nativeClient: this.native
5154
5417
  });
@@ -5156,123 +5419,145 @@ var init_client = __esm({
5156
5419
  /**
5157
5420
  * Create a sandbox, wait for it to reach `Running`, and return a connected handle.
5158
5421
  *
5159
- * Blocks until the sandbox is ready or `requestTimeout` elapses. The returned
5160
- * `Sandbox` auto-terminates when `terminate()` is called.
5422
+ * This is `create({ wait: false })` followed by `PendingSandbox.ready()`
5423
+ * polling every `pollInterval` seconds, with `requestTimeout` as the wait
5424
+ * budget, in one call. The returned `Sandbox` auto-terminates when
5425
+ * `terminate()` is called.
5426
+ *
5427
+ * **When the wait runs out the sandbox is not deleted.** A `SandboxPending`
5428
+ * is thrown with the sandbox id, and the sandbox keeps its place in the
5429
+ * queue and starts whenever capacity arrives; `connect` with that id to
5430
+ * collect it, or `delete` it to give up. Pass
5431
+ * `cancelOnTimeout: true` for the previous behaviour (delete, then throw
5432
+ * `SandboxError`), or set `maxPendingSecs` to let the server fail it after
5433
+ * a bound. A pool claim (`poolId`) answers from the server-side wait; a
5434
+ * claim that is still starting is then waited on the same way.
5161
5435
  */
5162
5436
  async createAndConnect(options) {
5163
5437
  const opStart = nowMs();
5164
5438
  const requestTimeout = options?.requestTimeout ?? options?.startupTimeout ?? this.requestTimeoutMs / 1e3;
5165
5439
  const requestClient = this.withRequestTimeout(requestTimeout);
5440
+ const deadline = Date.now() + secondsToMillis3(requestTimeout);
5166
5441
  logSdkTimingEvent("sandbox.create", "start", {
5167
5442
  request_timeout_s: requestTimeout,
5168
5443
  image: options?.image,
5169
5444
  pool_id: options?.poolId
5170
5445
  });
5446
+ const requestedName = options?.poolId != null ? null : options?.name ?? null;
5447
+ let sandboxId;
5448
+ let traceId;
5171
5449
  const createStart = nowMs();
5172
- const result = options?.poolId != null ? await requestClient.claim(options.poolId, {
5173
- fileSystems: options.fileSystems
5174
- }) : await requestClient.create(options);
5175
- logSdkTiming(
5176
- "sandbox.create",
5177
- options?.poolId != null ? "claim_response" : "create_response",
5178
- createStart,
5179
- {
5450
+ if (options?.poolId != null) {
5451
+ const result = await requestClient.claim(options.poolId, {
5452
+ fileSystems: options.fileSystems
5453
+ });
5454
+ logSdkTiming("sandbox.create", "claim_response", createStart, {
5180
5455
  sandbox_id: result.sandboxId,
5181
5456
  status: result.status,
5182
5457
  server_trace_id: result.traceId
5183
- }
5184
- );
5185
- const requestedName = options?.poolId != null ? null : options?.name ?? null;
5186
- const finishConnect = async (sandboxId, routingHint, name, ingressEndpoint, sandboxUrl) => {
5187
- const explicitProxyUrl = options?.proxyUrl ?? explicitProxyUrlOverride() ?? void 0;
5188
- const selectedProxyUrl = await this.native.selectSandboxProxyUrl(
5189
- sandboxId,
5190
- sandboxUrl ?? null,
5191
- ingressEndpoint ?? null,
5192
- explicitProxyUrl ?? null
5193
- );
5194
- const sandbox = requestClient.connect(
5195
- sandboxId,
5196
- selectedProxyUrl,
5197
- routingHint,
5198
- requestTimeout,
5199
- explicitProxyUrl
5200
- );
5201
- sandbox._setOwner(requestClient);
5202
- sandbox.traceId = result.traceId;
5203
- sandbox._setLifecycleIdentifier(sandboxId);
5204
- sandbox._setName(name ?? requestedName);
5205
- logSdkTiming("sandbox.create", "complete", opStart, {
5206
- sandbox_id: sandboxId,
5207
- status: "running" /* RUNNING */,
5208
- server_trace_id: result.traceId,
5209
- routing_hint: routingHint,
5210
- ingress_endpoint: ingressEndpoint,
5211
- sandbox_url: sandboxUrl
5212
5458
  });
5213
- return sandbox;
5214
- };
5215
- if (result.status === "running" /* RUNNING */) {
5216
- return finishConnect(
5217
- result.sandboxId,
5218
- result.routingHint,
5219
- result.name,
5220
- result.ingressEndpoint,
5221
- result.sandboxUrl
5222
- );
5223
- }
5224
- if (result.status === "suspended" /* SUSPENDED */ || result.status === "terminated" /* TERMINATED */ || result.status === "failed" /* FAILED */) {
5225
- throw new SandboxError(
5226
- formatStartupFailureMessage(result.sandboxId, result.status, {
5459
+ if (result.status === "running" /* RUNNING */) {
5460
+ const explicitProxyUrl = options?.proxyUrl ?? explicitProxyUrlOverride() ?? void 0;
5461
+ const selectedProxyUrl = await this.native.selectSandboxProxyUrl(
5462
+ result.sandboxId,
5463
+ result.sandboxUrl ?? null,
5464
+ result.ingressEndpoint ?? null,
5465
+ explicitProxyUrl ?? null
5466
+ );
5467
+ const sandbox2 = requestClient.connect(
5468
+ result.sandboxId,
5469
+ selectedProxyUrl,
5470
+ result.routingHint,
5471
+ requestTimeout,
5472
+ explicitProxyUrl
5473
+ );
5474
+ sandbox2._setOwner(requestClient);
5475
+ sandbox2.traceId = result.traceId;
5476
+ sandbox2._setLifecycleIdentifier(result.sandboxId);
5477
+ sandbox2._setName(result.name ?? requestedName);
5478
+ logSdkTiming("sandbox.create", "complete", opStart, {
5479
+ sandbox_id: result.sandboxId,
5480
+ status: "running" /* RUNNING */,
5481
+ server_trace_id: result.traceId
5482
+ });
5483
+ return sandbox2;
5484
+ }
5485
+ if (result.status === "suspended" /* SUSPENDED */ || result.status === "terminated" /* TERMINATED */ || result.status === "failed" /* FAILED */) {
5486
+ throw startupFailure(result.sandboxId, result.status, {
5227
5487
  errorDetails: result.errorDetails,
5228
5488
  terminationReason: result.terminationReason ?? result.reason
5229
- })
5230
- );
5231
- }
5232
- if (result.status === "timeout" /* TIMEOUT */) {
5233
- try {
5234
- await requestClient.delete(result.sandboxId);
5235
- } catch {
5489
+ });
5236
5490
  }
5237
- throw new SandboxError(
5238
- `Sandbox ${result.sandboxId} did not start within ${requestTimeout}s`
5239
- );
5240
- }
5241
- const deadline = Date.now() + secondsToMillis3(requestTimeout);
5242
- while (Date.now() < deadline) {
5243
- const pollStart = nowMs();
5244
- const info = await requestClient.get(result.sandboxId);
5245
- logSdkTiming("sandbox.create", "poll_response", pollStart, {
5246
- sandbox_id: result.sandboxId,
5247
- status: info.status,
5248
- server_trace_id: info.traceId
5491
+ sandboxId = result.sandboxId;
5492
+ traceId = result.traceId;
5493
+ } else {
5494
+ const pending = await requestClient.requestPending(options, {
5495
+ ownsSandbox: true
5249
5496
  });
5250
- if (info.status === "running" /* RUNNING */) {
5251
- return finishConnect(
5252
- info.sandboxId,
5253
- info.routingHint,
5254
- info.name,
5255
- info.ingressEndpoint,
5256
- info.sandboxUrl
5497
+ logSdkTiming("sandbox.create", "create_response", createStart, {
5498
+ sandbox_id: pending.sandboxId,
5499
+ status: pending.state,
5500
+ server_trace_id: pending.traceId
5501
+ });
5502
+ sandboxId = pending.sandboxId;
5503
+ traceId = pending.traceId;
5504
+ if (pending.state === "running" /* RUNNING */) {
5505
+ const explicitProxyUrl = options?.proxyUrl ?? explicitProxyUrlOverride() ?? void 0;
5506
+ const selectedProxyUrl = await this.native.selectSandboxProxyUrl(
5507
+ sandboxId,
5508
+ pending.sandboxUrl ?? null,
5509
+ pending.ingressEndpoint ?? null,
5510
+ explicitProxyUrl ?? null
5257
5511
  );
5258
- }
5259
- if (info.status === "suspended" /* SUSPENDED */ || info.status === "terminated" /* TERMINATED */) {
5260
- throw new SandboxError(
5261
- formatStartupFailureMessage(result.sandboxId, info.status, {
5262
- errorDetails: info.errorDetails,
5263
- terminationReason: info.terminationReason
5264
- })
5512
+ const sandbox2 = requestClient.connect(
5513
+ sandboxId,
5514
+ selectedProxyUrl,
5515
+ pending.routingHint,
5516
+ requestTimeout,
5517
+ explicitProxyUrl
5265
5518
  );
5519
+ sandbox2._setOwner(requestClient);
5520
+ sandbox2.traceId = traceId;
5521
+ sandbox2._setLifecycleIdentifier(sandboxId);
5522
+ sandbox2._setName(pending.name ?? requestedName);
5523
+ logSdkTiming("sandbox.create", "complete", opStart, {
5524
+ sandbox_id: sandboxId,
5525
+ status: "running" /* RUNNING */,
5526
+ server_trace_id: traceId
5527
+ });
5528
+ return sandbox2;
5529
+ }
5530
+ if (pending.state === "terminated" /* TERMINATED */ || pending.state === "failed" /* FAILED */) {
5531
+ throw startupFailure(sandboxId, pending.state, {
5532
+ errorDetails: pending.errorDetails,
5533
+ terminationReason: pending.terminationReason ?? pending.reason
5534
+ });
5266
5535
  }
5267
- await sleep2(500);
5268
- }
5269
- try {
5270
- await requestClient.delete(result.sandboxId);
5271
- } catch {
5272
5536
  }
5273
- throw new SandboxError(
5274
- `Sandbox ${result.sandboxId} did not start within ${requestTimeout}s`
5537
+ const remaining = Math.max(0, (deadline - Date.now()) / 1e3);
5538
+ const observed = await requestClient.waitForSandbox(
5539
+ sandboxId,
5540
+ remaining,
5541
+ options?.pollInterval ?? DEFAULT_WAIT_POLL_INTERVAL_SEC
5275
5542
  );
5543
+ const sandbox = await requestClient.settleWait(observed, {
5544
+ budget: requestTimeout,
5545
+ proxyUrl: options?.proxyUrl,
5546
+ requestTimeout,
5547
+ cancelOnTimeout: options?.cancelOnTimeout ?? false,
5548
+ requestedName,
5549
+ ownsSandbox: true,
5550
+ traceId
5551
+ });
5552
+ logSdkTiming("sandbox.create", "complete", opStart, {
5553
+ sandbox_id: sandboxId,
5554
+ status: "running" /* RUNNING */,
5555
+ server_trace_id: traceId,
5556
+ routing_hint: observed.routingHint,
5557
+ ingress_endpoint: observed.ingressEndpoint,
5558
+ sandbox_url: observed.sandboxUrl
5559
+ });
5560
+ return sandbox;
5276
5561
  }
5277
5562
  };
5278
5563
  RESERVED_SANDBOX_MANAGEMENT_PORT = 9501;
@@ -6632,6 +6917,7 @@ var init_sandbox_image = __esm({
6632
6917
  init_esm_shims();
6633
6918
  init_client();
6634
6919
  init_sandbox();
6920
+ init_pending_sandbox();
6635
6921
  init_sandbox();
6636
6922
  init_desktop();
6637
6923
  init_tunnel();
@@ -9647,6 +9933,7 @@ export {
9647
9933
  ImageBuildOperationType,
9648
9934
  MountError,
9649
9935
  OutputMode,
9936
+ PendingSandbox,
9650
9937
  PoolInUseError,
9651
9938
  PoolNotFoundError,
9652
9939
  ProcessStatus,
@@ -9665,9 +9952,13 @@ export {
9665
9952
  SandboxError,
9666
9953
  SandboxException,
9667
9954
  SandboxNotFoundError,
9955
+ SandboxPending,
9956
+ SandboxPendingReason,
9668
9957
  SandboxStatus,
9669
9958
  SnapshotStatus,
9670
9959
  StdinMode,
9960
+ TERMINATION_REASON_CANCELLED,
9961
+ TERMINATION_REASON_NO_CAPACITY,
9671
9962
  TcpTunnel,
9672
9963
  createFileSystem,
9673
9964
  createSandboxImage,
@@ -9676,6 +9967,7 @@ export {
9676
9967
  dockerfileContent,
9677
9968
  findSandboxImageByName,
9678
9969
  importSandboxImage,
9970
+ isSandboxPending,
9679
9971
  listFileSystems,
9680
9972
  listSandboxImages,
9681
9973
  registerApplication,