tensorlake 0.5.38 → 0.5.40

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.38";
22
+ SDK_VERSION = "0.5.40";
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";
@@ -139,364 +139,154 @@ var init_errors = __esm({
139
139
  }
140
140
  });
141
141
 
142
- // src/sdk-timings.ts
143
- function sdkTimingsEnabled() {
144
- const value = process.env[SDK_TIMINGS_ENV_VAR];
145
- return value === "1" || value === "true" || value === "yes";
146
- }
147
- function sdkTimingPayloadsEnabled() {
148
- const value = process.env[SDK_TIMING_PAYLOADS_ENV_VAR];
149
- return value === "1" || value === "true" || value === "yes";
150
- }
151
- function nowMs() {
152
- return performance.now();
153
- }
154
- function logSdkTiming(op, phase, startedAt, fields = {}) {
155
- if (!sdkTimingsEnabled()) return;
156
- logSdkTimingEvent(op, phase, {
157
- ...fields,
158
- duration_ms: Math.round((nowMs() - startedAt) * 100) / 100
159
- });
160
- }
161
- function logSdkTimingEvent(op, phase, fields = {}) {
162
- if (!sdkTimingsEnabled()) return;
163
- const parts = [
164
- "[tensorlake:sdk-timing]",
165
- `op=${op}`,
166
- `phase=${phase}`
167
- ];
168
- for (const [key, value] of Object.entries(fields)) {
169
- if (value == null) continue;
170
- parts.push(`${key}=${formatSdkTimingValue(value)}`);
142
+ // src/native-sandbox.ts
143
+ import { createRequire } from "module";
144
+ function resolveRequire() {
145
+ if (typeof module !== "undefined" && module.exports != null && typeof __require !== "undefined") {
146
+ return __require;
171
147
  }
172
- console.error(parts.join(" "));
148
+ return createRequire(import.meta.url);
173
149
  }
174
- function formatSdkTimingValue(value) {
175
- if (typeof value === "number" || typeof value === "boolean") {
176
- return String(value);
150
+ function loadNativeSandboxBinding() {
151
+ if (cachedBinding) return cachedBinding;
152
+ if (cachedBindingError) throw cachedBindingError;
153
+ try {
154
+ const { loadNative } = resolveRequire()("../lib/runtime.cjs");
155
+ const binding = loadNative();
156
+ if (binding == null || typeof binding.NativeSandboxClient !== "function" || typeof binding.NativeSandboxProxyClient !== "function") {
157
+ throw new SandboxError(
158
+ "native binding does not export the sandbox clients; rebuild with 'npm run build:native'"
159
+ );
160
+ }
161
+ cachedBinding = binding;
162
+ return cachedBinding;
163
+ } catch (error) {
164
+ cachedBindingError = error instanceof Error ? error : new Error(String(error));
165
+ throw cachedBindingError;
177
166
  }
178
- if (/^[A-Za-z0-9_./:@-]+$/.test(value)) {
179
- return value;
167
+ }
168
+ function parseNativeError(error) {
169
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
170
+ if (!message) return null;
171
+ try {
172
+ const parsed = JSON.parse(message);
173
+ if (parsed && typeof parsed.category === "string") {
174
+ return {
175
+ category: parsed.category,
176
+ status: typeof parsed.status === "number" ? parsed.status : null,
177
+ message: typeof parsed.message === "string" ? parsed.message : message
178
+ };
179
+ }
180
+ } catch {
180
181
  }
181
- return JSON.stringify(value);
182
+ return null;
182
183
  }
183
- var SDK_TIMINGS_ENV_VAR, SDK_TIMING_PAYLOADS_ENV_VAR;
184
- var init_sdk_timings = __esm({
185
- "src/sdk-timings.ts"() {
186
- "use strict";
187
- SDK_TIMINGS_ENV_VAR = "TENSORLAKE_SDK_TIMINGS";
188
- SDK_TIMING_PAYLOADS_ENV_VAR = "TENSORLAKE_SDK_TIMING_PAYLOADS";
184
+ function translateNativeError(error, context) {
185
+ const payload = parseNativeError(error);
186
+ if (!payload) {
187
+ if (error instanceof Error) return error;
188
+ return new SandboxError(String(error));
189
189
  }
190
- });
191
-
192
- // src/http.ts
193
- import {
194
- Agent,
195
- fetch as undiciFetch,
196
- setGlobalDispatcher
197
- } from "undici";
198
- function withTraceId(value, traceId) {
199
- if (value == null) {
200
- return { traceId };
190
+ const { category, status, message } = payload;
191
+ if (category === "connection") {
192
+ return new SandboxConnectionError(message, { cause: error });
201
193
  }
202
- return Object.assign(value, { traceId });
203
- }
204
- function hasHeader(headers, name) {
205
- const lowered = name.toLowerCase();
206
- return Object.keys(headers).some((key) => key.toLowerCase() === lowered);
207
- }
208
- function normalizeRequestBody(body) {
209
- if (body == null) {
210
- return void 0;
194
+ if (status === 404) {
195
+ if (context?.notFoundKind === "pool" && context.poolId) {
196
+ return new PoolNotFoundError(context.poolId);
197
+ }
198
+ if (context?.notFoundKind === "sandbox" && context.sandboxId) {
199
+ return new SandboxNotFoundError(context.sandboxId);
200
+ }
201
+ return new RemoteAPIError(404, message);
211
202
  }
212
- if (body instanceof Uint8Array) {
213
- return Uint8Array.from(body).buffer;
203
+ if (status === 409 && context?.poolId) {
204
+ return new PoolInUseError(context.poolId, message);
214
205
  }
215
- return body;
216
- }
217
- function stripPathPayload(path2) {
218
- const queryStart = path2.search(/[?#]/);
219
- return queryStart === -1 ? path2 : path2.slice(0, queryStart);
206
+ if (typeof status === "number") {
207
+ return new RemoteAPIError(status, message);
208
+ }
209
+ return new SandboxError(message);
220
210
  }
221
- function throwMappedError(status, body, path2) {
222
- let message = body;
211
+ async function callNative(fn, context) {
223
212
  try {
224
- const parsed = JSON.parse(body);
225
- if (parsed.message) message = parsed.message;
226
- else if (parsed.error) message = parsed.error;
227
- } catch {
213
+ return await fn();
214
+ } catch (error) {
215
+ throw translateNativeError(error, context);
228
216
  }
229
- if (status === 404) {
230
- if (path2.includes("sandbox-pools") || path2.includes("pools")) {
231
- const match = path2.match(/sandbox-pools\/([^/]+)/);
232
- if (match) throw new PoolNotFoundError(match[1]);
217
+ }
218
+ async function* nativeEventStream(start, context, signal) {
219
+ const queue = [];
220
+ let wake = null;
221
+ let finished = false;
222
+ let failure = null;
223
+ const notify = () => {
224
+ if (wake) {
225
+ const w = wake;
226
+ wake = null;
227
+ w();
233
228
  }
234
- if (path2.includes("sandboxes")) {
235
- const match = path2.match(/sandboxes\/([^/]+)/);
236
- if (match) throw new SandboxNotFoundError(match[1]);
229
+ };
230
+ const emit = (eventJson) => {
231
+ queue.push(eventJson);
232
+ notify();
233
+ };
234
+ const onAbort = () => notify();
235
+ signal?.addEventListener("abort", onAbort, { once: true });
236
+ const startPromise = start(emit).catch((error) => {
237
+ failure = error;
238
+ }).finally(() => {
239
+ finished = true;
240
+ notify();
241
+ });
242
+ try {
243
+ while (true) {
244
+ if (queue.length > 0) {
245
+ yield JSON.parse(queue.shift());
246
+ continue;
247
+ }
248
+ if (signal?.aborted) break;
249
+ if (finished) break;
250
+ await new Promise((resolve) => {
251
+ wake = resolve;
252
+ });
237
253
  }
238
- throw new RemoteAPIError(404, message);
239
- }
240
- if (status === 409) {
241
- if (path2.includes("sandbox-pools") || path2.includes("pools")) {
242
- const match = path2.match(/sandbox-pools\/([^/]+)/);
243
- if (match) throw new PoolInUseError(match[1], message);
254
+ if (failure != null) {
255
+ throw translateNativeError(failure, context);
244
256
  }
257
+ } finally {
258
+ signal?.removeEventListener("abort", onAbort);
259
+ void startPromise.catch(() => {
260
+ });
245
261
  }
246
- throw new RemoteAPIError(status, message);
247
262
  }
248
- function anySignal(signals) {
249
- const controller = new AbortController();
250
- for (const signal of signals) {
251
- if (signal.aborted) {
252
- controller.abort(signal.reason);
253
- return controller.signal;
263
+ function assembleCommandResult(events) {
264
+ const stdoutLines = [];
265
+ const stderrLines = [];
266
+ let exitCode = -1;
267
+ for (const eventJson of events) {
268
+ const raw = JSON.parse(eventJson);
269
+ if (typeof raw.line === "string") {
270
+ if (raw.stream === "stderr") {
271
+ stderrLines.push(raw.line);
272
+ } else {
273
+ stdoutLines.push(raw.line);
274
+ }
275
+ } else if ("exit_code" in raw || "signal" in raw) {
276
+ if (typeof raw.exit_code === "number") {
277
+ exitCode = raw.exit_code;
278
+ } else if (typeof raw.signal === "number") {
279
+ exitCode = -raw.signal;
280
+ }
254
281
  }
255
- signal.addEventListener("abort", () => controller.abort(signal.reason), {
256
- once: true
257
- });
258
282
  }
259
- return controller.signal;
260
- }
261
- function sleep(ms) {
262
- return new Promise((resolve) => setTimeout(resolve, ms));
283
+ return { exitCode, stdout: stdoutLines.join("\n"), stderr: stderrLines.join("\n") };
263
284
  }
264
- var REQUEST_TIMEOUT_HEADER, HttpClient;
265
- var init_http = __esm({
266
- "src/http.ts"() {
285
+ var cachedBinding, cachedBindingError;
286
+ var init_native_sandbox = __esm({
287
+ "src/native-sandbox.ts"() {
267
288
  "use strict";
268
- init_defaults();
269
289
  init_errors();
270
- init_sdk_timings();
271
- REQUEST_TIMEOUT_HEADER = "X-Tensorlake-Request-Timeout-Ms";
272
- setGlobalDispatcher(
273
- new Agent({
274
- keepAliveTimeout: 6e4,
275
- allowH2: true
276
- })
277
- );
278
- HttpClient = class _HttpClient {
279
- baseUrl;
280
- headers;
281
- maxRetries;
282
- retryBackoffMs;
283
- timeoutMs;
284
- abortController = null;
285
- constructor(options) {
286
- const url = options.baseUrl;
287
- this.baseUrl = url.endsWith("/") ? url.slice(0, -1) : url;
288
- this.maxRetries = options.maxRetries ?? MAX_RETRIES;
289
- this.retryBackoffMs = options.retryBackoffMs ?? RETRY_BACKOFF_MS;
290
- this.timeoutMs = options.timeoutMs === void 0 ? DEFAULT_HTTP_TIMEOUT_MS : options.timeoutMs;
291
- this.headers = {
292
- "User-Agent": `tensorlake-typescript-sdk/${SDK_VERSION}`
293
- };
294
- if (this.timeoutMs != null) {
295
- this.headers[REQUEST_TIMEOUT_HEADER] = String(this.timeoutMs);
296
- }
297
- if (options.apiKey) {
298
- this.headers["Authorization"] = `Bearer ${options.apiKey}`;
299
- }
300
- if (options.organizationId) {
301
- this.headers["X-Forwarded-Organization-Id"] = options.organizationId;
302
- }
303
- if (options.projectId) {
304
- this.headers["X-Forwarded-Project-Id"] = options.projectId;
305
- }
306
- if (options.hostHeader) {
307
- this.headers["Host"] = options.hostHeader;
308
- }
309
- if (options.sandboxIdHeader) {
310
- this.headers["X-Tensorlake-Sandbox-Id"] = options.sandboxIdHeader;
311
- }
312
- if (options.routingHint) {
313
- this.headers["X-Tensorlake-Route-Hint"] = options.routingHint;
314
- }
315
- }
316
- close() {
317
- this.abortController?.abort();
318
- this.abortController = null;
319
- }
320
- /** Make a JSON request, returning the parsed response body. */
321
- async requestJson(method, path2, options) {
322
- const response = await this.requestResponse(method, path2, {
323
- json: options?.body,
324
- headers: options?.headers,
325
- signal: options?.signal,
326
- retryableStatusCodes: options?.retryableStatusCodes,
327
- allowedErrorStatusCodes: options?.allowedErrorStatusCodes,
328
- allowHttpErrors: options?.allowHttpErrors
329
- });
330
- const text = await response.text();
331
- const data = text ? JSON.parse(text) : void 0;
332
- return withTraceId(data, response.traceId);
333
- }
334
- /** Make a request returning raw bytes. */
335
- async requestBytes(method, path2, options) {
336
- const headers = { ...options?.headers ?? {} };
337
- if (options?.contentType) {
338
- headers["Content-Type"] = options.contentType;
339
- }
340
- const response = await this.requestResponse(method, path2, {
341
- body: options?.body,
342
- headers,
343
- signal: options?.signal
344
- });
345
- const buffer = await response.arrayBuffer();
346
- return withTraceId(new Uint8Array(buffer), response.traceId);
347
- }
348
- /** Make a request and return the response body as an SSE stream. */
349
- async requestStream(method, path2, options) {
350
- const response = await this.requestResponse(method, path2, {
351
- json: options?.json,
352
- headers: { Accept: "text/event-stream" },
353
- signal: options?.signal
354
- });
355
- if (!response.body) {
356
- throw new RemoteAPIError(
357
- response.status,
358
- "No response body for SSE stream"
359
- );
360
- }
361
- return withTraceId(response.body, response.traceId);
362
- }
363
- /** Make a request and return the raw Response. */
364
- async requestResponse(method, path2, options) {
365
- const headers = {
366
- ...this.headers,
367
- ...options?.headers ?? {}
368
- };
369
- const hasJsonBody = options?.json !== void 0;
370
- if (hasJsonBody && !hasHeader(headers, "Content-Type")) {
371
- headers["Content-Type"] = "application/json";
372
- }
373
- const body = hasJsonBody ? JSON.stringify(options?.json) : normalizeRequestBody(options?.body);
374
- const { response, traceId } = await this.doFetch(
375
- method,
376
- path2,
377
- body,
378
- headers,
379
- options?.signal,
380
- options?.allowHttpErrors ?? false,
381
- options?.retryableStatusCodes ?? RETRYABLE_STATUS_CODES,
382
- options?.allowedErrorStatusCodes ?? /* @__PURE__ */ new Set()
383
- );
384
- return withTraceId(response, traceId);
385
- }
386
- static makeTraceparent() {
387
- const randomHex = (bytes) => Array.from(crypto.getRandomValues(new Uint8Array(bytes))).map((b) => b.toString(16).padStart(2, "0")).join("");
388
- const traceId = randomHex(16);
389
- const spanId = randomHex(8);
390
- return { traceparent: `00-${traceId}-${spanId}-01`, traceId };
391
- }
392
- async doFetch(method, path2, body, headers, signal, allowHttpErrors = false, retryableStatusCodes = RETRYABLE_STATUS_CODES, allowedErrorStatusCodes = /* @__PURE__ */ new Set()) {
393
- const url = `${this.baseUrl}${path2}`;
394
- const { traceparent, traceId } = _HttpClient.makeTraceparent();
395
- headers["traceparent"] = traceparent;
396
- let lastError;
397
- const operationStart = nowMs();
398
- const timingPath = sdkTimingPayloadsEnabled() ? path2 : stripPathPayload(path2);
399
- for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
400
- if (attempt > 0) {
401
- const delay = this.retryBackoffMs * Math.pow(2, attempt - 1);
402
- logSdkTimingEvent("http.request", "retry_sleep_start", {
403
- method,
404
- path: timingPath,
405
- attempt,
406
- delay_ms: delay,
407
- server_trace_id: traceId
408
- });
409
- await sleep(delay);
410
- }
411
- this.abortController = new AbortController();
412
- let timedOut = false;
413
- const timeoutId = this.timeoutMs == null ? null : setTimeout(() => {
414
- timedOut = true;
415
- this.abortController?.abort();
416
- }, this.timeoutMs);
417
- const combinedSignal = signal ? anySignal([signal, this.abortController.signal]) : this.abortController.signal;
418
- const attemptStart = nowMs();
419
- try {
420
- const response = await undiciFetch(url, {
421
- method,
422
- headers,
423
- body,
424
- signal: combinedSignal
425
- });
426
- if (timeoutId != null) clearTimeout(timeoutId);
427
- logSdkTiming("http.request", "headers", attemptStart, {
428
- method,
429
- path: timingPath,
430
- status: response.status,
431
- attempt,
432
- server_trace_id: traceId
433
- });
434
- if (response.ok) {
435
- logSdkTiming("http.request", "complete", operationStart, {
436
- method,
437
- path: timingPath,
438
- status: response.status,
439
- attempts: attempt + 1,
440
- server_trace_id: traceId
441
- });
442
- return { response, traceId };
443
- }
444
- if (retryableStatusCodes.has(response.status) && attempt < this.maxRetries) {
445
- lastError = new RemoteAPIError(
446
- response.status,
447
- await response.text().catch(() => "")
448
- );
449
- logSdkTimingEvent("http.request", "retryable_status", {
450
- method,
451
- path: timingPath,
452
- status: response.status,
453
- attempt,
454
- server_trace_id: traceId
455
- });
456
- continue;
457
- }
458
- if (allowHttpErrors || allowedErrorStatusCodes.has(response.status)) {
459
- logSdkTiming("http.request", "complete", operationStart, {
460
- method,
461
- path: timingPath,
462
- status: response.status,
463
- attempts: attempt + 1,
464
- server_trace_id: traceId
465
- });
466
- return { response, traceId };
467
- }
468
- const errorBody = await response.text().catch(() => "");
469
- throwMappedError(response.status, errorBody, path2);
470
- } catch (err) {
471
- if (timeoutId != null) clearTimeout(timeoutId);
472
- if (err instanceof RemoteAPIError || err instanceof SandboxNotFoundError || err instanceof PoolNotFoundError || err instanceof PoolInUseError) {
473
- throw err;
474
- }
475
- if (signal?.aborted && !timedOut) {
476
- throw new SandboxConnectionError("request aborted by caller", {
477
- cause: err
478
- });
479
- }
480
- lastError = err instanceof Error ? err : new Error(String(err));
481
- logSdkTiming("http.request", "error", attemptStart, {
482
- method,
483
- path: timingPath,
484
- attempt,
485
- server_trace_id: traceId,
486
- error: timedOut ? `request timed out after ${this.timeoutMs}ms` : describeError(err)
487
- });
488
- if (attempt >= this.maxRetries) {
489
- const detail = timedOut ? `request timed out after ${this.timeoutMs}ms` : describeError(err);
490
- throw new SandboxConnectionError(detail, { cause: err });
491
- }
492
- }
493
- }
494
- throw new SandboxConnectionError(
495
- lastError ? describeError(lastError) : "request failed",
496
- { cause: lastError }
497
- );
498
- }
499
- };
500
290
  }
501
291
  });
502
292
 
@@ -3005,71 +2795,63 @@ var init_desktop = __esm({
3005
2795
  }
3006
2796
  });
3007
2797
 
3008
- // src/sse.ts
3009
- async function* parseSSEMessages(stream, signal) {
3010
- const reader = stream.getReader();
3011
- const decoder = new TextDecoder();
3012
- let buffer = "";
3013
- try {
3014
- while (true) {
3015
- if (signal?.aborted) break;
3016
- const { done, value } = await reader.read();
3017
- if (done) break;
3018
- buffer += decoder.decode(value, { stream: true });
3019
- const parts = buffer.split(/\r?\n\r?\n/);
3020
- buffer = parts.pop() ?? "";
3021
- for (const part of parts) {
3022
- const lines = part.split(/\r?\n/);
3023
- const dataLines = [];
3024
- let event;
3025
- let id;
3026
- for (const line of lines) {
3027
- if (!line || line.startsWith(":")) continue;
3028
- const separator = line.indexOf(":");
3029
- const field = separator === -1 ? line : line.slice(0, separator);
3030
- let value2 = separator === -1 ? "" : line.slice(separator + 1);
3031
- if (value2.startsWith(" ")) {
3032
- value2 = value2.slice(1);
3033
- }
3034
- if (field === "data") {
3035
- dataLines.push(value2);
3036
- } else if (field === "event") {
3037
- event = value2;
3038
- } else if (field === "id") {
3039
- id = value2;
3040
- }
3041
- }
3042
- if (dataLines.length > 0 || event || id) {
3043
- yield { data: dataLines.join("\n"), event, id };
3044
- }
3045
- }
3046
- }
3047
- } finally {
3048
- reader.releaseLock();
3049
- }
2798
+ // src/sdk-timings.ts
2799
+ function sdkTimingsEnabled() {
2800
+ const value = process.env[SDK_TIMINGS_ENV_VAR];
2801
+ return value === "1" || value === "true" || value === "yes";
3050
2802
  }
3051
- async function* parseSSEStream(stream, signal) {
3052
- for await (const message of parseSSEMessages(stream, signal)) {
3053
- if (!message.data) continue;
3054
- try {
3055
- yield JSON.parse(message.data);
3056
- } catch {
3057
- }
3058
- }
2803
+ function sdkTimingPayloadsEnabled() {
2804
+ const value = process.env[SDK_TIMING_PAYLOADS_ENV_VAR];
2805
+ return value === "1" || value === "true" || value === "yes";
3059
2806
  }
3060
- var init_sse = __esm({
3061
- "src/sse.ts"() {
3062
- "use strict";
3063
- }
3064
- });
3065
-
3066
- // src/url.ts
3067
- function trimTrailingSlashes(url) {
3068
- return url.endsWith("/") ? url.slice(0, -1) : url;
2807
+ function nowMs() {
2808
+ return performance.now();
3069
2809
  }
3070
- function isLocalhost(apiUrl) {
3071
- try {
3072
- const parsed = new URL(apiUrl);
2810
+ function logSdkTiming(op, phase, startedAt, fields = {}) {
2811
+ if (!sdkTimingsEnabled()) return;
2812
+ logSdkTimingEvent(op, phase, {
2813
+ ...fields,
2814
+ duration_ms: Math.round((nowMs() - startedAt) * 100) / 100
2815
+ });
2816
+ }
2817
+ function logSdkTimingEvent(op, phase, fields = {}) {
2818
+ if (!sdkTimingsEnabled()) return;
2819
+ const parts = [
2820
+ "[tensorlake:sdk-timing]",
2821
+ `op=${op}`,
2822
+ `phase=${phase}`
2823
+ ];
2824
+ for (const [key, value] of Object.entries(fields)) {
2825
+ if (value == null) continue;
2826
+ parts.push(`${key}=${formatSdkTimingValue(value)}`);
2827
+ }
2828
+ console.error(parts.join(" "));
2829
+ }
2830
+ function formatSdkTimingValue(value) {
2831
+ if (typeof value === "number" || typeof value === "boolean") {
2832
+ return String(value);
2833
+ }
2834
+ if (/^[A-Za-z0-9_./:@-]+$/.test(value)) {
2835
+ return value;
2836
+ }
2837
+ return JSON.stringify(value);
2838
+ }
2839
+ var SDK_TIMINGS_ENV_VAR, SDK_TIMING_PAYLOADS_ENV_VAR;
2840
+ var init_sdk_timings = __esm({
2841
+ "src/sdk-timings.ts"() {
2842
+ "use strict";
2843
+ SDK_TIMINGS_ENV_VAR = "TENSORLAKE_SDK_TIMINGS";
2844
+ SDK_TIMING_PAYLOADS_ENV_VAR = "TENSORLAKE_SDK_TIMING_PAYLOADS";
2845
+ }
2846
+ });
2847
+
2848
+ // src/url.ts
2849
+ function trimTrailingSlashes(url) {
2850
+ return url.endsWith("/") ? url.slice(0, -1) : url;
2851
+ }
2852
+ function isLocalhost(apiUrl) {
2853
+ try {
2854
+ const parsed = new URL(apiUrl);
3073
2855
  return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
3074
2856
  } catch {
3075
2857
  return false;
@@ -3123,24 +2905,6 @@ function sandboxUrlFromIngressEndpoint(ingressEndpoint, sandboxId, port = 9501)
3123
2905
  const label = port === 9501 ? sandboxId : `${port}-${sandboxId}`;
3124
2906
  return `${parsed.protocol}//${label}.${parsed.host}`;
3125
2907
  }
3126
- function resolveSandboxLifecycleUrl(apiUrl) {
3127
- if (isLocalhost(apiUrl)) return apiUrl;
3128
- try {
3129
- const parsed = new URL(apiUrl);
3130
- if (parsed.hostname.startsWith("api.")) {
3131
- parsed.hostname = "sandbox." + parsed.hostname.slice(4);
3132
- return parsed.toString().replace(/\/$/, "");
3133
- }
3134
- } catch {
3135
- }
3136
- return apiUrl;
3137
- }
3138
- function lifecyclePath(path2, isLocal, namespace) {
3139
- if (isLocal) {
3140
- return `/v1/namespaces/${namespace}/${path2}`;
3141
- }
3142
- return `/${path2}`;
3143
- }
3144
2908
  var init_url = __esm({
3145
2909
  "src/url.ts"() {
3146
2910
  "use strict";
@@ -3152,7 +2916,7 @@ var init_url = __esm({
3152
2916
  import WebSocket2 from "ws";
3153
2917
  function processUserPayload(user) {
3154
2918
  if (user == null) {
3155
- return DEFAULT_PROCESS_USER;
2919
+ return void 0;
3156
2920
  }
3157
2921
  if (typeof user === "string" && user.trim() === "") {
3158
2922
  throw new SandboxError("process user must not be empty");
@@ -3182,32 +2946,24 @@ function sendPtyFrame(socket, frame) {
3182
2946
  socket.send(frame, (error) => error ? reject(error) : resolve());
3183
2947
  });
3184
2948
  }
3185
- function secondsToMillis3(seconds) {
3186
- if (!Number.isFinite(seconds) || seconds <= 0) {
3187
- throw new SandboxError("requestTimeout must be a positive number of seconds");
3188
- }
3189
- return Math.ceil(seconds * 1e3);
3190
- }
3191
- var DEFAULT_PROCESS_USER, SandboxProxyConnection, PTY_OP_DATA, PTY_OP_RESIZE, PTY_OP_READY, PTY_OP_EXIT, Pty, Sandbox;
2949
+ var SandboxProxyConnection, PTY_OP_DATA, PTY_OP_RESIZE, PTY_OP_READY, PTY_OP_EXIT, Pty, Sandbox;
3192
2950
  var init_sandbox = __esm({
3193
2951
  "src/sandbox.ts"() {
3194
2952
  "use strict";
3195
2953
  init_desktop();
3196
2954
  init_defaults();
3197
2955
  init_errors();
3198
- init_http();
2956
+ init_native_sandbox();
3199
2957
  init_models();
3200
2958
  init_tunnel();
3201
- init_sse();
3202
2959
  init_sdk_timings();
3203
2960
  init_url();
3204
- DEFAULT_PROCESS_USER = "tl-user";
3205
2961
  SandboxProxyConnection = class {
3206
2962
  constructor(sandbox, options) {
3207
2963
  this.sandbox = sandbox;
3208
2964
  this.options = options;
3209
2965
  this.routingHint = options.routingHint;
3210
- this.http = this.configureProxy(
2966
+ this.nativeProxy = this.configureProxy(
3211
2967
  options.proxyUrl ?? SANDBOX_PROXY_URL,
3212
2968
  options.sandboxId,
3213
2969
  options.routingHint
@@ -3216,7 +2972,7 @@ var init_sandbox = __esm({
3216
2972
  }
3217
2973
  baseUrl = "";
3218
2974
  wsHeaders = {};
3219
- http;
2975
+ nativeProxy;
3220
2976
  resolveProxyInfo;
3221
2977
  resolvePromise = null;
3222
2978
  routingHint;
@@ -3239,9 +2995,11 @@ var init_sandbox = __esm({
3239
2995
  this.sandbox._setName(info.name ?? null);
3240
2996
  this.routingHint = this.routingHint ?? info.routingHint;
3241
2997
  const proxyUrl = info.ingressEndpoint ?? this.options.proxyUrl ?? SANDBOX_PROXY_URL;
3242
- const nextHttp = this.configureProxy(proxyUrl, info.sandboxId, this.routingHint);
3243
- this.http.close();
3244
- this.http = nextHttp;
2998
+ this.nativeProxy = this.configureProxy(
2999
+ proxyUrl,
3000
+ info.sandboxId,
3001
+ this.routingHint
3002
+ );
3245
3003
  logSdkTiming("sandbox.proxy", "resolve_complete", resolveStart, {
3246
3004
  sandbox_id: info.sandboxId,
3247
3005
  server_trace_id: info.traceId,
@@ -3253,24 +3011,12 @@ var init_sandbox = __esm({
3253
3011
  });
3254
3012
  return this.resolvePromise;
3255
3013
  }
3256
- async requestJson(method, path2, options) {
3257
- await this.ensureResolved();
3258
- return this.http.requestJson(method, path2, options);
3259
- }
3260
- async requestBytes(method, path2, options) {
3261
- await this.ensureResolved();
3262
- return this.http.requestBytes(method, path2, options);
3263
- }
3264
- async requestStream(method, path2, options) {
3265
- await this.ensureResolved();
3266
- return this.http.requestStream(method, path2, options);
3267
- }
3268
- async requestResponse(method, path2, options) {
3014
+ /** Await proxy resolution and return the Rust-backed proxy client. */
3015
+ async client() {
3269
3016
  await this.ensureResolved();
3270
- return this.http.requestResponse(method, path2, options);
3017
+ return this.nativeProxy;
3271
3018
  }
3272
3019
  close() {
3273
- this.http.close();
3274
3020
  }
3275
3021
  configureProxy(proxyUrl, sandboxId, routingHint) {
3276
3022
  const { baseUrl, hostHeader, sandboxIdHeader } = resolveProxyTarget(
@@ -3294,16 +3040,34 @@ var init_sandbox = __esm({
3294
3040
  if (sandboxIdHeader) {
3295
3041
  this.wsHeaders["X-Tensorlake-Sandbox-Id"] = sandboxIdHeader;
3296
3042
  }
3297
- return new HttpClient({
3298
- baseUrl,
3299
- apiKey: this.options.apiKey,
3300
- organizationId: this.options.organizationId,
3301
- projectId: this.options.projectId,
3302
- hostHeader,
3303
- sandboxIdHeader,
3304
- routingHint,
3305
- timeoutMs: this.options.requestTimeout != null ? secondsToMillis3(this.options.requestTimeout) : this.options.timeoutMs ?? null
3306
- });
3043
+ if (this.options.nativeClient) {
3044
+ return this.options.nativeClient.connectProxy(
3045
+ proxyUrl,
3046
+ sandboxId,
3047
+ routingHint ?? null,
3048
+ this.proxyRequestTimeoutSec()
3049
+ );
3050
+ }
3051
+ const binding = loadNativeSandboxBinding();
3052
+ return new binding.NativeSandboxProxyClient(
3053
+ proxyUrl,
3054
+ sandboxId,
3055
+ this.options.apiKey ?? null,
3056
+ this.options.organizationId ?? null,
3057
+ this.options.projectId ?? null,
3058
+ routingHint ?? null,
3059
+ null,
3060
+ this.proxyRequestTimeoutSec()
3061
+ );
3062
+ }
3063
+ proxyRequestTimeoutSec() {
3064
+ if (this.options.requestTimeout != null) {
3065
+ return this.options.requestTimeout;
3066
+ }
3067
+ if (this.options.timeoutMs != null) {
3068
+ return this.options.timeoutMs / 1e3;
3069
+ }
3070
+ return null;
3307
3071
  }
3308
3072
  };
3309
3073
  PTY_OP_DATA = 0;
@@ -3472,7 +3236,6 @@ var init_sandbox = __esm({
3472
3236
  sandboxId;
3473
3237
  traceId = null;
3474
3238
  proxy;
3475
- http;
3476
3239
  ownsSandbox = false;
3477
3240
  lifecycleClient = null;
3478
3241
  lifecycleIdentifier;
@@ -3481,7 +3244,6 @@ var init_sandbox = __esm({
3481
3244
  this.sandboxId = options.sandboxId;
3482
3245
  this.lifecycleIdentifier = options.sandboxId;
3483
3246
  this.proxy = new SandboxProxyConnection(this, options);
3484
- this.http = this.proxy;
3485
3247
  }
3486
3248
  get baseUrl() {
3487
3249
  return this.proxy.baseUrl;
@@ -3686,9 +3448,9 @@ var init_sandbox = __esm({
3686
3448
  const filtered = all.filter((s) => s.sandboxId === this.lifecycleIdentifier);
3687
3449
  return Object.assign(filtered, { traceId: all.traceId });
3688
3450
  }
3689
- /** Close the HTTP client. The sandbox keeps running. */
3451
+ /** Close the proxy connection. The sandbox keeps running. */
3690
3452
  close() {
3691
- this.http.close();
3453
+ this.proxy.close();
3692
3454
  }
3693
3455
  /** Terminate the sandbox and release all resources. */
3694
3456
  async terminate() {
@@ -3715,51 +3477,18 @@ var init_sandbox = __esm({
3715
3477
  if (options?.workingDir) body.working_dir = options.workingDir;
3716
3478
  if (options?.timeout != null) body.timeout = options.timeout;
3717
3479
  const user = processUserPayload(options?.user);
3718
- body.user = user;
3480
+ if (user !== void 0) body.user = user;
3719
3481
  logSdkTimingEvent("sandbox.run", "start", {
3720
3482
  sandbox_id: this.sandboxId,
3721
3483
  command: sdkTimingPayloadsEnabled() ? command : void 0,
3722
3484
  command_length: command.length
3723
3485
  });
3724
- const requestStart = nowMs();
3725
- const sseStream = await this.http.requestStream(
3726
- "POST",
3727
- "/api/v1/processes/run",
3728
- { json: body }
3486
+ const proxy = await this.proxy.client();
3487
+ const { traceId, events } = await callNative(
3488
+ () => proxy.runProcess(JSON.stringify(body)),
3489
+ { sandboxId: this.sandboxId }
3729
3490
  );
3730
- const traceId = sseStream.traceId;
3731
- logSdkTiming("sandbox.run", "stream_headers", requestStart, {
3732
- sandbox_id: this.sandboxId,
3733
- server_trace_id: traceId,
3734
- command: sdkTimingPayloadsEnabled() ? command : void 0,
3735
- command_length: command.length
3736
- });
3737
- const stdoutLines = [];
3738
- const stderrLines = [];
3739
- let exitCode = -1;
3740
- const streamStart = nowMs();
3741
- for await (const raw of parseSSEStream(sseStream)) {
3742
- if (typeof raw.line === "string") {
3743
- if (raw.stream === "stderr") {
3744
- stderrLines.push(raw.line);
3745
- } else {
3746
- stdoutLines.push(raw.line);
3747
- }
3748
- } else if ("exit_code" in raw || "signal" in raw) {
3749
- if (typeof raw.exit_code === "number") {
3750
- exitCode = raw.exit_code;
3751
- } else if (typeof raw.signal === "number") {
3752
- exitCode = -raw.signal;
3753
- }
3754
- }
3755
- }
3756
- logSdkTiming("sandbox.run", "stream_complete", streamStart, {
3757
- sandbox_id: this.sandboxId,
3758
- server_trace_id: traceId,
3759
- command: sdkTimingPayloadsEnabled() ? command : void 0,
3760
- command_length: command.length,
3761
- exit_code: exitCode
3762
- });
3491
+ const { exitCode, stdout, stderr } = assembleCommandResult(events);
3763
3492
  logSdkTiming("sandbox.run", "complete", opStart, {
3764
3493
  sandbox_id: this.sandboxId,
3765
3494
  server_trace_id: traceId,
@@ -3767,10 +3496,7 @@ var init_sandbox = __esm({
3767
3496
  command_length: command.length,
3768
3497
  exit_code: exitCode
3769
3498
  });
3770
- return Object.assign(
3771
- { exitCode, stdout: stdoutLines.join("\n"), stderr: stderrLines.join("\n") },
3772
- { traceId }
3773
- );
3499
+ return Object.assign({ exitCode, stdout, stderr }, { traceId });
3774
3500
  }
3775
3501
  // --- Process management ---
3776
3502
  /**
@@ -3787,7 +3513,7 @@ var init_sandbox = __esm({
3787
3513
  if (options?.env != null) payload.env = options.env;
3788
3514
  if (options?.workingDir != null) payload.working_dir = options.workingDir;
3789
3515
  const user = processUserPayload(options?.user);
3790
- payload.user = user;
3516
+ if (user !== void 0) payload.user = user;
3791
3517
  if (options?.stdinMode != null && options.stdinMode !== "closed" /* CLOSED */) {
3792
3518
  payload.stdin_mode = options.stdinMode;
3793
3519
  }
@@ -3802,97 +3528,101 @@ var init_sandbox = __esm({
3802
3528
  if (options?.healthCheck != null) {
3803
3529
  payload.health_check = toSnakeKeys(options.healthCheck);
3804
3530
  }
3805
- const raw = await this.http.requestJson(
3806
- "POST",
3807
- "/api/v1/processes",
3808
- { body: payload }
3531
+ const proxy = await this.proxy.client();
3532
+ const { traceId, json } = await callNative(
3533
+ () => proxy.startProcess(JSON.stringify(payload)),
3534
+ { sandboxId: this.sandboxId }
3809
3535
  );
3810
- return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3536
+ return Object.assign(fromSnakeKeys(JSON.parse(json)), { traceId });
3811
3537
  }
3812
3538
  /** List all processes (running and exited) tracked by the sandbox daemon. */
3813
3539
  async listProcesses() {
3814
- const raw = await this.http.requestJson(
3815
- "GET",
3816
- "/api/v1/processes"
3540
+ const proxy = await this.proxy.client();
3541
+ const { traceId, json } = await callNative(() => proxy.listProcesses(), {
3542
+ sandboxId: this.sandboxId
3543
+ });
3544
+ const parsed = JSON.parse(json);
3545
+ const processes = (parsed.processes ?? []).map(
3546
+ (p) => fromSnakeKeys(p)
3817
3547
  );
3818
- const processes = (raw.processes ?? []).map((p) => fromSnakeKeys(p));
3819
- return Object.assign(processes, { traceId: raw.traceId });
3548
+ return Object.assign(processes, { traceId });
3820
3549
  }
3821
3550
  /** Get current status and metadata for a process by PID. */
3822
3551
  async getProcess(pid) {
3823
- const raw = await this.http.requestJson(
3824
- "GET",
3825
- `/api/v1/processes/${pid}`
3826
- );
3827
- return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3552
+ const proxy = await this.proxy.client();
3553
+ const { traceId, json } = await callNative(() => proxy.getProcess(pid), {
3554
+ sandboxId: this.sandboxId
3555
+ });
3556
+ return Object.assign(fromSnakeKeys(JSON.parse(json)), { traceId });
3828
3557
  }
3829
3558
  /** Send SIGKILL to a process. */
3830
3559
  async killProcess(pid) {
3831
- await this.http.requestJson("DELETE", `/api/v1/processes/${pid}`);
3560
+ const proxy = await this.proxy.client();
3561
+ await callNative(() => proxy.killProcess(pid), { sandboxId: this.sandboxId });
3832
3562
  }
3833
3563
  /** Restart a managed process by PID. */
3834
3564
  async restartProcess(pid) {
3835
- const raw = await this.http.requestJson(
3836
- "POST",
3837
- `/api/v1/processes/${pid}/restart`
3838
- );
3839
- return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3565
+ const proxy = await this.proxy.client();
3566
+ const { traceId, json } = await callNative(() => proxy.restartProcess(pid), {
3567
+ sandboxId: this.sandboxId
3568
+ });
3569
+ return Object.assign(fromSnakeKeys(JSON.parse(json)), { traceId });
3840
3570
  }
3841
3571
  /** Send an arbitrary signal to a process (e.g. `15` for SIGTERM, `9` for SIGKILL). */
3842
3572
  async sendSignal(pid, signal) {
3843
- const raw = await this.http.requestJson(
3844
- "POST",
3845
- `/api/v1/processes/${pid}/signal`,
3846
- { body: { signal } }
3573
+ const proxy = await this.proxy.client();
3574
+ const { traceId, json } = await callNative(
3575
+ () => proxy.sendSignal(pid, signal),
3576
+ { sandboxId: this.sandboxId }
3847
3577
  );
3848
- return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3578
+ return Object.assign(fromSnakeKeys(JSON.parse(json)), {
3579
+ traceId
3580
+ });
3849
3581
  }
3850
3582
  // --- Process I/O ---
3851
3583
  /** Write bytes to a process's stdin. The process must have been started with `stdinMode: StdinMode.PIPE`. */
3852
3584
  async writeStdin(pid, data) {
3853
- await this.http.requestBytes("POST", `/api/v1/processes/${pid}/stdin`, {
3854
- body: data,
3855
- contentType: "application/octet-stream"
3585
+ const proxy = await this.proxy.client();
3586
+ await callNative(() => proxy.writeStdin(pid, Buffer.from(data)), {
3587
+ sandboxId: this.sandboxId
3856
3588
  });
3857
3589
  }
3858
3590
  /** Close a process's stdin pipe, signalling EOF to the process. */
3859
3591
  async closeStdin(pid) {
3860
- await this.http.requestJson("POST", `/api/v1/processes/${pid}/stdin/close`);
3592
+ const proxy = await this.proxy.client();
3593
+ await callNative(() => proxy.closeStdin(pid), { sandboxId: this.sandboxId });
3861
3594
  }
3862
3595
  /** Return all captured stdout lines produced so far by a process. */
3863
3596
  async getStdout(pid) {
3864
- const raw = await this.http.requestJson(
3865
- "GET",
3866
- `/api/v1/processes/${pid}/stdout`
3867
- );
3868
- return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3597
+ const proxy = await this.proxy.client();
3598
+ const { traceId, json } = await callNative(() => proxy.getStdout(pid), {
3599
+ sandboxId: this.sandboxId
3600
+ });
3601
+ return Object.assign(fromSnakeKeys(JSON.parse(json)), { traceId });
3869
3602
  }
3870
3603
  /** Return all captured stderr lines produced so far by a process. */
3871
3604
  async getStderr(pid) {
3872
- const raw = await this.http.requestJson(
3873
- "GET",
3874
- `/api/v1/processes/${pid}/stderr`
3875
- );
3876
- return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3605
+ const proxy = await this.proxy.client();
3606
+ const { traceId, json } = await callNative(() => proxy.getStderr(pid), {
3607
+ sandboxId: this.sandboxId
3608
+ });
3609
+ return Object.assign(fromSnakeKeys(JSON.parse(json)), { traceId });
3877
3610
  }
3878
3611
  /** Return all captured stdout+stderr lines produced so far by a process. */
3879
3612
  async getOutput(pid) {
3880
- const raw = await this.http.requestJson(
3881
- "GET",
3882
- `/api/v1/processes/${pid}/output`
3883
- );
3884
- return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3613
+ const proxy = await this.proxy.client();
3614
+ const { traceId, json } = await callNative(() => proxy.getOutput(pid), {
3615
+ sandboxId: this.sandboxId
3616
+ });
3617
+ return Object.assign(fromSnakeKeys(JSON.parse(json)), { traceId });
3885
3618
  }
3886
3619
  // --- Streaming (SSE) ---
3887
3620
  /** Stream stdout events from a process until it exits. Yields one `OutputEvent` per line. */
3888
3621
  async *followStdout(pid, options) {
3889
- const stream = await this.http.requestStream(
3890
- "GET",
3891
- `/api/v1/processes/${pid}/stdout/follow`,
3892
- options
3893
- );
3894
- for await (const raw of parseSSEStream(
3895
- stream,
3622
+ const proxy = await this.proxy.client();
3623
+ for await (const raw of nativeEventStream(
3624
+ (emit) => proxy.followStdout(pid, emit),
3625
+ { sandboxId: this.sandboxId },
3896
3626
  options?.signal
3897
3627
  )) {
3898
3628
  yield fromSnakeKeys(raw);
@@ -3900,13 +3630,10 @@ var init_sandbox = __esm({
3900
3630
  }
3901
3631
  /** Stream stderr events from a process until it exits. Yields one `OutputEvent` per line. */
3902
3632
  async *followStderr(pid, options) {
3903
- const stream = await this.http.requestStream(
3904
- "GET",
3905
- `/api/v1/processes/${pid}/stderr/follow`,
3906
- options
3907
- );
3908
- for await (const raw of parseSSEStream(
3909
- stream,
3633
+ const proxy = await this.proxy.client();
3634
+ for await (const raw of nativeEventStream(
3635
+ (emit) => proxy.followStderr(pid, emit),
3636
+ { sandboxId: this.sandboxId },
3910
3637
  options?.signal
3911
3638
  )) {
3912
3639
  yield fromSnakeKeys(raw);
@@ -3914,13 +3641,10 @@ var init_sandbox = __esm({
3914
3641
  }
3915
3642
  /** Stream combined stdout+stderr events from a process until it exits. Yields one `OutputEvent` per line. */
3916
3643
  async *followOutput(pid, options) {
3917
- const stream = await this.http.requestStream(
3918
- "GET",
3919
- `/api/v1/processes/${pid}/output/follow`,
3920
- options
3921
- );
3922
- for await (const raw of parseSSEStream(
3923
- stream,
3644
+ const proxy = await this.proxy.client();
3645
+ for await (const raw of nativeEventStream(
3646
+ (emit) => proxy.followOutput(pid, emit),
3647
+ { sandboxId: this.sandboxId },
3924
3648
  options?.signal
3925
3649
  )) {
3926
3650
  yield fromSnakeKeys(raw);
@@ -3929,33 +3653,33 @@ var init_sandbox = __esm({
3929
3653
  // --- File operations ---
3930
3654
  /** Read a file from the sandbox and return its raw bytes. */
3931
3655
  async readFile(path2) {
3932
- return this.http.requestBytes(
3933
- "GET",
3934
- `/api/v1/files?path=${encodeURIComponent(path2)}`
3935
- );
3656
+ const proxy = await this.proxy.client();
3657
+ const { traceId, data } = await callNative(() => proxy.readFile(path2), {
3658
+ sandboxId: this.sandboxId
3659
+ });
3660
+ return Object.assign(Uint8Array.from(data), { traceId });
3936
3661
  }
3937
3662
  /** Write raw bytes to a file in the sandbox, creating it if it does not exist. */
3938
3663
  async writeFile(path2, content) {
3939
- await this.http.requestBytes(
3940
- "PUT",
3941
- `/api/v1/files?path=${encodeURIComponent(path2)}`,
3942
- { body: content, contentType: "application/octet-stream" }
3943
- );
3664
+ const proxy = await this.proxy.client();
3665
+ await callNative(() => proxy.writeFile(path2, Buffer.from(content)), {
3666
+ sandboxId: this.sandboxId
3667
+ });
3944
3668
  }
3945
3669
  /** Delete a file from the sandbox. */
3946
3670
  async deleteFile(path2) {
3947
- await this.http.requestJson(
3948
- "DELETE",
3949
- `/api/v1/files?path=${encodeURIComponent(path2)}`
3950
- );
3671
+ const proxy = await this.proxy.client();
3672
+ await callNative(() => proxy.deleteFile(path2), { sandboxId: this.sandboxId });
3951
3673
  }
3952
3674
  /** List the contents of a directory in the sandbox. */
3953
3675
  async listDirectory(path2) {
3954
- const raw = await this.http.requestJson(
3955
- "GET",
3956
- `/api/v1/files/list?path=${encodeURIComponent(path2)}`
3957
- );
3958
- return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3676
+ const proxy = await this.proxy.client();
3677
+ const { traceId, json } = await callNative(() => proxy.listDirectory(path2), {
3678
+ sandboxId: this.sandboxId
3679
+ });
3680
+ return Object.assign(fromSnakeKeys(JSON.parse(json)), {
3681
+ traceId
3682
+ });
3959
3683
  }
3960
3684
  // --- PTY ---
3961
3685
  /** Create an interactive PTY session. Returns a `sessionId` and `token` for WebSocket connection via `connectPty()`. */
@@ -3968,12 +3692,12 @@ var init_sandbox = __esm({
3968
3692
  if (options.args != null) payload.args = options.args;
3969
3693
  if (options.env != null) payload.env = options.env;
3970
3694
  if (options.workingDir != null) payload.working_dir = options.workingDir;
3971
- const raw = await this.http.requestJson(
3972
- "POST",
3973
- "/api/v1/pty",
3974
- { body: payload }
3695
+ const proxy = await this.proxy.client();
3696
+ const { traceId, json } = await callNative(
3697
+ () => proxy.createPtySession(JSON.stringify(payload)),
3698
+ { sandboxId: this.sandboxId }
3975
3699
  );
3976
- return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3700
+ return Object.assign(fromSnakeKeys(JSON.parse(json)), { traceId });
3977
3701
  }
3978
3702
  /** Create a PTY session and connect to it immediately. Cleans up the session if the WebSocket connection fails. */
3979
3703
  async createPty(options) {
@@ -3983,7 +3707,8 @@ var init_sandbox = __esm({
3983
3707
  return await this.connectPty(session.sessionId, session.token, { onData, onExit });
3984
3708
  } catch (error) {
3985
3709
  try {
3986
- await this.http.requestResponse("DELETE", `/api/v1/pty/${session.sessionId}`);
3710
+ const proxy = await this.proxy.client();
3711
+ await proxy.deletePtySession(session.sessionId);
3987
3712
  } catch {
3988
3713
  }
3989
3714
  throw error;
@@ -4003,7 +3728,8 @@ var init_sandbox = __esm({
4003
3728
  "X-PTY-Token": authToken
4004
3729
  },
4005
3730
  killSession: async () => {
4006
- await this.http.requestResponse("DELETE", `/api/v1/pty/${sessionId}`);
3731
+ const proxy = await this.proxy.client();
3732
+ await proxy.deletePtySession(sessionId);
4007
3733
  }
4008
3734
  });
4009
3735
  if (options?.onData) {
@@ -4091,19 +3817,19 @@ var init_sandbox = __esm({
4091
3817
  // --- Health ---
4092
3818
  /** Check the sandbox daemon health. */
4093
3819
  async health() {
4094
- const raw = await this.http.requestJson(
4095
- "GET",
4096
- "/api/v1/health"
4097
- );
4098
- return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3820
+ const proxy = await this.proxy.client();
3821
+ const { traceId, json } = await callNative(() => proxy.health(), {
3822
+ sandboxId: this.sandboxId
3823
+ });
3824
+ return Object.assign(fromSnakeKeys(JSON.parse(json)), { traceId });
4099
3825
  }
4100
3826
  /** Get sandbox daemon info (version, uptime, process counts). */
4101
3827
  async daemonInfo() {
4102
- const raw = await this.http.requestJson(
4103
- "GET",
4104
- "/api/v1/info"
4105
- );
4106
- return Object.assign(fromSnakeKeys(raw), { traceId: raw.traceId });
3828
+ const proxy = await this.proxy.client();
3829
+ const { traceId, json } = await callNative(() => proxy.info(), {
3830
+ sandboxId: this.sandboxId
3831
+ });
3832
+ return Object.assign(fromSnakeKeys(JSON.parse(json)), { traceId });
4107
3833
  }
4108
3834
  };
4109
3835
  }
@@ -4160,7 +3886,7 @@ async function readCloudInitBase64(cloudInit) {
4160
3886
  }
4161
3887
  function resolveRequestTimeoutMs(options) {
4162
3888
  if (options?.requestTimeout != null) {
4163
- return secondsToMillis4(options.requestTimeout);
3889
+ return secondsToMillis3(options.requestTimeout);
4164
3890
  }
4165
3891
  if (options?.timeoutMs != null) {
4166
3892
  validateTimeoutMs(options.timeoutMs);
@@ -4168,7 +3894,7 @@ function resolveRequestTimeoutMs(options) {
4168
3894
  }
4169
3895
  return DEFAULT_HTTP_TIMEOUT_MS;
4170
3896
  }
4171
- function secondsToMillis4(seconds) {
3897
+ function secondsToMillis3(seconds) {
4172
3898
  if (!Number.isFinite(seconds) || seconds <= 0) {
4173
3899
  throw new SandboxError("requestTimeout must be a positive number of seconds");
4174
3900
  }
@@ -4179,7 +3905,7 @@ function validateTimeoutMs(timeoutMs) {
4179
3905
  throw new SandboxError("timeoutMs must be a positive number of milliseconds");
4180
3906
  }
4181
3907
  }
4182
- function sleep2(ms) {
3908
+ function sleep(ms) {
4183
3909
  return new Promise((resolve) => setTimeout(resolve, ms));
4184
3910
  }
4185
3911
  function snapshotStatusSatisfiesWaitCondition(status, waitUntil) {
@@ -4235,31 +3961,25 @@ function validateUserPort(port) {
4235
3961
  function dedupeAndSortPorts(ports) {
4236
3962
  return [...new Set(ports)].sort((a, b) => a - b);
4237
3963
  }
4238
- var CREATE_SANDBOX_RETRYABLE_STATUS_CODES, CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES, COPY_SANDBOX_ALLOWED_ERROR_STATUS_CODES, MAX_CLOUD_INIT_USER_DATA_BYTES, SandboxClient, RESERVED_SANDBOX_MANAGEMENT_PORT;
3964
+ var MAX_CLOUD_INIT_USER_DATA_BYTES, SandboxClient, RESERVED_SANDBOX_MANAGEMENT_PORT;
4239
3965
  var init_client = __esm({
4240
3966
  "src/client.ts"() {
4241
3967
  "use strict";
4242
3968
  init_defaults();
4243
3969
  init_errors();
4244
- init_http();
3970
+ init_native_sandbox();
4245
3971
  init_models();
4246
3972
  init_sandbox();
4247
3973
  init_sdk_timings();
4248
3974
  init_url();
4249
- CREATE_SANDBOX_RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([502, 503]);
4250
- CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES = /* @__PURE__ */ new Set([504]);
4251
- COPY_SANDBOX_ALLOWED_ERROR_STATUS_CODES = /* @__PURE__ */ new Set([422, 504]);
4252
3975
  MAX_CLOUD_INIT_USER_DATA_BYTES = 16 * 1024;
4253
3976
  SandboxClient = class _SandboxClient {
4254
- http;
3977
+ native;
4255
3978
  apiUrl;
4256
3979
  apiKey;
4257
3980
  organizationId;
4258
3981
  projectId;
4259
3982
  namespace;
4260
- local;
4261
- maxRetries;
4262
- retryBackoffMs;
4263
3983
  requestTimeoutMs;
4264
3984
  /** @internal Pass `true` to suppress the deprecation warning when used by `Sandbox.create()` / `Sandbox.connect()`. */
4265
3985
  constructor(options, _internal = false) {
@@ -4273,19 +3993,17 @@ var init_client = __esm({
4273
3993
  this.organizationId = options?.organizationId;
4274
3994
  this.projectId = options?.projectId;
4275
3995
  this.namespace = options?.namespace ?? NAMESPACE;
4276
- this.local = isLocalhost(this.apiUrl);
4277
- this.maxRetries = options?.maxRetries ?? MAX_RETRIES;
4278
- this.retryBackoffMs = options?.retryBackoffMs ?? RETRY_BACKOFF_MS;
4279
3996
  this.requestTimeoutMs = resolveRequestTimeoutMs(options);
4280
- this.http = new HttpClient({
4281
- baseUrl: resolveSandboxLifecycleUrl(this.apiUrl),
4282
- apiKey: this.apiKey,
4283
- organizationId: this.organizationId,
4284
- projectId: this.projectId,
4285
- maxRetries: this.maxRetries,
4286
- retryBackoffMs: this.retryBackoffMs,
4287
- timeoutMs: this.requestTimeoutMs
4288
- });
3997
+ const binding = loadNativeSandboxBinding();
3998
+ this.native = new binding.NativeSandboxClient(
3999
+ this.apiUrl,
4000
+ this.apiKey ?? null,
4001
+ this.organizationId ?? null,
4002
+ this.projectId ?? null,
4003
+ this.namespace,
4004
+ null,
4005
+ this.requestTimeoutMs / 1e3
4006
+ );
4289
4007
  }
4290
4008
  /** Create a client for the TensorLake cloud platform. */
4291
4009
  static forCloud(options) {
@@ -4308,13 +4026,12 @@ var init_client = __esm({
4308
4026
  });
4309
4027
  }
4310
4028
  close() {
4311
- this.http.close();
4312
4029
  }
4313
4030
  withRequestTimeout(requestTimeout) {
4314
4031
  if (requestTimeout == null) {
4315
4032
  return this;
4316
4033
  }
4317
- const timeoutMs = secondsToMillis4(requestTimeout);
4034
+ const timeoutMs = secondsToMillis3(requestTimeout);
4318
4035
  if (timeoutMs === this.requestTimeoutMs) {
4319
4036
  return this;
4320
4037
  }
@@ -4325,17 +4042,24 @@ var init_client = __esm({
4325
4042
  organizationId: this.organizationId,
4326
4043
  projectId: this.projectId,
4327
4044
  namespace: this.namespace,
4328
- maxRetries: this.maxRetries,
4329
- retryBackoffMs: this.retryBackoffMs,
4330
4045
  timeoutMs
4331
4046
  },
4332
4047
  /* _internal */
4333
4048
  true
4334
4049
  );
4335
4050
  }
4336
- // --- Path helper ---
4337
- path(subpath) {
4338
- return lifecyclePath(subpath, this.local, this.namespace);
4051
+ // --- Native marshalling helpers ---
4052
+ /** Run a native JSON call and reshape it into a `Traced<T>`. */
4053
+ async tracedJson(fn, idField, context) {
4054
+ const { traceId, json } = await callNative(fn, context);
4055
+ return Object.assign(fromSnakeKeys(JSON.parse(json), idField), {
4056
+ traceId
4057
+ });
4058
+ }
4059
+ /** Run a native JSON call and reshape it into a plain `T` (no trace id). */
4060
+ async plainJson(fn, idField, context) {
4061
+ const { json } = await callNative(fn, context);
4062
+ return fromSnakeKeys(JSON.parse(json), idField);
4339
4063
  }
4340
4064
  // --- Sandbox CRUD ---
4341
4065
  /** Create a new sandbox. Returns immediately; the sandbox may still be starting. Use `createAndConnect()` for a blocking, ready-to-use handle. */
@@ -4361,36 +4085,27 @@ var init_client = __esm({
4361
4085
  deny_out: options?.denyOut ?? []
4362
4086
  };
4363
4087
  }
4364
- const raw = await this.http.requestJson(
4365
- "POST",
4366
- this.path("sandboxes"),
4367
- {
4368
- body,
4369
- retryableStatusCodes: CREATE_SANDBOX_RETRYABLE_STATUS_CODES,
4370
- allowedErrorStatusCodes: CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES
4371
- }
4088
+ return this.tracedJson(
4089
+ () => this.native.createSandbox(JSON.stringify(body)),
4090
+ "sandboxId"
4372
4091
  );
4373
- const result = fromSnakeKeys(raw, "sandboxId");
4374
- return Object.assign(result, { traceId: raw.traceId });
4375
4092
  }
4376
4093
  /** Get current state and metadata for a sandbox by ID. */
4377
4094
  async get(sandboxId) {
4378
- const raw = await this.http.requestJson(
4379
- "GET",
4380
- this.path(`sandboxes/${sandboxId}`)
4095
+ return this.tracedJson(
4096
+ () => this.native.getSandbox(sandboxId),
4097
+ "sandboxId",
4098
+ { sandboxId, notFoundKind: "sandbox" }
4381
4099
  );
4382
- return Object.assign(fromSnakeKeys(raw, "sandboxId"), { traceId: raw.traceId });
4383
4100
  }
4384
4101
  /** List all sandboxes in the namespace. */
4385
4102
  async list() {
4386
- const raw = await this.http.requestJson(
4387
- "GET",
4388
- this.path("sandboxes")
4389
- );
4390
- const sandboxes = (raw.sandboxes ?? []).map(
4103
+ const { traceId, json } = await callNative(() => this.native.listSandboxes());
4104
+ const parsed = JSON.parse(json);
4105
+ const sandboxes = (parsed.sandboxes ?? []).map(
4391
4106
  (s) => fromSnakeKeys(s, "sandboxId")
4392
4107
  );
4393
- return Object.assign(sandboxes, { traceId: raw.traceId });
4108
+ return Object.assign(sandboxes, { traceId });
4394
4109
  }
4395
4110
  /**
4396
4111
  * List archived (terminated) sandboxes in the namespace.
@@ -4399,37 +4114,30 @@ var init_client = __esm({
4399
4114
  * archived sandboxes store until the server-configured TTL expires.
4400
4115
  */
4401
4116
  async listArchived(options) {
4402
- const query = [];
4403
- if (options?.limit != null) {
4404
- query.push(`limit=${encodeURIComponent(String(options.limit))}`);
4405
- }
4406
- if (options?.cursor != null) {
4407
- query.push(`cursor=${encodeURIComponent(options.cursor)}`);
4408
- }
4409
- if (options?.direction != null) {
4410
- query.push(`direction=${encodeURIComponent(options.direction)}`);
4411
- }
4412
- const suffix = query.length ? `?${query.join("&")}` : "";
4413
- const raw = await this.http.requestJson("GET", this.path(`archived-sandboxes${suffix}`));
4414
- const sandboxes = (raw.sandboxes ?? []).map(
4117
+ const { traceId, json } = await callNative(
4118
+ () => this.native.listArchivedSandboxes(
4119
+ options?.limit ?? null,
4120
+ options?.cursor ?? null,
4121
+ options?.direction ?? null
4122
+ )
4123
+ );
4124
+ const parsed = JSON.parse(json);
4125
+ const sandboxes = (parsed.sandboxes ?? []).map(
4415
4126
  (s) => fromSnakeKeys(s, "sandboxId")
4416
4127
  );
4417
4128
  const response = {
4418
4129
  sandboxes,
4419
- prevCursor: raw.prev_cursor,
4420
- nextCursor: raw.next_cursor
4130
+ prevCursor: parsed.prev_cursor,
4131
+ nextCursor: parsed.next_cursor
4421
4132
  };
4422
- return Object.assign(response, { traceId: raw.traceId });
4133
+ return Object.assign(response, { traceId });
4423
4134
  }
4424
4135
  /** Get a single archived sandbox by id. */
4425
4136
  async getArchived(sandboxId) {
4426
- const raw = await this.http.requestJson(
4427
- "GET",
4428
- this.path(`archived-sandboxes/${encodeURIComponent(sandboxId)}`)
4429
- );
4430
- return Object.assign(
4431
- fromSnakeKeys(raw, "sandboxId"),
4432
- { traceId: raw.traceId }
4137
+ return this.tracedJson(
4138
+ () => this.native.getArchivedSandbox(sandboxId),
4139
+ "sandboxId",
4140
+ { sandboxId, notFoundKind: "sandbox" }
4433
4141
  );
4434
4142
  }
4435
4143
  /** Update sandbox properties such as name, exposed ports, and proxy auth settings. */
@@ -4445,12 +4153,11 @@ var init_client = __esm({
4445
4153
  if (Object.keys(body).length === 0) {
4446
4154
  throw new SandboxError("At least one sandbox update field must be provided.");
4447
4155
  }
4448
- const raw = await this.http.requestJson(
4449
- "PATCH",
4450
- this.path(`sandboxes/${sandboxId}`),
4451
- { body }
4156
+ return this.tracedJson(
4157
+ () => this.native.updateSandbox(sandboxId, JSON.stringify(body)),
4158
+ "sandboxId",
4159
+ { sandboxId, notFoundKind: "sandbox" }
4452
4160
  );
4453
- return Object.assign(fromSnakeKeys(raw, "sandboxId"), { traceId: raw.traceId });
4454
4161
  }
4455
4162
  /** Get the current proxy port settings for a sandbox. */
4456
4163
  async getPortAccess(sandboxId) {
@@ -4488,10 +4195,7 @@ var init_client = __esm({
4488
4195
  }
4489
4196
  /** Terminate and delete a sandbox. */
4490
4197
  async delete(sandboxId) {
4491
- await this.http.requestJson(
4492
- "DELETE",
4493
- this.path(`sandboxes/${sandboxId}`)
4494
- );
4198
+ await callNative(() => this.native.deleteSandbox(sandboxId), { sandboxId, notFoundKind: "sandbox" });
4495
4199
  }
4496
4200
  /**
4497
4201
  * Suspend a named sandbox, preserving its state for later resume.
@@ -4500,18 +4204,9 @@ var init_client = __esm({
4500
4204
  * cannot. By default blocks until the sandbox is fully `Suspended`. Pass
4501
4205
  * `{ wait: false }` to return immediately after the request is sent
4502
4206
  * (fire-and-return); the server processes the suspend asynchronously.
4503
- *
4504
- * @param sandboxId - ID or name of the sandbox.
4505
- * @param options.wait - If `true` (default), poll until `Suspended`. Pass `false` to fire-and-return.
4506
- * @param options.timeout - Max seconds to wait when `wait=true` (default 300).
4507
- * @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
4508
- * @throws {SandboxError} If `wait=true` and the sandbox does not reach `Suspended` within `timeout`.
4509
4207
  */
4510
4208
  async suspend(sandboxId, options) {
4511
- await this.http.requestResponse(
4512
- "POST",
4513
- this.path(`sandboxes/${sandboxId}/suspend`)
4514
- );
4209
+ await callNative(() => this.native.suspendSandbox(sandboxId), { sandboxId, notFoundKind: "sandbox" });
4515
4210
  if (options?.wait === false) return;
4516
4211
  const timeout = options?.timeout ?? 300;
4517
4212
  const pollInterval = options?.pollInterval ?? 1;
@@ -4522,7 +4217,7 @@ var init_client = __esm({
4522
4217
  if (info.status === "terminated" /* TERMINATED */) {
4523
4218
  throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for suspend`);
4524
4219
  }
4525
- await sleep2(pollInterval * 1e3);
4220
+ await sleep(pollInterval * 1e3);
4526
4221
  }
4527
4222
  throw new SandboxError(`Sandbox ${sandboxId} did not suspend within ${timeout}s`);
4528
4223
  }
@@ -4532,18 +4227,9 @@ var init_client = __esm({
4532
4227
  * By default blocks until the sandbox is `Running` and routable. Pass
4533
4228
  * `{ wait: false }` to return immediately after the request is sent
4534
4229
  * (fire-and-return); the server processes the resume asynchronously.
4535
- *
4536
- * @param sandboxId - ID or name of the sandbox.
4537
- * @param options.wait - If `true` (default), poll until `Running`. Pass `false` to fire-and-return.
4538
- * @param options.timeout - Max seconds to wait when `wait=true` (default 300).
4539
- * @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
4540
- * @throws {SandboxError} If `wait=true` and the sandbox does not reach `Running` within `timeout`.
4541
4230
  */
4542
4231
  async resume(sandboxId, options) {
4543
- await this.http.requestResponse(
4544
- "POST",
4545
- this.path(`sandboxes/${sandboxId}/resume`)
4546
- );
4232
+ await callNative(() => this.native.resumeSandbox(sandboxId), { sandboxId, notFoundKind: "sandbox" });
4547
4233
  if (options?.wait === false) return;
4548
4234
  const timeout = options?.timeout ?? 300;
4549
4235
  const pollInterval = options?.pollInterval ?? 1;
@@ -4554,22 +4240,17 @@ var init_client = __esm({
4554
4240
  if (info.status === "terminated" /* TERMINATED */) {
4555
4241
  throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for resume`);
4556
4242
  }
4557
- await sleep2(pollInterval * 1e3);
4243
+ await sleep(pollInterval * 1e3);
4558
4244
  }
4559
4245
  throw new SandboxError(`Sandbox ${sandboxId} did not resume within ${timeout}s`);
4560
4246
  }
4561
4247
  /** Claim a warm sandbox from a pool, creating one if no warm containers are available. */
4562
4248
  async claim(poolId) {
4563
- const raw = await this.http.requestJson(
4564
- "POST",
4565
- this.path(`sandbox-pools/${poolId}/sandboxes`),
4566
- {
4567
- retryableStatusCodes: CREATE_SANDBOX_RETRYABLE_STATUS_CODES,
4568
- allowedErrorStatusCodes: CREATE_SANDBOX_ALLOWED_ERROR_STATUS_CODES
4569
- }
4249
+ return this.tracedJson(
4250
+ () => this.native.claimSandbox(poolId),
4251
+ "sandboxId",
4252
+ { poolId, notFoundKind: "pool" }
4570
4253
  );
4571
- const result = fromSnakeKeys(raw, "sandboxId");
4572
- return Object.assign(result, { traceId: raw.traceId });
4573
4254
  }
4574
4255
  /**
4575
4256
  * Live-copy a running sandbox.
@@ -4584,13 +4265,11 @@ var init_client = __esm({
4584
4265
  throw new SandboxError("times must be a positive integer");
4585
4266
  }
4586
4267
  const client = this.withRequestTimeout(options?.requestTimeout);
4587
- const raw = await client.http.requestJson(
4588
- "POST",
4589
- client.path(`sandboxes/${sandboxId}/copy?times=${encodeURIComponent(String(times))}`),
4590
- { allowedErrorStatusCodes: COPY_SANDBOX_ALLOWED_ERROR_STATUS_CODES }
4268
+ return client.tracedJson(
4269
+ () => client.native.copySandbox(sandboxId, times),
4270
+ "sandboxId",
4271
+ { sandboxId, notFoundKind: "sandbox" }
4591
4272
  );
4592
- const result = fromSnakeKeys(raw, "sandboxId");
4593
- return Object.assign(result, { traceId: raw.traceId });
4594
4273
  }
4595
4274
  // --- Snapshots ---
4596
4275
  /**
@@ -4600,44 +4279,33 @@ var init_client = __esm({
4600
4279
  * status — the snapshot is created asynchronously. Poll `getSnapshot()` until
4601
4280
  * `local_ready`, `completed`, or `failed`, or use `snapshotAndWait()` to
4602
4281
  * block automatically.
4603
- *
4604
- * @param options.snapshotType - `"filesystem"` for cold-boot snapshots (e.g. image builds).
4605
- * Omit to use the server default (`filesystem`).
4606
4282
  */
4607
4283
  async snapshot(sandboxId, options) {
4608
- const requestOptions = options?.snapshotType != null ? { body: { snapshot_type: options.snapshotType } } : void 0;
4609
- const raw = await this.http.requestJson(
4610
- "POST",
4611
- this.path(`sandboxes/${sandboxId}/snapshot`),
4612
- requestOptions
4284
+ return this.plainJson(
4285
+ () => this.native.createSnapshot(sandboxId, options?.snapshotType ?? null),
4286
+ "snapshotId",
4287
+ { sandboxId, notFoundKind: "sandbox" }
4613
4288
  );
4614
- return fromSnakeKeys(raw, "snapshotId");
4615
4289
  }
4616
4290
  /** Get current status and metadata for a snapshot by ID. */
4617
4291
  async getSnapshot(snapshotId) {
4618
- const raw = await this.http.requestJson(
4619
- "GET",
4620
- this.path(`snapshots/${snapshotId}`)
4292
+ return this.tracedJson(
4293
+ () => this.native.getSnapshot(snapshotId),
4294
+ "snapshotId"
4621
4295
  );
4622
- return Object.assign(fromSnakeKeys(raw, "snapshotId"), { traceId: raw.traceId });
4623
4296
  }
4624
4297
  /** List all snapshots in the namespace. */
4625
4298
  async listSnapshots() {
4626
- const raw = await this.http.requestJson(
4627
- "GET",
4628
- this.path("snapshots")
4629
- );
4630
- const snapshots = (raw.snapshots ?? []).map(
4299
+ const { traceId, json } = await callNative(() => this.native.listSnapshots());
4300
+ const parsed = JSON.parse(json);
4301
+ const snapshots = (parsed.snapshots ?? []).map(
4631
4302
  (s) => fromSnakeKeys(s, "snapshotId")
4632
4303
  );
4633
- return Object.assign(snapshots, { traceId: raw.traceId });
4304
+ return Object.assign(snapshots, { traceId });
4634
4305
  }
4635
4306
  /** Delete a snapshot by ID. */
4636
4307
  async deleteSnapshot(snapshotId) {
4637
- await this.http.requestJson(
4638
- "DELETE",
4639
- this.path(`snapshots/${snapshotId}`)
4640
- );
4308
+ await callNative(() => this.native.deleteSnapshot(snapshotId));
4641
4309
  }
4642
4310
  /**
4643
4311
  * Create a snapshot and block until it is locally ready.
@@ -4645,14 +4313,6 @@ var init_client = __esm({
4645
4313
  * Combines `snapshot()` with polling `getSnapshot()` until `local_ready`
4646
4314
  * or `completed`. Pass `{ waitUntil: "completed" }` when durable
4647
4315
  * `snapshotUri` metadata is required.
4648
- * Prefer `sandbox.checkpoint()` on a `Sandbox` handle for the same behavior
4649
- * without managing the client separately.
4650
- *
4651
- * @param sandboxId - ID of the running sandbox to snapshot.
4652
- * @param options.timeout - Max seconds to wait (default 300).
4653
- * @param options.pollInterval - Seconds between status polls (default 1).
4654
- * @param options.snapshotType - Snapshot type passed through to `snapshot()`.
4655
- * @throws {SandboxError} If the snapshot fails or `timeout` elapses.
4656
4316
  */
4657
4317
  async snapshotAndWait(sandboxId, options) {
4658
4318
  const timeout = options?.timeout ?? 300;
@@ -4670,7 +4330,7 @@ var init_client = __esm({
4670
4330
  `Snapshot ${result.snapshotId} failed: ${info.error}`
4671
4331
  );
4672
4332
  }
4673
- await sleep2(pollInterval * 1e3);
4333
+ await sleep(pollInterval * 1e3);
4674
4334
  }
4675
4335
  throw new SandboxError(
4676
4336
  `Snapshot ${result.snapshotId} did not reach ${waitUntil} within ${timeout}s`
@@ -4691,31 +4351,27 @@ var init_client = __esm({
4691
4351
  if (options.entrypoint != null) body.entrypoint = options.entrypoint;
4692
4352
  if (options.maxContainers != null) body.max_containers = options.maxContainers;
4693
4353
  if (options.warmContainers != null) body.warm_containers = options.warmContainers;
4694
- const raw = await this.http.requestJson(
4695
- "POST",
4696
- this.path("sandbox-pools"),
4697
- { body }
4354
+ return this.plainJson(
4355
+ () => this.native.createPool(JSON.stringify(body)),
4356
+ "poolId"
4698
4357
  );
4699
- return fromSnakeKeys(raw, "poolId");
4700
4358
  }
4701
4359
  /** Get current state and metadata for a sandbox pool by ID. */
4702
4360
  async getPool(poolId) {
4703
- const raw = await this.http.requestJson(
4704
- "GET",
4705
- this.path(`sandbox-pools/${poolId}`)
4361
+ return this.plainJson(
4362
+ () => this.native.getPool(poolId),
4363
+ "poolId",
4364
+ { poolId, notFoundKind: "pool" }
4706
4365
  );
4707
- return fromSnakeKeys(raw, "poolId");
4708
4366
  }
4709
4367
  /** List all sandbox pools in the namespace. */
4710
4368
  async listPools() {
4711
- const raw = await this.http.requestJson(
4712
- "GET",
4713
- this.path("sandbox-pools")
4714
- );
4715
- const pools = (raw.pools ?? []).map(
4369
+ const { traceId, json } = await callNative(() => this.native.listPools());
4370
+ const parsed = JSON.parse(json);
4371
+ const pools = (parsed.pools ?? []).map(
4716
4372
  (p) => fromSnakeKeys(p, "poolId")
4717
4373
  );
4718
- return Object.assign(pools, { traceId: raw.traceId });
4374
+ return Object.assign(pools, { traceId });
4719
4375
  }
4720
4376
  /** Replace the configuration of an existing sandbox pool. */
4721
4377
  async updatePool(poolId, options) {
@@ -4731,19 +4387,15 @@ var init_client = __esm({
4731
4387
  if (options.entrypoint != null) body.entrypoint = options.entrypoint;
4732
4388
  if (options.maxContainers != null) body.max_containers = options.maxContainers;
4733
4389
  if (options.warmContainers != null) body.warm_containers = options.warmContainers;
4734
- const raw = await this.http.requestJson(
4735
- "PUT",
4736
- this.path(`sandbox-pools/${poolId}`),
4737
- { body }
4390
+ return this.plainJson(
4391
+ () => this.native.updatePool(poolId, JSON.stringify(body)),
4392
+ "poolId",
4393
+ { poolId, notFoundKind: "pool" }
4738
4394
  );
4739
- return fromSnakeKeys(raw, "poolId");
4740
4395
  }
4741
4396
  /** Delete a sandbox pool. Fails if the pool has active containers. */
4742
4397
  async deletePool(poolId) {
4743
- await this.http.requestJson(
4744
- "DELETE",
4745
- this.path(`sandbox-pools/${poolId}`)
4746
- );
4398
+ await callNative(() => this.native.deletePool(poolId), { poolId, notFoundKind: "pool" });
4747
4399
  }
4748
4400
  // --- Connect ---
4749
4401
  /** Return a `Sandbox` handle for an existing running sandbox without verifying it exists. */
@@ -4757,7 +4409,8 @@ var init_client = __esm({
4757
4409
  projectId: this.projectId,
4758
4410
  routingHint,
4759
4411
  resolveProxyInfo: proxyUrl == null ? async (currentIdentifier) => this.get(currentIdentifier) : void 0,
4760
- requestTimeout
4412
+ requestTimeout,
4413
+ nativeClient: this.native
4761
4414
  });
4762
4415
  }
4763
4416
  /**
@@ -4765,10 +4418,6 @@ var init_client = __esm({
4765
4418
  *
4766
4419
  * Blocks until the sandbox is ready or `requestTimeout` elapses. The returned
4767
4420
  * `Sandbox` auto-terminates when `terminate()` is called.
4768
- *
4769
- * @param options.requestTimeout - Max seconds to wait for `Running` status (default: client requestTimeout).
4770
- * @param options.startupTimeout - Deprecated alias for `requestTimeout`.
4771
- * @throws {SandboxError} If the sandbox terminates during startup or the timeout elapses.
4772
4421
  */
4773
4422
  async createAndConnect(options) {
4774
4423
  if (options?.poolId != null && options?.cloudInit != null) {
@@ -4830,7 +4479,7 @@ var init_client = __esm({
4830
4479
  `Sandbox ${result.sandboxId} did not start within ${requestTimeout}s`
4831
4480
  );
4832
4481
  }
4833
- const deadline = Date.now() + secondsToMillis4(requestTimeout);
4482
+ const deadline = Date.now() + secondsToMillis3(requestTimeout);
4834
4483
  while (Date.now() < deadline) {
4835
4484
  const pollStart = nowMs();
4836
4485
  const info = await requestClient.get(result.sandboxId);
@@ -4850,7 +4499,7 @@ var init_client = __esm({
4850
4499
  })
4851
4500
  );
4852
4501
  }
4853
- await sleep2(500);
4502
+ await sleep(500);
4854
4503
  }
4855
4504
  try {
4856
4505
  await requestClient.delete(result.sandboxId);
@@ -4996,21 +4645,21 @@ __export(sandbox_image_exports, {
4996
4645
  createSandboxImage: () => createSandboxImage,
4997
4646
  runCreateSandboxImageCli: () => runCreateSandboxImageCli
4998
4647
  });
4999
- import { createRequire } from "module";
4648
+ import { createRequire as createRequire2 } from "module";
5000
4649
  import path from "path";
5001
4650
  import { existsSync } from "fs";
5002
4651
  import { parseArgs } from "util";
5003
- function resolveRequire() {
4652
+ function resolveRequire2() {
5004
4653
  if (typeof module !== "undefined" && module.exports != null && typeof __require !== "undefined") {
5005
4654
  return __require;
5006
4655
  }
5007
- return createRequire(import.meta.url);
4656
+ return createRequire2(import.meta.url);
5008
4657
  }
5009
4658
  function loadNativeBinding() {
5010
4659
  if (cachedNativeBinding) return cachedNativeBinding;
5011
4660
  if (cachedNativeBindingError) throw cachedNativeBindingError;
5012
4661
  try {
5013
- const { loadNative } = resolveRequire()("../lib/runtime.cjs");
4662
+ const { loadNative } = resolveRequire2()("../lib/runtime.cjs");
5014
4663
  cachedNativeBinding = loadNative();
5015
4664
  return cachedNativeBinding;
5016
4665
  } catch (error) {
@@ -5226,9 +4875,369 @@ init_tunnel();
5226
4875
 
5227
4876
  // src/cloud-client.ts
5228
4877
  init_defaults();
5229
- init_http();
4878
+
4879
+ // src/http.ts
4880
+ init_defaults();
4881
+ init_errors();
4882
+ init_sdk_timings();
4883
+ import {
4884
+ Agent,
4885
+ fetch as undiciFetch,
4886
+ setGlobalDispatcher
4887
+ } from "undici";
4888
+ var REQUEST_TIMEOUT_HEADER = "X-Tensorlake-Request-Timeout-Ms";
4889
+ setGlobalDispatcher(
4890
+ new Agent({
4891
+ keepAliveTimeout: 6e4,
4892
+ allowH2: true
4893
+ })
4894
+ );
4895
+ function withTraceId(value, traceId) {
4896
+ if (value == null) {
4897
+ return { traceId };
4898
+ }
4899
+ return Object.assign(value, { traceId });
4900
+ }
4901
+ var HttpClient = class _HttpClient {
4902
+ baseUrl;
4903
+ headers;
4904
+ maxRetries;
4905
+ retryBackoffMs;
4906
+ timeoutMs;
4907
+ abortController = null;
4908
+ constructor(options) {
4909
+ const url = options.baseUrl;
4910
+ this.baseUrl = url.endsWith("/") ? url.slice(0, -1) : url;
4911
+ this.maxRetries = options.maxRetries ?? MAX_RETRIES;
4912
+ this.retryBackoffMs = options.retryBackoffMs ?? RETRY_BACKOFF_MS;
4913
+ this.timeoutMs = options.timeoutMs === void 0 ? DEFAULT_HTTP_TIMEOUT_MS : options.timeoutMs;
4914
+ this.headers = {
4915
+ "User-Agent": `tensorlake-typescript-sdk/${SDK_VERSION}`
4916
+ };
4917
+ if (this.timeoutMs != null) {
4918
+ this.headers[REQUEST_TIMEOUT_HEADER] = String(this.timeoutMs);
4919
+ }
4920
+ if (options.apiKey) {
4921
+ this.headers["Authorization"] = `Bearer ${options.apiKey}`;
4922
+ }
4923
+ if (options.organizationId) {
4924
+ this.headers["X-Forwarded-Organization-Id"] = options.organizationId;
4925
+ }
4926
+ if (options.projectId) {
4927
+ this.headers["X-Forwarded-Project-Id"] = options.projectId;
4928
+ }
4929
+ if (options.hostHeader) {
4930
+ this.headers["Host"] = options.hostHeader;
4931
+ }
4932
+ if (options.sandboxIdHeader) {
4933
+ this.headers["X-Tensorlake-Sandbox-Id"] = options.sandboxIdHeader;
4934
+ }
4935
+ if (options.routingHint) {
4936
+ this.headers["X-Tensorlake-Route-Hint"] = options.routingHint;
4937
+ }
4938
+ }
4939
+ close() {
4940
+ this.abortController?.abort();
4941
+ this.abortController = null;
4942
+ }
4943
+ /** Make a JSON request, returning the parsed response body. */
4944
+ async requestJson(method, path2, options) {
4945
+ const response = await this.requestResponse(method, path2, {
4946
+ json: options?.body,
4947
+ headers: options?.headers,
4948
+ signal: options?.signal,
4949
+ retryableStatusCodes: options?.retryableStatusCodes,
4950
+ allowedErrorStatusCodes: options?.allowedErrorStatusCodes,
4951
+ allowHttpErrors: options?.allowHttpErrors
4952
+ });
4953
+ const text = await response.text();
4954
+ const data = text ? JSON.parse(text) : void 0;
4955
+ return withTraceId(data, response.traceId);
4956
+ }
4957
+ /** Make a request returning raw bytes. */
4958
+ async requestBytes(method, path2, options) {
4959
+ const headers = { ...options?.headers ?? {} };
4960
+ if (options?.contentType) {
4961
+ headers["Content-Type"] = options.contentType;
4962
+ }
4963
+ const response = await this.requestResponse(method, path2, {
4964
+ body: options?.body,
4965
+ headers,
4966
+ signal: options?.signal
4967
+ });
4968
+ const buffer = await response.arrayBuffer();
4969
+ return withTraceId(new Uint8Array(buffer), response.traceId);
4970
+ }
4971
+ /** Make a request and return the response body as an SSE stream. */
4972
+ async requestStream(method, path2, options) {
4973
+ const response = await this.requestResponse(method, path2, {
4974
+ json: options?.json,
4975
+ headers: { Accept: "text/event-stream" },
4976
+ signal: options?.signal
4977
+ });
4978
+ if (!response.body) {
4979
+ throw new RemoteAPIError(
4980
+ response.status,
4981
+ "No response body for SSE stream"
4982
+ );
4983
+ }
4984
+ return withTraceId(response.body, response.traceId);
4985
+ }
4986
+ /** Make a request and return the raw Response. */
4987
+ async requestResponse(method, path2, options) {
4988
+ const headers = {
4989
+ ...this.headers,
4990
+ ...options?.headers ?? {}
4991
+ };
4992
+ const hasJsonBody = options?.json !== void 0;
4993
+ if (hasJsonBody && !hasHeader(headers, "Content-Type")) {
4994
+ headers["Content-Type"] = "application/json";
4995
+ }
4996
+ const body = hasJsonBody ? JSON.stringify(options?.json) : normalizeRequestBody(options?.body);
4997
+ const { response, traceId } = await this.doFetch(
4998
+ method,
4999
+ path2,
5000
+ body,
5001
+ headers,
5002
+ options?.signal,
5003
+ options?.allowHttpErrors ?? false,
5004
+ options?.retryableStatusCodes ?? RETRYABLE_STATUS_CODES,
5005
+ options?.allowedErrorStatusCodes ?? /* @__PURE__ */ new Set()
5006
+ );
5007
+ return withTraceId(response, traceId);
5008
+ }
5009
+ static makeTraceparent() {
5010
+ const randomHex = (bytes) => Array.from(crypto.getRandomValues(new Uint8Array(bytes))).map((b) => b.toString(16).padStart(2, "0")).join("");
5011
+ const traceId = randomHex(16);
5012
+ const spanId = randomHex(8);
5013
+ return { traceparent: `00-${traceId}-${spanId}-01`, traceId };
5014
+ }
5015
+ async doFetch(method, path2, body, headers, signal, allowHttpErrors = false, retryableStatusCodes = RETRYABLE_STATUS_CODES, allowedErrorStatusCodes = /* @__PURE__ */ new Set()) {
5016
+ const url = `${this.baseUrl}${path2}`;
5017
+ const { traceparent, traceId } = _HttpClient.makeTraceparent();
5018
+ headers["traceparent"] = traceparent;
5019
+ let lastError;
5020
+ const operationStart = nowMs();
5021
+ const timingPath = sdkTimingPayloadsEnabled() ? path2 : stripPathPayload(path2);
5022
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
5023
+ if (attempt > 0) {
5024
+ const delay = this.retryBackoffMs * Math.pow(2, attempt - 1);
5025
+ logSdkTimingEvent("http.request", "retry_sleep_start", {
5026
+ method,
5027
+ path: timingPath,
5028
+ attempt,
5029
+ delay_ms: delay,
5030
+ server_trace_id: traceId
5031
+ });
5032
+ await sleep2(delay);
5033
+ }
5034
+ this.abortController = new AbortController();
5035
+ let timedOut = false;
5036
+ const timeoutId = this.timeoutMs == null ? null : setTimeout(() => {
5037
+ timedOut = true;
5038
+ this.abortController?.abort();
5039
+ }, this.timeoutMs);
5040
+ const combinedSignal = signal ? anySignal([signal, this.abortController.signal]) : this.abortController.signal;
5041
+ const attemptStart = nowMs();
5042
+ try {
5043
+ const response = await undiciFetch(url, {
5044
+ method,
5045
+ headers,
5046
+ body,
5047
+ signal: combinedSignal
5048
+ });
5049
+ if (timeoutId != null) clearTimeout(timeoutId);
5050
+ logSdkTiming("http.request", "headers", attemptStart, {
5051
+ method,
5052
+ path: timingPath,
5053
+ status: response.status,
5054
+ attempt,
5055
+ server_trace_id: traceId
5056
+ });
5057
+ if (response.ok) {
5058
+ logSdkTiming("http.request", "complete", operationStart, {
5059
+ method,
5060
+ path: timingPath,
5061
+ status: response.status,
5062
+ attempts: attempt + 1,
5063
+ server_trace_id: traceId
5064
+ });
5065
+ return { response, traceId };
5066
+ }
5067
+ if (retryableStatusCodes.has(response.status) && attempt < this.maxRetries) {
5068
+ lastError = new RemoteAPIError(
5069
+ response.status,
5070
+ await response.text().catch(() => "")
5071
+ );
5072
+ logSdkTimingEvent("http.request", "retryable_status", {
5073
+ method,
5074
+ path: timingPath,
5075
+ status: response.status,
5076
+ attempt,
5077
+ server_trace_id: traceId
5078
+ });
5079
+ continue;
5080
+ }
5081
+ if (allowHttpErrors || allowedErrorStatusCodes.has(response.status)) {
5082
+ logSdkTiming("http.request", "complete", operationStart, {
5083
+ method,
5084
+ path: timingPath,
5085
+ status: response.status,
5086
+ attempts: attempt + 1,
5087
+ server_trace_id: traceId
5088
+ });
5089
+ return { response, traceId };
5090
+ }
5091
+ const errorBody = await response.text().catch(() => "");
5092
+ throwMappedError(response.status, errorBody, path2);
5093
+ } catch (err) {
5094
+ if (timeoutId != null) clearTimeout(timeoutId);
5095
+ if (err instanceof RemoteAPIError || err instanceof SandboxNotFoundError || err instanceof PoolNotFoundError || err instanceof PoolInUseError) {
5096
+ throw err;
5097
+ }
5098
+ if (signal?.aborted && !timedOut) {
5099
+ throw new SandboxConnectionError("request aborted by caller", {
5100
+ cause: err
5101
+ });
5102
+ }
5103
+ lastError = err instanceof Error ? err : new Error(String(err));
5104
+ logSdkTiming("http.request", "error", attemptStart, {
5105
+ method,
5106
+ path: timingPath,
5107
+ attempt,
5108
+ server_trace_id: traceId,
5109
+ error: timedOut ? `request timed out after ${this.timeoutMs}ms` : describeError(err)
5110
+ });
5111
+ if (attempt >= this.maxRetries) {
5112
+ const detail = timedOut ? `request timed out after ${this.timeoutMs}ms` : describeError(err);
5113
+ throw new SandboxConnectionError(detail, { cause: err });
5114
+ }
5115
+ }
5116
+ }
5117
+ throw new SandboxConnectionError(
5118
+ lastError ? describeError(lastError) : "request failed",
5119
+ { cause: lastError }
5120
+ );
5121
+ }
5122
+ };
5123
+ function hasHeader(headers, name) {
5124
+ const lowered = name.toLowerCase();
5125
+ return Object.keys(headers).some((key) => key.toLowerCase() === lowered);
5126
+ }
5127
+ function normalizeRequestBody(body) {
5128
+ if (body == null) {
5129
+ return void 0;
5130
+ }
5131
+ if (body instanceof Uint8Array) {
5132
+ return Uint8Array.from(body).buffer;
5133
+ }
5134
+ return body;
5135
+ }
5136
+ function stripPathPayload(path2) {
5137
+ const queryStart = path2.search(/[?#]/);
5138
+ return queryStart === -1 ? path2 : path2.slice(0, queryStart);
5139
+ }
5140
+ function throwMappedError(status, body, path2) {
5141
+ let message = body;
5142
+ try {
5143
+ const parsed = JSON.parse(body);
5144
+ if (parsed.message) message = parsed.message;
5145
+ else if (parsed.error) message = parsed.error;
5146
+ } catch {
5147
+ }
5148
+ if (status === 404) {
5149
+ if (path2.includes("sandbox-pools") || path2.includes("pools")) {
5150
+ const match = path2.match(/sandbox-pools\/([^/]+)/);
5151
+ if (match) throw new PoolNotFoundError(match[1]);
5152
+ }
5153
+ if (path2.includes("sandboxes")) {
5154
+ const match = path2.match(/sandboxes\/([^/]+)/);
5155
+ if (match) throw new SandboxNotFoundError(match[1]);
5156
+ }
5157
+ throw new RemoteAPIError(404, message);
5158
+ }
5159
+ if (status === 409) {
5160
+ if (path2.includes("sandbox-pools") || path2.includes("pools")) {
5161
+ const match = path2.match(/sandbox-pools\/([^/]+)/);
5162
+ if (match) throw new PoolInUseError(match[1], message);
5163
+ }
5164
+ }
5165
+ throw new RemoteAPIError(status, message);
5166
+ }
5167
+ function anySignal(signals) {
5168
+ const controller = new AbortController();
5169
+ for (const signal of signals) {
5170
+ if (signal.aborted) {
5171
+ controller.abort(signal.reason);
5172
+ return controller.signal;
5173
+ }
5174
+ signal.addEventListener("abort", () => controller.abort(signal.reason), {
5175
+ once: true
5176
+ });
5177
+ }
5178
+ return controller.signal;
5179
+ }
5180
+ function sleep2(ms) {
5181
+ return new Promise((resolve) => setTimeout(resolve, ms));
5182
+ }
5183
+
5184
+ // src/cloud-client.ts
5230
5185
  init_models();
5231
- init_sse();
5186
+
5187
+ // src/sse.ts
5188
+ async function* parseSSEMessages(stream, signal) {
5189
+ const reader = stream.getReader();
5190
+ const decoder = new TextDecoder();
5191
+ let buffer = "";
5192
+ try {
5193
+ while (true) {
5194
+ if (signal?.aborted) break;
5195
+ const { done, value } = await reader.read();
5196
+ if (done) break;
5197
+ buffer += decoder.decode(value, { stream: true });
5198
+ const parts = buffer.split(/\r?\n\r?\n/);
5199
+ buffer = parts.pop() ?? "";
5200
+ for (const part of parts) {
5201
+ const lines = part.split(/\r?\n/);
5202
+ const dataLines = [];
5203
+ let event;
5204
+ let id;
5205
+ for (const line of lines) {
5206
+ if (!line || line.startsWith(":")) continue;
5207
+ const separator = line.indexOf(":");
5208
+ const field = separator === -1 ? line : line.slice(0, separator);
5209
+ let value2 = separator === -1 ? "" : line.slice(separator + 1);
5210
+ if (value2.startsWith(" ")) {
5211
+ value2 = value2.slice(1);
5212
+ }
5213
+ if (field === "data") {
5214
+ dataLines.push(value2);
5215
+ } else if (field === "event") {
5216
+ event = value2;
5217
+ } else if (field === "id") {
5218
+ id = value2;
5219
+ }
5220
+ }
5221
+ if (dataLines.length > 0 || event || id) {
5222
+ yield { data: dataLines.join("\n"), event, id };
5223
+ }
5224
+ }
5225
+ }
5226
+ } finally {
5227
+ reader.releaseLock();
5228
+ }
5229
+ }
5230
+ async function* parseSSEStream(stream, signal) {
5231
+ for await (const message of parseSSEMessages(stream, signal)) {
5232
+ if (!message.data) continue;
5233
+ try {
5234
+ yield JSON.parse(message.data);
5235
+ } catch {
5236
+ }
5237
+ }
5238
+ }
5239
+
5240
+ // src/cloud-client.ts
5232
5241
  var CloudClient = class _CloudClient {
5233
5242
  http;
5234
5243
  organizationId;