tensorlake 0.5.29 → 0.5.31

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.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/dist/index.cjs CHANGED
@@ -35,7 +35,7 @@ var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TI
35
35
  var init_defaults = __esm({
36
36
  "src/defaults.ts"() {
37
37
  "use strict";
38
- SDK_VERSION = "0.5.29";
38
+ SDK_VERSION = "0.5.31";
39
39
  API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
40
40
  API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
41
41
  NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
@@ -195,13 +195,14 @@ function anySignal(signals) {
195
195
  function sleep(ms) {
196
196
  return new Promise((resolve) => setTimeout(resolve, ms));
197
197
  }
198
- var import_undici, HttpClient;
198
+ var import_undici, REQUEST_TIMEOUT_HEADER, HttpClient;
199
199
  var init_http = __esm({
200
200
  "src/http.ts"() {
201
201
  "use strict";
202
202
  import_undici = require("undici");
203
203
  init_defaults();
204
204
  init_errors();
205
+ REQUEST_TIMEOUT_HEADER = "X-Tensorlake-Request-Timeout-Ms";
205
206
  (0, import_undici.setGlobalDispatcher)(
206
207
  new import_undici.Agent({
207
208
  keepAliveTimeout: 6e4,
@@ -220,10 +221,13 @@ var init_http = __esm({
220
221
  this.baseUrl = url.endsWith("/") ? url.slice(0, -1) : url;
221
222
  this.maxRetries = options.maxRetries ?? MAX_RETRIES;
222
223
  this.retryBackoffMs = options.retryBackoffMs ?? RETRY_BACKOFF_MS;
223
- this.timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
224
+ this.timeoutMs = options.timeoutMs === void 0 ? DEFAULT_HTTP_TIMEOUT_MS : options.timeoutMs;
224
225
  this.headers = {
225
226
  "User-Agent": `tensorlake-typescript-sdk/${SDK_VERSION}`
226
227
  };
228
+ if (this.timeoutMs != null) {
229
+ this.headers[REQUEST_TIMEOUT_HEADER] = String(this.timeoutMs);
230
+ }
227
231
  if (options.apiKey) {
228
232
  this.headers["Authorization"] = `Bearer ${options.apiKey}`;
229
233
  }
@@ -252,7 +256,10 @@ var init_http = __esm({
252
256
  const response = await this.requestResponse(method, path2, {
253
257
  json: options?.body,
254
258
  headers: options?.headers,
255
- signal: options?.signal
259
+ signal: options?.signal,
260
+ retryableStatusCodes: options?.retryableStatusCodes,
261
+ allowedErrorStatusCodes: options?.allowedErrorStatusCodes,
262
+ allowHttpErrors: options?.allowHttpErrors
256
263
  });
257
264
  const text = await response.text();
258
265
  const data = text ? JSON.parse(text) : void 0;
@@ -304,7 +311,9 @@ var init_http = __esm({
304
311
  body,
305
312
  headers,
306
313
  options?.signal,
307
- options?.allowHttpErrors ?? false
314
+ options?.allowHttpErrors ?? false,
315
+ options?.retryableStatusCodes ?? RETRYABLE_STATUS_CODES,
316
+ options?.allowedErrorStatusCodes ?? /* @__PURE__ */ new Set()
308
317
  );
309
318
  return withTraceId(response, traceId);
310
319
  }
@@ -314,7 +323,7 @@ var init_http = __esm({
314
323
  const spanId = randomHex(8);
315
324
  return { traceparent: `00-${traceId}-${spanId}-01`, traceId };
316
325
  }
317
- async doFetch(method, path2, body, headers, signal, allowHttpErrors = false) {
326
+ async doFetch(method, path2, body, headers, signal, allowHttpErrors = false, retryableStatusCodes = RETRYABLE_STATUS_CODES, allowedErrorStatusCodes = /* @__PURE__ */ new Set()) {
318
327
  const url = `${this.baseUrl}${path2}`;
319
328
  const { traceparent, traceId } = _HttpClient.makeTraceparent();
320
329
  headers["traceparent"] = traceparent;
@@ -325,10 +334,7 @@ var init_http = __esm({
325
334
  await sleep(delay);
326
335
  }
327
336
  this.abortController = new AbortController();
328
- const timeoutId = setTimeout(
329
- () => this.abortController?.abort(),
330
- this.timeoutMs
331
- );
337
+ const timeoutId = this.timeoutMs == null ? null : setTimeout(() => this.abortController?.abort(), this.timeoutMs);
332
338
  const combinedSignal = signal ? anySignal([signal, this.abortController.signal]) : this.abortController.signal;
333
339
  try {
334
340
  const response = await (0, import_undici.fetch)(url, {
@@ -337,22 +343,22 @@ var init_http = __esm({
337
343
  body,
338
344
  signal: combinedSignal
339
345
  });
340
- clearTimeout(timeoutId);
346
+ if (timeoutId != null) clearTimeout(timeoutId);
341
347
  if (response.ok) return { response, traceId };
342
- if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < this.maxRetries) {
348
+ if (retryableStatusCodes.has(response.status) && attempt < this.maxRetries) {
343
349
  lastError = new RemoteAPIError(
344
350
  response.status,
345
351
  await response.text().catch(() => "")
346
352
  );
347
353
  continue;
348
354
  }
349
- if (allowHttpErrors) {
355
+ if (allowHttpErrors || allowedErrorStatusCodes.has(response.status)) {
350
356
  return { response, traceId };
351
357
  }
352
358
  const errorBody = await response.text().catch(() => "");
353
359
  throwMappedError(response.status, errorBody, path2);
354
360
  } catch (err) {
355
- clearTimeout(timeoutId);
361
+ if (timeoutId != null) clearTimeout(timeoutId);
356
362
  if (err instanceof RemoteAPIError || err instanceof SandboxNotFoundError || err instanceof PoolNotFoundError || err instanceof PoolInUseError) {
357
363
  throw err;
358
364
  }
@@ -424,6 +430,7 @@ var init_models = __esm({
424
430
  SandboxStatus3["SUSPENDING"] = "suspending";
425
431
  SandboxStatus3["SUSPENDED"] = "suspended";
426
432
  SandboxStatus3["TERMINATED"] = "terminated";
433
+ SandboxStatus3["TIMEOUT"] = "timeout";
427
434
  return SandboxStatus3;
428
435
  })(SandboxStatus || {});
429
436
  SnapshotStatus = /* @__PURE__ */ ((SnapshotStatus2) => {
@@ -2969,6 +2976,14 @@ function resolveProxyTarget(proxyUrl, sandboxId) {
2969
2976
  };
2970
2977
  }
2971
2978
  }
2979
+ function sandboxUrlFromIngressEndpoint(ingressEndpoint, sandboxId, port = 9501) {
2980
+ const parsed = new URL(ingressEndpoint);
2981
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2982
+ throw new Error("ingressEndpoint must be an absolute http(s) URL");
2983
+ }
2984
+ const label = port === 9501 ? sandboxId : `${port}-${sandboxId}`;
2985
+ return `${parsed.protocol}//${label}.${parsed.host}`;
2986
+ }
2972
2987
  function resolveSandboxLifecycleUrl(apiUrl) {
2973
2988
  if (isLocalhost(apiUrl)) return apiUrl;
2974
2989
  try {
@@ -3027,7 +3042,13 @@ function sendPtyFrame(socket, frame) {
3027
3042
  socket.send(frame, (error) => error ? reject(error) : resolve());
3028
3043
  });
3029
3044
  }
3030
- var import_ws2, DEFAULT_PROCESS_USER, PTY_OP_DATA, PTY_OP_RESIZE, PTY_OP_READY, PTY_OP_EXIT, Pty, Sandbox;
3045
+ function secondsToMillis3(seconds) {
3046
+ if (!Number.isFinite(seconds) || seconds <= 0) {
3047
+ throw new SandboxError("requestTimeout must be a positive number of seconds");
3048
+ }
3049
+ return Math.ceil(seconds * 1e3);
3050
+ }
3051
+ var import_ws2, DEFAULT_PROCESS_USER, SandboxProxyConnection, PTY_OP_DATA, PTY_OP_RESIZE, PTY_OP_READY, PTY_OP_EXIT, Pty, Sandbox;
3031
3052
  var init_sandbox = __esm({
3032
3053
  "src/sandbox.ts"() {
3033
3054
  "use strict";
@@ -3041,6 +3062,99 @@ var init_sandbox = __esm({
3041
3062
  init_url();
3042
3063
  import_ws2 = __toESM(require("ws"), 1);
3043
3064
  DEFAULT_PROCESS_USER = "tl-user";
3065
+ SandboxProxyConnection = class {
3066
+ constructor(sandbox, options) {
3067
+ this.sandbox = sandbox;
3068
+ this.options = options;
3069
+ this.routingHint = options.routingHint;
3070
+ this.http = this.configureProxy(
3071
+ options.proxyUrl ?? SANDBOX_PROXY_URL,
3072
+ options.sandboxId,
3073
+ options.routingHint
3074
+ );
3075
+ this.resolveProxyInfo = options.resolveProxyInfo;
3076
+ }
3077
+ baseUrl = "";
3078
+ wsHeaders = {};
3079
+ http;
3080
+ resolveProxyInfo;
3081
+ resolvePromise = null;
3082
+ routingHint;
3083
+ async ensureResolved() {
3084
+ if (this.resolveProxyInfo == null) {
3085
+ return;
3086
+ }
3087
+ if (this.resolvePromise != null) {
3088
+ return this.resolvePromise;
3089
+ }
3090
+ this.resolvePromise = this.resolveProxyInfo(this.sandbox._getLifecycleIdentifier()).then((info) => {
3091
+ this.resolveProxyInfo = void 0;
3092
+ this.sandbox.traceId = info.traceId;
3093
+ this.sandbox._setLifecycleIdentifier(info.sandboxId);
3094
+ this.sandbox._setName(info.name ?? null);
3095
+ this.routingHint = this.routingHint ?? info.routingHint;
3096
+ const proxyUrl = info.ingressEndpoint ?? this.options.proxyUrl ?? SANDBOX_PROXY_URL;
3097
+ const nextHttp = this.configureProxy(proxyUrl, info.sandboxId, this.routingHint);
3098
+ this.http.close();
3099
+ this.http = nextHttp;
3100
+ }).finally(() => {
3101
+ this.resolvePromise = null;
3102
+ });
3103
+ return this.resolvePromise;
3104
+ }
3105
+ async requestJson(method, path2, options) {
3106
+ await this.ensureResolved();
3107
+ return this.http.requestJson(method, path2, options);
3108
+ }
3109
+ async requestBytes(method, path2, options) {
3110
+ await this.ensureResolved();
3111
+ return this.http.requestBytes(method, path2, options);
3112
+ }
3113
+ async requestStream(method, path2, options) {
3114
+ await this.ensureResolved();
3115
+ return this.http.requestStream(method, path2, options);
3116
+ }
3117
+ async requestResponse(method, path2, options) {
3118
+ await this.ensureResolved();
3119
+ return this.http.requestResponse(method, path2, options);
3120
+ }
3121
+ close() {
3122
+ this.http.close();
3123
+ }
3124
+ configureProxy(proxyUrl, sandboxId, routingHint) {
3125
+ const { baseUrl, hostHeader, sandboxIdHeader } = resolveProxyTarget(
3126
+ proxyUrl,
3127
+ sandboxId
3128
+ );
3129
+ this.baseUrl = baseUrl;
3130
+ this.wsHeaders = {};
3131
+ if (this.options.apiKey) {
3132
+ this.wsHeaders.Authorization = `Bearer ${this.options.apiKey}`;
3133
+ }
3134
+ if (this.options.organizationId) {
3135
+ this.wsHeaders["X-Forwarded-Organization-Id"] = this.options.organizationId;
3136
+ }
3137
+ if (this.options.projectId) {
3138
+ this.wsHeaders["X-Forwarded-Project-Id"] = this.options.projectId;
3139
+ }
3140
+ if (hostHeader) {
3141
+ this.wsHeaders.Host = hostHeader;
3142
+ }
3143
+ if (sandboxIdHeader) {
3144
+ this.wsHeaders["X-Tensorlake-Sandbox-Id"] = sandboxIdHeader;
3145
+ }
3146
+ return new HttpClient({
3147
+ baseUrl,
3148
+ apiKey: this.options.apiKey,
3149
+ organizationId: this.options.organizationId,
3150
+ projectId: this.options.projectId,
3151
+ hostHeader,
3152
+ sandboxIdHeader,
3153
+ routingHint,
3154
+ timeoutMs: this.options.requestTimeout != null ? secondsToMillis3(this.options.requestTimeout) : this.options.timeoutMs ?? null
3155
+ });
3156
+ }
3157
+ };
3044
3158
  PTY_OP_DATA = 0;
3045
3159
  PTY_OP_RESIZE = 1;
3046
3160
  PTY_OP_READY = 2;
@@ -3206,9 +3320,8 @@ var init_sandbox = __esm({
3206
3320
  Sandbox = class {
3207
3321
  sandboxId;
3208
3322
  traceId = null;
3323
+ proxy;
3209
3324
  http;
3210
- baseUrl;
3211
- wsHeaders;
3212
3325
  ownsSandbox = false;
3213
3326
  lifecycleClient = null;
3214
3327
  lifecycleIdentifier;
@@ -3216,34 +3329,14 @@ var init_sandbox = __esm({
3216
3329
  constructor(options) {
3217
3330
  this.sandboxId = options.sandboxId;
3218
3331
  this.lifecycleIdentifier = options.sandboxId;
3219
- const proxyUrl = options.proxyUrl ?? SANDBOX_PROXY_URL;
3220
- const { baseUrl, hostHeader, sandboxIdHeader } = resolveProxyTarget(proxyUrl, options.sandboxId);
3221
- this.baseUrl = baseUrl;
3222
- this.wsHeaders = {};
3223
- if (options.apiKey) {
3224
- this.wsHeaders.Authorization = `Bearer ${options.apiKey}`;
3225
- }
3226
- if (options.organizationId) {
3227
- this.wsHeaders["X-Forwarded-Organization-Id"] = options.organizationId;
3228
- }
3229
- if (options.projectId) {
3230
- this.wsHeaders["X-Forwarded-Project-Id"] = options.projectId;
3231
- }
3232
- if (hostHeader) {
3233
- this.wsHeaders.Host = hostHeader;
3234
- }
3235
- if (sandboxIdHeader) {
3236
- this.wsHeaders["X-Tensorlake-Sandbox-Id"] = sandboxIdHeader;
3237
- }
3238
- this.http = new HttpClient({
3239
- baseUrl,
3240
- apiKey: options.apiKey,
3241
- organizationId: options.organizationId,
3242
- projectId: options.projectId,
3243
- hostHeader,
3244
- sandboxIdHeader,
3245
- routingHint: options.routingHint
3246
- });
3332
+ this.proxy = new SandboxProxyConnection(this, options);
3333
+ this.http = this.proxy;
3334
+ }
3335
+ get baseUrl() {
3336
+ return this.proxy.baseUrl;
3337
+ }
3338
+ get wsHeaders() {
3339
+ return this.proxy.wsHeaders;
3247
3340
  }
3248
3341
  get name() {
3249
3342
  return this.sandboxName;
@@ -3256,6 +3349,10 @@ var init_sandbox = __esm({
3256
3349
  _setLifecycleIdentifier(identifier) {
3257
3350
  this.lifecycleIdentifier = identifier;
3258
3351
  }
3352
+ /** @internal Used by the lazy proxy resolver. */
3353
+ _getLifecycleIdentifier() {
3354
+ return this.lifecycleIdentifier;
3355
+ }
3259
3356
  /** @internal Used by SandboxClient.createAndConnect to set ownership. */
3260
3357
  _setOwner(client) {
3261
3358
  this.ownsSandbox = true;
@@ -3275,16 +3372,14 @@ var init_sandbox = __esm({
3275
3372
  /* _internal */
3276
3373
  true
3277
3374
  );
3278
- const sandbox = await client.createAndConnect(options);
3279
- sandbox.lifecycleClient = client;
3280
- return sandbox;
3375
+ return client.createAndConnect(options);
3281
3376
  }
3282
3377
  /**
3283
3378
  * Attach to an existing sandbox and return a connected handle.
3284
3379
  *
3285
- * Returns immediately without contacting the server. Call `sandbox.info()`
3286
- * to fetch the current state on demand. Does **not** auto-resume a suspended
3287
- * sandbox — call `sandbox.resume()` explicitly.
3380
+ * When `proxyUrl` is omitted, resolves the sandbox first so the handle uses
3381
+ * the correct cloud/region ingress endpoint. Does **not** auto-resume a
3382
+ * suspended sandbox — call `sandbox.resume()` explicitly.
3288
3383
  */
3289
3384
  static async connect(options) {
3290
3385
  const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
@@ -3296,7 +3391,8 @@ var init_sandbox = __esm({
3296
3391
  const sandbox = client.connect(
3297
3392
  options.sandboxId,
3298
3393
  options.proxyUrl,
3299
- options.routingHint
3394
+ options.routingHint,
3395
+ options.requestTimeout
3300
3396
  );
3301
3397
  sandbox.lifecycleClient = client;
3302
3398
  return sandbox;
@@ -3698,6 +3794,7 @@ var init_sandbox = __esm({
3698
3794
  }
3699
3795
  /** Attach to an existing PTY session by ID and token and return a connected `Pty` handle. */
3700
3796
  async connectPty(sessionId, token, options) {
3797
+ await this.proxy.ensureResolved();
3701
3798
  const wsUrl = new URL(this.ptyWsUrl(sessionId, token));
3702
3799
  const authToken = wsUrl.searchParams.get("token") ?? token;
3703
3800
  const pty = new Pty({
@@ -3723,6 +3820,7 @@ var init_sandbox = __esm({
3723
3820
  }
3724
3821
  /** Open a TCP tunnel to a port inside the sandbox and return the local listener. */
3725
3822
  async createTunnel(remotePort, options) {
3823
+ await this.proxy.ensureResolved();
3726
3824
  return TcpTunnel.listen({
3727
3825
  baseUrl: this.baseUrl,
3728
3826
  wsHeaders: this.wsHeaders,
@@ -3734,6 +3832,7 @@ var init_sandbox = __esm({
3734
3832
  }
3735
3833
  /** Connect to a sandbox VNC session for programmatic desktop control. */
3736
3834
  async connectDesktop(options) {
3835
+ await this.proxy.ensureResolved();
3737
3836
  const port = options?.port ?? 5901;
3738
3837
  const connectTimeout = options?.connectTimeout ?? 10;
3739
3838
  const startMs = Date.now();
@@ -3818,6 +3917,27 @@ var client_exports = {};
3818
3917
  __export(client_exports, {
3819
3918
  SandboxClient: () => SandboxClient
3820
3919
  });
3920
+ function resolveRequestTimeoutMs(options) {
3921
+ if (options?.requestTimeout != null) {
3922
+ return secondsToMillis4(options.requestTimeout);
3923
+ }
3924
+ if (options?.timeoutMs != null) {
3925
+ validateTimeoutMs(options.timeoutMs);
3926
+ return options.timeoutMs;
3927
+ }
3928
+ return DEFAULT_HTTP_TIMEOUT_MS;
3929
+ }
3930
+ function secondsToMillis4(seconds) {
3931
+ if (!Number.isFinite(seconds) || seconds <= 0) {
3932
+ throw new SandboxError("requestTimeout must be a positive number of seconds");
3933
+ }
3934
+ return Math.ceil(seconds * 1e3);
3935
+ }
3936
+ function validateTimeoutMs(timeoutMs) {
3937
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
3938
+ throw new SandboxError("timeoutMs must be a positive number of milliseconds");
3939
+ }
3940
+ }
3821
3941
  function sleep2(ms) {
3822
3942
  return new Promise((resolve) => setTimeout(resolve, ms));
3823
3943
  }
@@ -3874,7 +3994,7 @@ function validateUserPort(port) {
3874
3994
  function dedupeAndSortPorts(ports) {
3875
3995
  return [...new Set(ports)].sort((a, b) => a - b);
3876
3996
  }
3877
- var SandboxClient, RESERVED_SANDBOX_MANAGEMENT_PORT;
3997
+ var CREATE_SANDBOX_RETRYABLE_STATUS_CODES, CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES, SandboxClient, RESERVED_SANDBOX_MANAGEMENT_PORT;
3878
3998
  var init_client = __esm({
3879
3999
  "src/client.ts"() {
3880
4000
  "use strict";
@@ -3884,6 +4004,8 @@ var init_client = __esm({
3884
4004
  init_models();
3885
4005
  init_sandbox();
3886
4006
  init_url();
4007
+ CREATE_SANDBOX_RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([502, 503]);
4008
+ CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES = /* @__PURE__ */ new Set([504]);
3887
4009
  SandboxClient = class _SandboxClient {
3888
4010
  http;
3889
4011
  apiUrl;
@@ -3892,6 +4014,9 @@ var init_client = __esm({
3892
4014
  projectId;
3893
4015
  namespace;
3894
4016
  local;
4017
+ maxRetries;
4018
+ retryBackoffMs;
4019
+ requestTimeoutMs;
3895
4020
  /** @internal Pass `true` to suppress the deprecation warning when used by `Sandbox.create()` / `Sandbox.connect()`. */
3896
4021
  constructor(options, _internal = false) {
3897
4022
  if (!_internal) {
@@ -3905,13 +4030,17 @@ var init_client = __esm({
3905
4030
  this.projectId = options?.projectId;
3906
4031
  this.namespace = options?.namespace ?? NAMESPACE;
3907
4032
  this.local = isLocalhost(this.apiUrl);
4033
+ this.maxRetries = options?.maxRetries ?? MAX_RETRIES;
4034
+ this.retryBackoffMs = options?.retryBackoffMs ?? RETRY_BACKOFF_MS;
4035
+ this.requestTimeoutMs = resolveRequestTimeoutMs(options);
3908
4036
  this.http = new HttpClient({
3909
4037
  baseUrl: resolveSandboxLifecycleUrl(this.apiUrl),
3910
4038
  apiKey: this.apiKey,
3911
4039
  organizationId: this.organizationId,
3912
4040
  projectId: this.projectId,
3913
- maxRetries: options?.maxRetries ?? MAX_RETRIES,
3914
- retryBackoffMs: options?.retryBackoffMs ?? RETRY_BACKOFF_MS
4041
+ maxRetries: this.maxRetries,
4042
+ retryBackoffMs: this.retryBackoffMs,
4043
+ timeoutMs: this.requestTimeoutMs
3915
4044
  });
3916
4045
  }
3917
4046
  /** Create a client for the TensorLake cloud platform. */
@@ -3920,19 +4049,46 @@ var init_client = __esm({
3920
4049
  apiUrl: options?.apiUrl ?? "https://api.tensorlake.ai",
3921
4050
  apiKey: options?.apiKey,
3922
4051
  organizationId: options?.organizationId,
3923
- projectId: options?.projectId
4052
+ projectId: options?.projectId,
4053
+ requestTimeout: options?.requestTimeout,
4054
+ timeoutMs: options?.timeoutMs
3924
4055
  });
3925
4056
  }
3926
4057
  /** Create a client for a local Indexify server. */
3927
4058
  static forLocalhost(options) {
3928
4059
  return new _SandboxClient({
3929
4060
  apiUrl: options?.apiUrl ?? "http://localhost:8900",
3930
- namespace: options?.namespace ?? "default"
4061
+ namespace: options?.namespace ?? "default",
4062
+ requestTimeout: options?.requestTimeout,
4063
+ timeoutMs: options?.timeoutMs
3931
4064
  });
3932
4065
  }
3933
4066
  close() {
3934
4067
  this.http.close();
3935
4068
  }
4069
+ withRequestTimeout(requestTimeout) {
4070
+ if (requestTimeout == null) {
4071
+ return this;
4072
+ }
4073
+ const timeoutMs = secondsToMillis4(requestTimeout);
4074
+ if (timeoutMs === this.requestTimeoutMs) {
4075
+ return this;
4076
+ }
4077
+ return new _SandboxClient(
4078
+ {
4079
+ apiUrl: this.apiUrl,
4080
+ apiKey: this.apiKey,
4081
+ organizationId: this.organizationId,
4082
+ projectId: this.projectId,
4083
+ namespace: this.namespace,
4084
+ maxRetries: this.maxRetries,
4085
+ retryBackoffMs: this.retryBackoffMs,
4086
+ timeoutMs
4087
+ },
4088
+ /* _internal */
4089
+ true
4090
+ );
4091
+ }
3936
4092
  // --- Path helper ---
3937
4093
  path(subpath) {
3938
4094
  return lifecyclePath(subpath, this.local, this.namespace);
@@ -3963,7 +4119,11 @@ var init_client = __esm({
3963
4119
  const raw = await this.http.requestJson(
3964
4120
  "POST",
3965
4121
  this.path("sandboxes"),
3966
- { body }
4122
+ {
4123
+ body,
4124
+ retryableStatusCodes: CREATE_SANDBOX_RETRYABLE_STATUS_CODES,
4125
+ allowedErrorStatusCodes: CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES
4126
+ }
3967
4127
  );
3968
4128
  const result = fromSnakeKeys(raw, "sandboxId");
3969
4129
  return Object.assign(result, { traceId: raw.traceId });
@@ -4053,6 +4213,7 @@ var init_client = __esm({
4053
4213
  return {
4054
4214
  allowUnauthenticatedAccess: info.allowUnauthenticatedAccess ?? false,
4055
4215
  exposedPorts: dedupeAndSortPorts(info.exposedPorts ?? []),
4216
+ ingressEndpoint: info.ingressEndpoint,
4056
4217
  sandboxUrl: info.sandboxUrl
4057
4218
  };
4058
4219
  }
@@ -4156,7 +4317,11 @@ var init_client = __esm({
4156
4317
  async claim(poolId) {
4157
4318
  const raw = await this.http.requestJson(
4158
4319
  "POST",
4159
- this.path(`sandbox-pools/${poolId}/sandboxes`)
4320
+ this.path(`sandbox-pools/${poolId}/sandboxes`),
4321
+ {
4322
+ retryableStatusCodes: CREATE_SANDBOX_RETRYABLE_STATUS_CODES,
4323
+ allowedErrorStatusCodes: CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES
4324
+ }
4160
4325
  );
4161
4326
  const result = fromSnakeKeys(raw, "sandboxId");
4162
4327
  return Object.assign(result, { traceId: raw.traceId });
@@ -4318,7 +4483,7 @@ var init_client = __esm({
4318
4483
  }
4319
4484
  // --- Connect ---
4320
4485
  /** Return a `Sandbox` handle for an existing running sandbox without verifying it exists. */
4321
- connect(identifier, proxyUrl, routingHint) {
4486
+ connect(identifier, proxyUrl, routingHint, requestTimeout) {
4322
4487
  const resolvedProxy = proxyUrl ?? resolveProxyUrl(this.apiUrl);
4323
4488
  return new Sandbox({
4324
4489
  sandboxId: identifier,
@@ -4326,32 +4491,41 @@ var init_client = __esm({
4326
4491
  apiKey: this.apiKey,
4327
4492
  organizationId: this.organizationId,
4328
4493
  projectId: this.projectId,
4329
- routingHint
4494
+ routingHint,
4495
+ resolveProxyInfo: proxyUrl == null ? async (currentIdentifier) => this.get(currentIdentifier) : void 0,
4496
+ requestTimeout
4330
4497
  });
4331
4498
  }
4332
4499
  /**
4333
4500
  * Create a sandbox, wait for it to reach `Running`, and return a connected handle.
4334
4501
  *
4335
- * Blocks until the sandbox is ready or `startupTimeout` elapses. The returned
4502
+ * Blocks until the sandbox is ready or `requestTimeout` elapses. The returned
4336
4503
  * `Sandbox` auto-terminates when `terminate()` is called.
4337
4504
  *
4338
- * @param options.startupTimeout - Max seconds to wait for `Running` status (default 60).
4505
+ * @param options.requestTimeout - Max seconds to wait for `Running` status (default: client requestTimeout).
4506
+ * @param options.startupTimeout - Deprecated alias for `requestTimeout`.
4339
4507
  * @throws {SandboxError} If the sandbox terminates during startup or the timeout elapses.
4340
4508
  */
4341
4509
  async createAndConnect(options) {
4342
- const startupTimeout = options?.startupTimeout ?? 60;
4343
- const result = options?.poolId != null ? await this.claim(options.poolId) : await this.create(options);
4510
+ const requestTimeout = options?.requestTimeout ?? options?.startupTimeout ?? this.requestTimeoutMs / 1e3;
4511
+ const requestClient = this.withRequestTimeout(requestTimeout);
4512
+ const result = options?.poolId != null ? await requestClient.claim(options.poolId) : await requestClient.create(options);
4344
4513
  const requestedName = options?.poolId != null ? null : options?.name ?? null;
4345
- const finishConnect = (routingHint, name) => {
4346
- const sandbox = this.connect(result.sandboxId, options?.proxyUrl, routingHint);
4347
- sandbox._setOwner(this);
4514
+ const finishConnect = (routingHint, name, ingressEndpoint) => {
4515
+ const sandbox = requestClient.connect(
4516
+ result.sandboxId,
4517
+ options?.proxyUrl ?? ingressEndpoint,
4518
+ routingHint,
4519
+ requestTimeout
4520
+ );
4521
+ sandbox._setOwner(requestClient);
4348
4522
  sandbox.traceId = result.traceId;
4349
4523
  sandbox._setLifecycleIdentifier(result.sandboxId);
4350
4524
  sandbox._setName(name ?? requestedName);
4351
4525
  return sandbox;
4352
4526
  };
4353
4527
  if (result.status === "running" /* RUNNING */) {
4354
- return finishConnect(result.routingHint, result.name);
4528
+ return finishConnect(result.routingHint, result.name, result.ingressEndpoint);
4355
4529
  }
4356
4530
  if (result.status === "suspended" /* SUSPENDED */ || result.status === "terminated" /* TERMINATED */) {
4357
4531
  throw new SandboxError(
@@ -4361,11 +4535,20 @@ var init_client = __esm({
4361
4535
  })
4362
4536
  );
4363
4537
  }
4364
- const deadline = Date.now() + startupTimeout * 1e3;
4538
+ if (result.status === "timeout" /* TIMEOUT */) {
4539
+ try {
4540
+ await requestClient.delete(result.sandboxId);
4541
+ } catch {
4542
+ }
4543
+ throw new SandboxError(
4544
+ `Sandbox ${result.sandboxId} did not start within ${requestTimeout}s`
4545
+ );
4546
+ }
4547
+ const deadline = Date.now() + secondsToMillis4(requestTimeout);
4365
4548
  while (Date.now() < deadline) {
4366
- const info = await this.get(result.sandboxId);
4549
+ const info = await requestClient.get(result.sandboxId);
4367
4550
  if (info.status === "running" /* RUNNING */) {
4368
- return finishConnect(info.routingHint, info.name);
4551
+ return finishConnect(info.routingHint, info.name, info.ingressEndpoint);
4369
4552
  }
4370
4553
  if (info.status === "suspended" /* SUSPENDED */ || info.status === "terminated" /* TERMINATED */) {
4371
4554
  throw new SandboxError(
@@ -4378,11 +4561,11 @@ var init_client = __esm({
4378
4561
  await sleep2(500);
4379
4562
  }
4380
4563
  try {
4381
- await this.delete(result.sandboxId);
4564
+ await requestClient.delete(result.sandboxId);
4382
4565
  } catch {
4383
4566
  }
4384
4567
  throw new SandboxError(
4385
- `Sandbox ${result.sandboxId} did not start within ${startupTimeout}s`
4568
+ `Sandbox ${result.sandboxId} did not start within ${requestTimeout}s`
4386
4569
  );
4387
4570
  }
4388
4571
  };
@@ -4772,7 +4955,8 @@ __export(index_exports, {
4772
4955
  StdinMode: () => StdinMode,
4773
4956
  TcpTunnel: () => TcpTunnel,
4774
4957
  createSandboxImage: () => createSandboxImage,
4775
- dockerfileContent: () => dockerfileContent
4958
+ dockerfileContent: () => dockerfileContent,
4959
+ sandboxUrlFromIngressEndpoint: () => sandboxUrlFromIngressEndpoint
4776
4960
  });
4777
4961
  module.exports = __toCommonJS(index_exports);
4778
4962
  init_client();
@@ -5165,6 +5349,7 @@ var APIClient = class {
5165
5349
  // src/index.ts
5166
5350
  init_sandbox_image();
5167
5351
  init_image();
5352
+ init_url();
5168
5353
  init_errors();
5169
5354
  init_models();
5170
5355
  // Annotate the CommonJS export names for ESM import in node:
@@ -5195,6 +5380,7 @@ init_models();
5195
5380
  StdinMode,
5196
5381
  TcpTunnel,
5197
5382
  createSandboxImage,
5198
- dockerfileContent
5383
+ dockerfileContent,
5384
+ sandboxUrlFromIngressEndpoint
5199
5385
  });
5200
5386
  //# sourceMappingURL=index.cjs.map