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.
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TI
19
19
  var init_defaults = __esm({
20
20
  "src/defaults.ts"() {
21
21
  "use strict";
22
- SDK_VERSION = "0.5.29";
22
+ SDK_VERSION = "0.5.31";
23
23
  API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
24
24
  API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
25
25
  NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
@@ -184,12 +184,13 @@ function anySignal(signals) {
184
184
  function sleep(ms) {
185
185
  return new Promise((resolve) => setTimeout(resolve, ms));
186
186
  }
187
- var HttpClient;
187
+ var REQUEST_TIMEOUT_HEADER, HttpClient;
188
188
  var init_http = __esm({
189
189
  "src/http.ts"() {
190
190
  "use strict";
191
191
  init_defaults();
192
192
  init_errors();
193
+ REQUEST_TIMEOUT_HEADER = "X-Tensorlake-Request-Timeout-Ms";
193
194
  setGlobalDispatcher(
194
195
  new Agent({
195
196
  keepAliveTimeout: 6e4,
@@ -208,10 +209,13 @@ var init_http = __esm({
208
209
  this.baseUrl = url.endsWith("/") ? url.slice(0, -1) : url;
209
210
  this.maxRetries = options.maxRetries ?? MAX_RETRIES;
210
211
  this.retryBackoffMs = options.retryBackoffMs ?? RETRY_BACKOFF_MS;
211
- this.timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
212
+ this.timeoutMs = options.timeoutMs === void 0 ? DEFAULT_HTTP_TIMEOUT_MS : options.timeoutMs;
212
213
  this.headers = {
213
214
  "User-Agent": `tensorlake-typescript-sdk/${SDK_VERSION}`
214
215
  };
216
+ if (this.timeoutMs != null) {
217
+ this.headers[REQUEST_TIMEOUT_HEADER] = String(this.timeoutMs);
218
+ }
215
219
  if (options.apiKey) {
216
220
  this.headers["Authorization"] = `Bearer ${options.apiKey}`;
217
221
  }
@@ -240,7 +244,10 @@ var init_http = __esm({
240
244
  const response = await this.requestResponse(method, path2, {
241
245
  json: options?.body,
242
246
  headers: options?.headers,
243
- signal: options?.signal
247
+ signal: options?.signal,
248
+ retryableStatusCodes: options?.retryableStatusCodes,
249
+ allowedErrorStatusCodes: options?.allowedErrorStatusCodes,
250
+ allowHttpErrors: options?.allowHttpErrors
244
251
  });
245
252
  const text = await response.text();
246
253
  const data = text ? JSON.parse(text) : void 0;
@@ -292,7 +299,9 @@ var init_http = __esm({
292
299
  body,
293
300
  headers,
294
301
  options?.signal,
295
- options?.allowHttpErrors ?? false
302
+ options?.allowHttpErrors ?? false,
303
+ options?.retryableStatusCodes ?? RETRYABLE_STATUS_CODES,
304
+ options?.allowedErrorStatusCodes ?? /* @__PURE__ */ new Set()
296
305
  );
297
306
  return withTraceId(response, traceId);
298
307
  }
@@ -302,7 +311,7 @@ var init_http = __esm({
302
311
  const spanId = randomHex(8);
303
312
  return { traceparent: `00-${traceId}-${spanId}-01`, traceId };
304
313
  }
305
- async doFetch(method, path2, body, headers, signal, allowHttpErrors = false) {
314
+ async doFetch(method, path2, body, headers, signal, allowHttpErrors = false, retryableStatusCodes = RETRYABLE_STATUS_CODES, allowedErrorStatusCodes = /* @__PURE__ */ new Set()) {
306
315
  const url = `${this.baseUrl}${path2}`;
307
316
  const { traceparent, traceId } = _HttpClient.makeTraceparent();
308
317
  headers["traceparent"] = traceparent;
@@ -313,10 +322,7 @@ var init_http = __esm({
313
322
  await sleep(delay);
314
323
  }
315
324
  this.abortController = new AbortController();
316
- const timeoutId = setTimeout(
317
- () => this.abortController?.abort(),
318
- this.timeoutMs
319
- );
325
+ const timeoutId = this.timeoutMs == null ? null : setTimeout(() => this.abortController?.abort(), this.timeoutMs);
320
326
  const combinedSignal = signal ? anySignal([signal, this.abortController.signal]) : this.abortController.signal;
321
327
  try {
322
328
  const response = await undiciFetch(url, {
@@ -325,22 +331,22 @@ var init_http = __esm({
325
331
  body,
326
332
  signal: combinedSignal
327
333
  });
328
- clearTimeout(timeoutId);
334
+ if (timeoutId != null) clearTimeout(timeoutId);
329
335
  if (response.ok) return { response, traceId };
330
- if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < this.maxRetries) {
336
+ if (retryableStatusCodes.has(response.status) && attempt < this.maxRetries) {
331
337
  lastError = new RemoteAPIError(
332
338
  response.status,
333
339
  await response.text().catch(() => "")
334
340
  );
335
341
  continue;
336
342
  }
337
- if (allowHttpErrors) {
343
+ if (allowHttpErrors || allowedErrorStatusCodes.has(response.status)) {
338
344
  return { response, traceId };
339
345
  }
340
346
  const errorBody = await response.text().catch(() => "");
341
347
  throwMappedError(response.status, errorBody, path2);
342
348
  } catch (err) {
343
- clearTimeout(timeoutId);
349
+ if (timeoutId != null) clearTimeout(timeoutId);
344
350
  if (err instanceof RemoteAPIError || err instanceof SandboxNotFoundError || err instanceof PoolNotFoundError || err instanceof PoolInUseError) {
345
351
  throw err;
346
352
  }
@@ -412,6 +418,7 @@ var init_models = __esm({
412
418
  SandboxStatus3["SUSPENDING"] = "suspending";
413
419
  SandboxStatus3["SUSPENDED"] = "suspended";
414
420
  SandboxStatus3["TERMINATED"] = "terminated";
421
+ SandboxStatus3["TIMEOUT"] = "timeout";
415
422
  return SandboxStatus3;
416
423
  })(SandboxStatus || {});
417
424
  SnapshotStatus = /* @__PURE__ */ ((SnapshotStatus2) => {
@@ -2957,6 +2964,14 @@ function resolveProxyTarget(proxyUrl, sandboxId) {
2957
2964
  };
2958
2965
  }
2959
2966
  }
2967
+ function sandboxUrlFromIngressEndpoint(ingressEndpoint, sandboxId, port = 9501) {
2968
+ const parsed = new URL(ingressEndpoint);
2969
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2970
+ throw new Error("ingressEndpoint must be an absolute http(s) URL");
2971
+ }
2972
+ const label = port === 9501 ? sandboxId : `${port}-${sandboxId}`;
2973
+ return `${parsed.protocol}//${label}.${parsed.host}`;
2974
+ }
2960
2975
  function resolveSandboxLifecycleUrl(apiUrl) {
2961
2976
  if (isLocalhost(apiUrl)) return apiUrl;
2962
2977
  try {
@@ -3016,7 +3031,13 @@ function sendPtyFrame(socket, frame) {
3016
3031
  socket.send(frame, (error) => error ? reject(error) : resolve());
3017
3032
  });
3018
3033
  }
3019
- var DEFAULT_PROCESS_USER, PTY_OP_DATA, PTY_OP_RESIZE, PTY_OP_READY, PTY_OP_EXIT, Pty, Sandbox;
3034
+ function secondsToMillis3(seconds) {
3035
+ if (!Number.isFinite(seconds) || seconds <= 0) {
3036
+ throw new SandboxError("requestTimeout must be a positive number of seconds");
3037
+ }
3038
+ return Math.ceil(seconds * 1e3);
3039
+ }
3040
+ var DEFAULT_PROCESS_USER, SandboxProxyConnection, PTY_OP_DATA, PTY_OP_RESIZE, PTY_OP_READY, PTY_OP_EXIT, Pty, Sandbox;
3020
3041
  var init_sandbox = __esm({
3021
3042
  "src/sandbox.ts"() {
3022
3043
  "use strict";
@@ -3029,6 +3050,99 @@ var init_sandbox = __esm({
3029
3050
  init_sse();
3030
3051
  init_url();
3031
3052
  DEFAULT_PROCESS_USER = "tl-user";
3053
+ SandboxProxyConnection = class {
3054
+ constructor(sandbox, options) {
3055
+ this.sandbox = sandbox;
3056
+ this.options = options;
3057
+ this.routingHint = options.routingHint;
3058
+ this.http = this.configureProxy(
3059
+ options.proxyUrl ?? SANDBOX_PROXY_URL,
3060
+ options.sandboxId,
3061
+ options.routingHint
3062
+ );
3063
+ this.resolveProxyInfo = options.resolveProxyInfo;
3064
+ }
3065
+ baseUrl = "";
3066
+ wsHeaders = {};
3067
+ http;
3068
+ resolveProxyInfo;
3069
+ resolvePromise = null;
3070
+ routingHint;
3071
+ async ensureResolved() {
3072
+ if (this.resolveProxyInfo == null) {
3073
+ return;
3074
+ }
3075
+ if (this.resolvePromise != null) {
3076
+ return this.resolvePromise;
3077
+ }
3078
+ this.resolvePromise = this.resolveProxyInfo(this.sandbox._getLifecycleIdentifier()).then((info) => {
3079
+ this.resolveProxyInfo = void 0;
3080
+ this.sandbox.traceId = info.traceId;
3081
+ this.sandbox._setLifecycleIdentifier(info.sandboxId);
3082
+ this.sandbox._setName(info.name ?? null);
3083
+ this.routingHint = this.routingHint ?? info.routingHint;
3084
+ const proxyUrl = info.ingressEndpoint ?? this.options.proxyUrl ?? SANDBOX_PROXY_URL;
3085
+ const nextHttp = this.configureProxy(proxyUrl, info.sandboxId, this.routingHint);
3086
+ this.http.close();
3087
+ this.http = nextHttp;
3088
+ }).finally(() => {
3089
+ this.resolvePromise = null;
3090
+ });
3091
+ return this.resolvePromise;
3092
+ }
3093
+ async requestJson(method, path2, options) {
3094
+ await this.ensureResolved();
3095
+ return this.http.requestJson(method, path2, options);
3096
+ }
3097
+ async requestBytes(method, path2, options) {
3098
+ await this.ensureResolved();
3099
+ return this.http.requestBytes(method, path2, options);
3100
+ }
3101
+ async requestStream(method, path2, options) {
3102
+ await this.ensureResolved();
3103
+ return this.http.requestStream(method, path2, options);
3104
+ }
3105
+ async requestResponse(method, path2, options) {
3106
+ await this.ensureResolved();
3107
+ return this.http.requestResponse(method, path2, options);
3108
+ }
3109
+ close() {
3110
+ this.http.close();
3111
+ }
3112
+ configureProxy(proxyUrl, sandboxId, routingHint) {
3113
+ const { baseUrl, hostHeader, sandboxIdHeader } = resolveProxyTarget(
3114
+ proxyUrl,
3115
+ sandboxId
3116
+ );
3117
+ this.baseUrl = baseUrl;
3118
+ this.wsHeaders = {};
3119
+ if (this.options.apiKey) {
3120
+ this.wsHeaders.Authorization = `Bearer ${this.options.apiKey}`;
3121
+ }
3122
+ if (this.options.organizationId) {
3123
+ this.wsHeaders["X-Forwarded-Organization-Id"] = this.options.organizationId;
3124
+ }
3125
+ if (this.options.projectId) {
3126
+ this.wsHeaders["X-Forwarded-Project-Id"] = this.options.projectId;
3127
+ }
3128
+ if (hostHeader) {
3129
+ this.wsHeaders.Host = hostHeader;
3130
+ }
3131
+ if (sandboxIdHeader) {
3132
+ this.wsHeaders["X-Tensorlake-Sandbox-Id"] = sandboxIdHeader;
3133
+ }
3134
+ return new HttpClient({
3135
+ baseUrl,
3136
+ apiKey: this.options.apiKey,
3137
+ organizationId: this.options.organizationId,
3138
+ projectId: this.options.projectId,
3139
+ hostHeader,
3140
+ sandboxIdHeader,
3141
+ routingHint,
3142
+ timeoutMs: this.options.requestTimeout != null ? secondsToMillis3(this.options.requestTimeout) : this.options.timeoutMs ?? null
3143
+ });
3144
+ }
3145
+ };
3032
3146
  PTY_OP_DATA = 0;
3033
3147
  PTY_OP_RESIZE = 1;
3034
3148
  PTY_OP_READY = 2;
@@ -3194,9 +3308,8 @@ var init_sandbox = __esm({
3194
3308
  Sandbox = class {
3195
3309
  sandboxId;
3196
3310
  traceId = null;
3311
+ proxy;
3197
3312
  http;
3198
- baseUrl;
3199
- wsHeaders;
3200
3313
  ownsSandbox = false;
3201
3314
  lifecycleClient = null;
3202
3315
  lifecycleIdentifier;
@@ -3204,34 +3317,14 @@ var init_sandbox = __esm({
3204
3317
  constructor(options) {
3205
3318
  this.sandboxId = options.sandboxId;
3206
3319
  this.lifecycleIdentifier = options.sandboxId;
3207
- const proxyUrl = options.proxyUrl ?? SANDBOX_PROXY_URL;
3208
- const { baseUrl, hostHeader, sandboxIdHeader } = resolveProxyTarget(proxyUrl, options.sandboxId);
3209
- this.baseUrl = baseUrl;
3210
- this.wsHeaders = {};
3211
- if (options.apiKey) {
3212
- this.wsHeaders.Authorization = `Bearer ${options.apiKey}`;
3213
- }
3214
- if (options.organizationId) {
3215
- this.wsHeaders["X-Forwarded-Organization-Id"] = options.organizationId;
3216
- }
3217
- if (options.projectId) {
3218
- this.wsHeaders["X-Forwarded-Project-Id"] = options.projectId;
3219
- }
3220
- if (hostHeader) {
3221
- this.wsHeaders.Host = hostHeader;
3222
- }
3223
- if (sandboxIdHeader) {
3224
- this.wsHeaders["X-Tensorlake-Sandbox-Id"] = sandboxIdHeader;
3225
- }
3226
- this.http = new HttpClient({
3227
- baseUrl,
3228
- apiKey: options.apiKey,
3229
- organizationId: options.organizationId,
3230
- projectId: options.projectId,
3231
- hostHeader,
3232
- sandboxIdHeader,
3233
- routingHint: options.routingHint
3234
- });
3320
+ this.proxy = new SandboxProxyConnection(this, options);
3321
+ this.http = this.proxy;
3322
+ }
3323
+ get baseUrl() {
3324
+ return this.proxy.baseUrl;
3325
+ }
3326
+ get wsHeaders() {
3327
+ return this.proxy.wsHeaders;
3235
3328
  }
3236
3329
  get name() {
3237
3330
  return this.sandboxName;
@@ -3244,6 +3337,10 @@ var init_sandbox = __esm({
3244
3337
  _setLifecycleIdentifier(identifier) {
3245
3338
  this.lifecycleIdentifier = identifier;
3246
3339
  }
3340
+ /** @internal Used by the lazy proxy resolver. */
3341
+ _getLifecycleIdentifier() {
3342
+ return this.lifecycleIdentifier;
3343
+ }
3247
3344
  /** @internal Used by SandboxClient.createAndConnect to set ownership. */
3248
3345
  _setOwner(client) {
3249
3346
  this.ownsSandbox = true;
@@ -3263,16 +3360,14 @@ var init_sandbox = __esm({
3263
3360
  /* _internal */
3264
3361
  true
3265
3362
  );
3266
- const sandbox = await client.createAndConnect(options);
3267
- sandbox.lifecycleClient = client;
3268
- return sandbox;
3363
+ return client.createAndConnect(options);
3269
3364
  }
3270
3365
  /**
3271
3366
  * Attach to an existing sandbox and return a connected handle.
3272
3367
  *
3273
- * Returns immediately without contacting the server. Call `sandbox.info()`
3274
- * to fetch the current state on demand. Does **not** auto-resume a suspended
3275
- * sandbox — call `sandbox.resume()` explicitly.
3368
+ * When `proxyUrl` is omitted, resolves the sandbox first so the handle uses
3369
+ * the correct cloud/region ingress endpoint. Does **not** auto-resume a
3370
+ * suspended sandbox — call `sandbox.resume()` explicitly.
3276
3371
  */
3277
3372
  static async connect(options) {
3278
3373
  const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
@@ -3284,7 +3379,8 @@ var init_sandbox = __esm({
3284
3379
  const sandbox = client.connect(
3285
3380
  options.sandboxId,
3286
3381
  options.proxyUrl,
3287
- options.routingHint
3382
+ options.routingHint,
3383
+ options.requestTimeout
3288
3384
  );
3289
3385
  sandbox.lifecycleClient = client;
3290
3386
  return sandbox;
@@ -3686,6 +3782,7 @@ var init_sandbox = __esm({
3686
3782
  }
3687
3783
  /** Attach to an existing PTY session by ID and token and return a connected `Pty` handle. */
3688
3784
  async connectPty(sessionId, token, options) {
3785
+ await this.proxy.ensureResolved();
3689
3786
  const wsUrl = new URL(this.ptyWsUrl(sessionId, token));
3690
3787
  const authToken = wsUrl.searchParams.get("token") ?? token;
3691
3788
  const pty = new Pty({
@@ -3711,6 +3808,7 @@ var init_sandbox = __esm({
3711
3808
  }
3712
3809
  /** Open a TCP tunnel to a port inside the sandbox and return the local listener. */
3713
3810
  async createTunnel(remotePort, options) {
3811
+ await this.proxy.ensureResolved();
3714
3812
  return TcpTunnel.listen({
3715
3813
  baseUrl: this.baseUrl,
3716
3814
  wsHeaders: this.wsHeaders,
@@ -3722,6 +3820,7 @@ var init_sandbox = __esm({
3722
3820
  }
3723
3821
  /** Connect to a sandbox VNC session for programmatic desktop control. */
3724
3822
  async connectDesktop(options) {
3823
+ await this.proxy.ensureResolved();
3725
3824
  const port = options?.port ?? 5901;
3726
3825
  const connectTimeout = options?.connectTimeout ?? 10;
3727
3826
  const startMs = Date.now();
@@ -3806,6 +3905,27 @@ var client_exports = {};
3806
3905
  __export(client_exports, {
3807
3906
  SandboxClient: () => SandboxClient
3808
3907
  });
3908
+ function resolveRequestTimeoutMs(options) {
3909
+ if (options?.requestTimeout != null) {
3910
+ return secondsToMillis4(options.requestTimeout);
3911
+ }
3912
+ if (options?.timeoutMs != null) {
3913
+ validateTimeoutMs(options.timeoutMs);
3914
+ return options.timeoutMs;
3915
+ }
3916
+ return DEFAULT_HTTP_TIMEOUT_MS;
3917
+ }
3918
+ function secondsToMillis4(seconds) {
3919
+ if (!Number.isFinite(seconds) || seconds <= 0) {
3920
+ throw new SandboxError("requestTimeout must be a positive number of seconds");
3921
+ }
3922
+ return Math.ceil(seconds * 1e3);
3923
+ }
3924
+ function validateTimeoutMs(timeoutMs) {
3925
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
3926
+ throw new SandboxError("timeoutMs must be a positive number of milliseconds");
3927
+ }
3928
+ }
3809
3929
  function sleep2(ms) {
3810
3930
  return new Promise((resolve) => setTimeout(resolve, ms));
3811
3931
  }
@@ -3862,7 +3982,7 @@ function validateUserPort(port) {
3862
3982
  function dedupeAndSortPorts(ports) {
3863
3983
  return [...new Set(ports)].sort((a, b) => a - b);
3864
3984
  }
3865
- var SandboxClient, RESERVED_SANDBOX_MANAGEMENT_PORT;
3985
+ var CREATE_SANDBOX_RETRYABLE_STATUS_CODES, CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES, SandboxClient, RESERVED_SANDBOX_MANAGEMENT_PORT;
3866
3986
  var init_client = __esm({
3867
3987
  "src/client.ts"() {
3868
3988
  "use strict";
@@ -3872,6 +3992,8 @@ var init_client = __esm({
3872
3992
  init_models();
3873
3993
  init_sandbox();
3874
3994
  init_url();
3995
+ CREATE_SANDBOX_RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([502, 503]);
3996
+ CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES = /* @__PURE__ */ new Set([504]);
3875
3997
  SandboxClient = class _SandboxClient {
3876
3998
  http;
3877
3999
  apiUrl;
@@ -3880,6 +4002,9 @@ var init_client = __esm({
3880
4002
  projectId;
3881
4003
  namespace;
3882
4004
  local;
4005
+ maxRetries;
4006
+ retryBackoffMs;
4007
+ requestTimeoutMs;
3883
4008
  /** @internal Pass `true` to suppress the deprecation warning when used by `Sandbox.create()` / `Sandbox.connect()`. */
3884
4009
  constructor(options, _internal = false) {
3885
4010
  if (!_internal) {
@@ -3893,13 +4018,17 @@ var init_client = __esm({
3893
4018
  this.projectId = options?.projectId;
3894
4019
  this.namespace = options?.namespace ?? NAMESPACE;
3895
4020
  this.local = isLocalhost(this.apiUrl);
4021
+ this.maxRetries = options?.maxRetries ?? MAX_RETRIES;
4022
+ this.retryBackoffMs = options?.retryBackoffMs ?? RETRY_BACKOFF_MS;
4023
+ this.requestTimeoutMs = resolveRequestTimeoutMs(options);
3896
4024
  this.http = new HttpClient({
3897
4025
  baseUrl: resolveSandboxLifecycleUrl(this.apiUrl),
3898
4026
  apiKey: this.apiKey,
3899
4027
  organizationId: this.organizationId,
3900
4028
  projectId: this.projectId,
3901
- maxRetries: options?.maxRetries ?? MAX_RETRIES,
3902
- retryBackoffMs: options?.retryBackoffMs ?? RETRY_BACKOFF_MS
4029
+ maxRetries: this.maxRetries,
4030
+ retryBackoffMs: this.retryBackoffMs,
4031
+ timeoutMs: this.requestTimeoutMs
3903
4032
  });
3904
4033
  }
3905
4034
  /** Create a client for the TensorLake cloud platform. */
@@ -3908,19 +4037,46 @@ var init_client = __esm({
3908
4037
  apiUrl: options?.apiUrl ?? "https://api.tensorlake.ai",
3909
4038
  apiKey: options?.apiKey,
3910
4039
  organizationId: options?.organizationId,
3911
- projectId: options?.projectId
4040
+ projectId: options?.projectId,
4041
+ requestTimeout: options?.requestTimeout,
4042
+ timeoutMs: options?.timeoutMs
3912
4043
  });
3913
4044
  }
3914
4045
  /** Create a client for a local Indexify server. */
3915
4046
  static forLocalhost(options) {
3916
4047
  return new _SandboxClient({
3917
4048
  apiUrl: options?.apiUrl ?? "http://localhost:8900",
3918
- namespace: options?.namespace ?? "default"
4049
+ namespace: options?.namespace ?? "default",
4050
+ requestTimeout: options?.requestTimeout,
4051
+ timeoutMs: options?.timeoutMs
3919
4052
  });
3920
4053
  }
3921
4054
  close() {
3922
4055
  this.http.close();
3923
4056
  }
4057
+ withRequestTimeout(requestTimeout) {
4058
+ if (requestTimeout == null) {
4059
+ return this;
4060
+ }
4061
+ const timeoutMs = secondsToMillis4(requestTimeout);
4062
+ if (timeoutMs === this.requestTimeoutMs) {
4063
+ return this;
4064
+ }
4065
+ return new _SandboxClient(
4066
+ {
4067
+ apiUrl: this.apiUrl,
4068
+ apiKey: this.apiKey,
4069
+ organizationId: this.organizationId,
4070
+ projectId: this.projectId,
4071
+ namespace: this.namespace,
4072
+ maxRetries: this.maxRetries,
4073
+ retryBackoffMs: this.retryBackoffMs,
4074
+ timeoutMs
4075
+ },
4076
+ /* _internal */
4077
+ true
4078
+ );
4079
+ }
3924
4080
  // --- Path helper ---
3925
4081
  path(subpath) {
3926
4082
  return lifecyclePath(subpath, this.local, this.namespace);
@@ -3951,7 +4107,11 @@ var init_client = __esm({
3951
4107
  const raw = await this.http.requestJson(
3952
4108
  "POST",
3953
4109
  this.path("sandboxes"),
3954
- { body }
4110
+ {
4111
+ body,
4112
+ retryableStatusCodes: CREATE_SANDBOX_RETRYABLE_STATUS_CODES,
4113
+ allowedErrorStatusCodes: CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES
4114
+ }
3955
4115
  );
3956
4116
  const result = fromSnakeKeys(raw, "sandboxId");
3957
4117
  return Object.assign(result, { traceId: raw.traceId });
@@ -4041,6 +4201,7 @@ var init_client = __esm({
4041
4201
  return {
4042
4202
  allowUnauthenticatedAccess: info.allowUnauthenticatedAccess ?? false,
4043
4203
  exposedPorts: dedupeAndSortPorts(info.exposedPorts ?? []),
4204
+ ingressEndpoint: info.ingressEndpoint,
4044
4205
  sandboxUrl: info.sandboxUrl
4045
4206
  };
4046
4207
  }
@@ -4144,7 +4305,11 @@ var init_client = __esm({
4144
4305
  async claim(poolId) {
4145
4306
  const raw = await this.http.requestJson(
4146
4307
  "POST",
4147
- this.path(`sandbox-pools/${poolId}/sandboxes`)
4308
+ this.path(`sandbox-pools/${poolId}/sandboxes`),
4309
+ {
4310
+ retryableStatusCodes: CREATE_SANDBOX_RETRYABLE_STATUS_CODES,
4311
+ allowedErrorStatusCodes: CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES
4312
+ }
4148
4313
  );
4149
4314
  const result = fromSnakeKeys(raw, "sandboxId");
4150
4315
  return Object.assign(result, { traceId: raw.traceId });
@@ -4306,7 +4471,7 @@ var init_client = __esm({
4306
4471
  }
4307
4472
  // --- Connect ---
4308
4473
  /** Return a `Sandbox` handle for an existing running sandbox without verifying it exists. */
4309
- connect(identifier, proxyUrl, routingHint) {
4474
+ connect(identifier, proxyUrl, routingHint, requestTimeout) {
4310
4475
  const resolvedProxy = proxyUrl ?? resolveProxyUrl(this.apiUrl);
4311
4476
  return new Sandbox({
4312
4477
  sandboxId: identifier,
@@ -4314,32 +4479,41 @@ var init_client = __esm({
4314
4479
  apiKey: this.apiKey,
4315
4480
  organizationId: this.organizationId,
4316
4481
  projectId: this.projectId,
4317
- routingHint
4482
+ routingHint,
4483
+ resolveProxyInfo: proxyUrl == null ? async (currentIdentifier) => this.get(currentIdentifier) : void 0,
4484
+ requestTimeout
4318
4485
  });
4319
4486
  }
4320
4487
  /**
4321
4488
  * Create a sandbox, wait for it to reach `Running`, and return a connected handle.
4322
4489
  *
4323
- * Blocks until the sandbox is ready or `startupTimeout` elapses. The returned
4490
+ * Blocks until the sandbox is ready or `requestTimeout` elapses. The returned
4324
4491
  * `Sandbox` auto-terminates when `terminate()` is called.
4325
4492
  *
4326
- * @param options.startupTimeout - Max seconds to wait for `Running` status (default 60).
4493
+ * @param options.requestTimeout - Max seconds to wait for `Running` status (default: client requestTimeout).
4494
+ * @param options.startupTimeout - Deprecated alias for `requestTimeout`.
4327
4495
  * @throws {SandboxError} If the sandbox terminates during startup or the timeout elapses.
4328
4496
  */
4329
4497
  async createAndConnect(options) {
4330
- const startupTimeout = options?.startupTimeout ?? 60;
4331
- const result = options?.poolId != null ? await this.claim(options.poolId) : await this.create(options);
4498
+ const requestTimeout = options?.requestTimeout ?? options?.startupTimeout ?? this.requestTimeoutMs / 1e3;
4499
+ const requestClient = this.withRequestTimeout(requestTimeout);
4500
+ const result = options?.poolId != null ? await requestClient.claim(options.poolId) : await requestClient.create(options);
4332
4501
  const requestedName = options?.poolId != null ? null : options?.name ?? null;
4333
- const finishConnect = (routingHint, name) => {
4334
- const sandbox = this.connect(result.sandboxId, options?.proxyUrl, routingHint);
4335
- sandbox._setOwner(this);
4502
+ const finishConnect = (routingHint, name, ingressEndpoint) => {
4503
+ const sandbox = requestClient.connect(
4504
+ result.sandboxId,
4505
+ options?.proxyUrl ?? ingressEndpoint,
4506
+ routingHint,
4507
+ requestTimeout
4508
+ );
4509
+ sandbox._setOwner(requestClient);
4336
4510
  sandbox.traceId = result.traceId;
4337
4511
  sandbox._setLifecycleIdentifier(result.sandboxId);
4338
4512
  sandbox._setName(name ?? requestedName);
4339
4513
  return sandbox;
4340
4514
  };
4341
4515
  if (result.status === "running" /* RUNNING */) {
4342
- return finishConnect(result.routingHint, result.name);
4516
+ return finishConnect(result.routingHint, result.name, result.ingressEndpoint);
4343
4517
  }
4344
4518
  if (result.status === "suspended" /* SUSPENDED */ || result.status === "terminated" /* TERMINATED */) {
4345
4519
  throw new SandboxError(
@@ -4349,11 +4523,20 @@ var init_client = __esm({
4349
4523
  })
4350
4524
  );
4351
4525
  }
4352
- const deadline = Date.now() + startupTimeout * 1e3;
4526
+ if (result.status === "timeout" /* TIMEOUT */) {
4527
+ try {
4528
+ await requestClient.delete(result.sandboxId);
4529
+ } catch {
4530
+ }
4531
+ throw new SandboxError(
4532
+ `Sandbox ${result.sandboxId} did not start within ${requestTimeout}s`
4533
+ );
4534
+ }
4535
+ const deadline = Date.now() + secondsToMillis4(requestTimeout);
4353
4536
  while (Date.now() < deadline) {
4354
- const info = await this.get(result.sandboxId);
4537
+ const info = await requestClient.get(result.sandboxId);
4355
4538
  if (info.status === "running" /* RUNNING */) {
4356
- return finishConnect(info.routingHint, info.name);
4539
+ return finishConnect(info.routingHint, info.name, info.ingressEndpoint);
4357
4540
  }
4358
4541
  if (info.status === "suspended" /* SUSPENDED */ || info.status === "terminated" /* TERMINATED */) {
4359
4542
  throw new SandboxError(
@@ -4366,11 +4549,11 @@ var init_client = __esm({
4366
4549
  await sleep2(500);
4367
4550
  }
4368
4551
  try {
4369
- await this.delete(result.sandboxId);
4552
+ await requestClient.delete(result.sandboxId);
4370
4553
  } catch {
4371
4554
  }
4372
4555
  throw new SandboxError(
4373
- `Sandbox ${result.sandboxId} did not start within ${startupTimeout}s`
4556
+ `Sandbox ${result.sandboxId} did not start within ${requestTimeout}s`
4374
4557
  );
4375
4558
  }
4376
4559
  };
@@ -5121,6 +5304,7 @@ var APIClient = class {
5121
5304
  // src/index.ts
5122
5305
  init_sandbox_image();
5123
5306
  init_image();
5307
+ init_url();
5124
5308
  init_errors();
5125
5309
  init_models();
5126
5310
  export {
@@ -5150,6 +5334,7 @@ export {
5150
5334
  StdinMode,
5151
5335
  TcpTunnel,
5152
5336
  createSandboxImage,
5153
- dockerfileContent
5337
+ dockerfileContent,
5338
+ sandboxUrlFromIngressEndpoint
5154
5339
  };
5155
5340
  //# sourceMappingURL=index.js.map