tensorlake 0.4.50 → 0.5.0
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/bin/darwin-arm64/tensorlake +0 -0
- package/dist/bin/darwin-arm64/tl +0 -0
- package/dist/bin/linux-x64/tensorlake +0 -0
- package/dist/bin/linux-x64/tl +0 -0
- package/dist/bin/win32-x64/tensorlake.exe +0 -0
- package/dist/bin/win32-x64/tl.exe +0 -0
- package/dist/index.cjs +312 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +185 -8
- package/dist/index.d.ts +185 -8
- package/dist/index.js +312 -28
- package/dist/index.js.map +1 -1
- package/dist/{sandbox-image-C79FXqQk.d.cts → sandbox-image-CMJ_FOOV.d.cts} +17 -1
- package/dist/{sandbox-image-C79FXqQk.d.ts → sandbox-image-CMJ_FOOV.d.ts} +17 -1
- package/dist/sandbox-image.cjs +299 -15
- package/dist/sandbox-image.cjs.map +1 -1
- package/dist/sandbox-image.d.cts +1 -1
- package/dist/sandbox-image.d.ts +1 -1
- package/dist/sandbox-image.js +304 -15
- package/dist/sandbox-image.js.map +1 -1
- package/package.json +1 -1
package/dist/sandbox-image.cjs
CHANGED
|
@@ -79,10 +79,11 @@ var init_models = __esm({
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
// src/defaults.ts
|
|
82
|
-
var API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TIMEOUT_MS, MAX_RETRIES, RETRY_BACKOFF_MS, RETRYABLE_STATUS_CODES;
|
|
82
|
+
var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TIMEOUT_MS, MAX_RETRIES, RETRY_BACKOFF_MS, RETRYABLE_STATUS_CODES;
|
|
83
83
|
var init_defaults = __esm({
|
|
84
84
|
"src/defaults.ts"() {
|
|
85
85
|
"use strict";
|
|
86
|
+
SDK_VERSION = "0.4.49";
|
|
86
87
|
API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
|
|
87
88
|
API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
|
|
88
89
|
NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
|
|
@@ -156,6 +157,12 @@ var init_errors = __esm({
|
|
|
156
157
|
});
|
|
157
158
|
|
|
158
159
|
// src/http.ts
|
|
160
|
+
function withTraceId(value, traceId) {
|
|
161
|
+
if (value == null) {
|
|
162
|
+
return { traceId };
|
|
163
|
+
}
|
|
164
|
+
return Object.assign(value, { traceId });
|
|
165
|
+
}
|
|
159
166
|
function hasHeader(headers, name) {
|
|
160
167
|
const lowered = name.toLowerCase();
|
|
161
168
|
return Object.keys(headers).some((key) => key.toLowerCase() === lowered);
|
|
@@ -225,7 +232,7 @@ var init_http = __esm({
|
|
|
225
232
|
allowH2: true
|
|
226
233
|
})
|
|
227
234
|
);
|
|
228
|
-
HttpClient = class {
|
|
235
|
+
HttpClient = class _HttpClient {
|
|
229
236
|
baseUrl;
|
|
230
237
|
headers;
|
|
231
238
|
maxRetries;
|
|
@@ -238,7 +245,9 @@ var init_http = __esm({
|
|
|
238
245
|
this.maxRetries = options.maxRetries ?? MAX_RETRIES;
|
|
239
246
|
this.retryBackoffMs = options.retryBackoffMs ?? RETRY_BACKOFF_MS;
|
|
240
247
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
|
|
241
|
-
this.headers = {
|
|
248
|
+
this.headers = {
|
|
249
|
+
"User-Agent": `tensorlake-typescript-sdk/${SDK_VERSION}`
|
|
250
|
+
};
|
|
242
251
|
if (options.apiKey) {
|
|
243
252
|
this.headers["Authorization"] = `Bearer ${options.apiKey}`;
|
|
244
253
|
}
|
|
@@ -267,8 +276,8 @@ var init_http = __esm({
|
|
|
267
276
|
signal: options?.signal
|
|
268
277
|
});
|
|
269
278
|
const text = await response.text();
|
|
270
|
-
|
|
271
|
-
return
|
|
279
|
+
const data = text ? JSON.parse(text) : void 0;
|
|
280
|
+
return withTraceId(data, response.traceId);
|
|
272
281
|
}
|
|
273
282
|
/** Make a request returning raw bytes. */
|
|
274
283
|
async requestBytes(method, path2, options) {
|
|
@@ -282,7 +291,7 @@ var init_http = __esm({
|
|
|
282
291
|
signal: options?.signal
|
|
283
292
|
});
|
|
284
293
|
const buffer = await response.arrayBuffer();
|
|
285
|
-
return new Uint8Array(buffer);
|
|
294
|
+
return withTraceId(new Uint8Array(buffer), response.traceId);
|
|
286
295
|
}
|
|
287
296
|
/** Make a request and return the response body as an SSE stream. */
|
|
288
297
|
async requestStream(method, path2, options) {
|
|
@@ -297,7 +306,7 @@ var init_http = __esm({
|
|
|
297
306
|
"No response body for SSE stream"
|
|
298
307
|
);
|
|
299
308
|
}
|
|
300
|
-
return response.body;
|
|
309
|
+
return withTraceId(response.body, response.traceId);
|
|
301
310
|
}
|
|
302
311
|
/** Make a request and return the raw Response. */
|
|
303
312
|
async requestResponse(method, path2, options) {
|
|
@@ -310,7 +319,7 @@ var init_http = __esm({
|
|
|
310
319
|
headers["Content-Type"] = "application/json";
|
|
311
320
|
}
|
|
312
321
|
const body = hasJsonBody ? JSON.stringify(options?.json) : normalizeRequestBody(options?.body);
|
|
313
|
-
|
|
322
|
+
const { response, traceId } = await this.doFetch(
|
|
314
323
|
method,
|
|
315
324
|
path2,
|
|
316
325
|
body,
|
|
@@ -318,9 +327,18 @@ var init_http = __esm({
|
|
|
318
327
|
options?.signal,
|
|
319
328
|
options?.allowHttpErrors ?? false
|
|
320
329
|
);
|
|
330
|
+
return withTraceId(response, traceId);
|
|
331
|
+
}
|
|
332
|
+
static makeTraceparent() {
|
|
333
|
+
const randomHex = (bytes) => Array.from(crypto.getRandomValues(new Uint8Array(bytes))).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
334
|
+
const traceId = randomHex(16);
|
|
335
|
+
const spanId = randomHex(8);
|
|
336
|
+
return { traceparent: `00-${traceId}-${spanId}-01`, traceId };
|
|
321
337
|
}
|
|
322
338
|
async doFetch(method, path2, body, headers, signal, allowHttpErrors = false) {
|
|
323
339
|
const url = `${this.baseUrl}${path2}`;
|
|
340
|
+
const { traceparent, traceId } = _HttpClient.makeTraceparent();
|
|
341
|
+
headers["traceparent"] = traceparent;
|
|
324
342
|
let lastError;
|
|
325
343
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
326
344
|
if (attempt > 0) {
|
|
@@ -341,7 +359,7 @@ var init_http = __esm({
|
|
|
341
359
|
signal: combinedSignal
|
|
342
360
|
});
|
|
343
361
|
clearTimeout(timeoutId);
|
|
344
|
-
if (response.ok) return response;
|
|
362
|
+
if (response.ok) return { response, traceId };
|
|
345
363
|
if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < this.maxRetries) {
|
|
346
364
|
lastError = new RemoteAPIError(
|
|
347
365
|
response.status,
|
|
@@ -350,7 +368,7 @@ var init_http = __esm({
|
|
|
350
368
|
continue;
|
|
351
369
|
}
|
|
352
370
|
if (allowHttpErrors) {
|
|
353
|
-
return response;
|
|
371
|
+
return { response, traceId };
|
|
354
372
|
}
|
|
355
373
|
const errorBody = await response.text().catch(() => "");
|
|
356
374
|
throwMappedError(response.status, errorBody, path2);
|
|
@@ -3097,6 +3115,7 @@ var init_sandbox = __esm({
|
|
|
3097
3115
|
};
|
|
3098
3116
|
Sandbox = class {
|
|
3099
3117
|
sandboxId;
|
|
3118
|
+
traceId = null;
|
|
3100
3119
|
http;
|
|
3101
3120
|
baseUrl;
|
|
3102
3121
|
wsHeaders;
|
|
@@ -3134,9 +3153,126 @@ var init_sandbox = __esm({
|
|
|
3134
3153
|
this.ownsSandbox = true;
|
|
3135
3154
|
this.lifecycleClient = client;
|
|
3136
3155
|
}
|
|
3156
|
+
// --- Static factory methods ---
|
|
3157
|
+
/**
|
|
3158
|
+
* Create a new sandbox and return a connected, running handle.
|
|
3159
|
+
*
|
|
3160
|
+
* Covers both fresh sandbox creation and restore-from-snapshot (set
|
|
3161
|
+
* `snapshotId`). Blocks until the sandbox is `Running`.
|
|
3162
|
+
*/
|
|
3163
|
+
static async create(options) {
|
|
3164
|
+
const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3165
|
+
const client = new SandboxClient2(
|
|
3166
|
+
options,
|
|
3167
|
+
/* _internal */
|
|
3168
|
+
true
|
|
3169
|
+
);
|
|
3170
|
+
const sandbox = await client.createAndConnect(options);
|
|
3171
|
+
sandbox.lifecycleClient = client;
|
|
3172
|
+
return sandbox;
|
|
3173
|
+
}
|
|
3174
|
+
/**
|
|
3175
|
+
* Attach to an existing sandbox and return a connected handle.
|
|
3176
|
+
*
|
|
3177
|
+
* Verifies the sandbox exists via a server GET call, then returns a handle
|
|
3178
|
+
* in whatever state the sandbox is in. Does **not** auto-resume a suspended
|
|
3179
|
+
* sandbox — call `sandbox.resume()` explicitly.
|
|
3180
|
+
*/
|
|
3181
|
+
static async connect(options) {
|
|
3182
|
+
const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3183
|
+
const client = new SandboxClient2(
|
|
3184
|
+
options,
|
|
3185
|
+
/* _internal */
|
|
3186
|
+
true
|
|
3187
|
+
);
|
|
3188
|
+
await client.get(options.sandboxId);
|
|
3189
|
+
const sandbox = client.connect(options.sandboxId, options.proxyUrl, options.routingHint);
|
|
3190
|
+
sandbox.lifecycleClient = client;
|
|
3191
|
+
return sandbox;
|
|
3192
|
+
}
|
|
3193
|
+
// --- Static snapshot management ---
|
|
3194
|
+
/** Get information about a snapshot by ID. No sandbox handle needed. */
|
|
3195
|
+
static async getSnapshot(snapshotId, options) {
|
|
3196
|
+
const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3197
|
+
const client = new SandboxClient2(
|
|
3198
|
+
options,
|
|
3199
|
+
/* _internal */
|
|
3200
|
+
true
|
|
3201
|
+
);
|
|
3202
|
+
return client.getSnapshot(snapshotId);
|
|
3203
|
+
}
|
|
3204
|
+
/** Delete a snapshot by ID. No sandbox handle needed. */
|
|
3205
|
+
static async deleteSnapshot(snapshotId, options) {
|
|
3206
|
+
const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3207
|
+
const client = new SandboxClient2(
|
|
3208
|
+
options,
|
|
3209
|
+
/* _internal */
|
|
3210
|
+
true
|
|
3211
|
+
);
|
|
3212
|
+
await client.deleteSnapshot(snapshotId);
|
|
3213
|
+
}
|
|
3214
|
+
// --- Instance lifecycle methods ---
|
|
3215
|
+
requireLifecycleClient(operation) {
|
|
3216
|
+
if (!this.lifecycleClient) {
|
|
3217
|
+
throw new SandboxError(
|
|
3218
|
+
`Cannot ${operation}: no lifecycle client available. Use Sandbox.create() or Sandbox.connect() to get a lifecycle-aware handle.`
|
|
3219
|
+
);
|
|
3220
|
+
}
|
|
3221
|
+
return this.lifecycleClient;
|
|
3222
|
+
}
|
|
3223
|
+
/**
|
|
3224
|
+
* Suspend this sandbox.
|
|
3225
|
+
*
|
|
3226
|
+
* By default blocks until the sandbox is fully `Suspended`. Pass
|
|
3227
|
+
* `{ wait: false }` for fire-and-return.
|
|
3228
|
+
*/
|
|
3229
|
+
async suspend(options) {
|
|
3230
|
+
const client = this.requireLifecycleClient("suspend");
|
|
3231
|
+
await client.suspend(this.sandboxId, options);
|
|
3232
|
+
}
|
|
3233
|
+
/**
|
|
3234
|
+
* Resume this sandbox.
|
|
3235
|
+
*
|
|
3236
|
+
* By default blocks until the sandbox is `Running` and routable. Pass
|
|
3237
|
+
* `{ wait: false }` for fire-and-return.
|
|
3238
|
+
*/
|
|
3239
|
+
async resume(options) {
|
|
3240
|
+
const client = this.requireLifecycleClient("resume");
|
|
3241
|
+
await client.resume(this.sandboxId, options);
|
|
3242
|
+
}
|
|
3243
|
+
/**
|
|
3244
|
+
* Create a snapshot of this sandbox's filesystem and wait for it to
|
|
3245
|
+
* be committed.
|
|
3246
|
+
*
|
|
3247
|
+
* By default blocks until the snapshot artifact is ready and returns
|
|
3248
|
+
* the completed `SnapshotInfo`. Pass `{ wait: false }` to fire-and-return
|
|
3249
|
+
* (returns `undefined`).
|
|
3250
|
+
*/
|
|
3251
|
+
async checkpoint(options) {
|
|
3252
|
+
const client = this.requireLifecycleClient("checkpoint");
|
|
3253
|
+
if (options?.wait === false) {
|
|
3254
|
+
await client.snapshot(this.sandboxId, { contentMode: options.contentMode });
|
|
3255
|
+
return void 0;
|
|
3256
|
+
}
|
|
3257
|
+
return client.snapshotAndWait(this.sandboxId, {
|
|
3258
|
+
timeout: options?.timeout,
|
|
3259
|
+
pollInterval: options?.pollInterval,
|
|
3260
|
+
contentMode: options?.contentMode
|
|
3261
|
+
});
|
|
3262
|
+
}
|
|
3263
|
+
/**
|
|
3264
|
+
* List snapshots taken from this sandbox.
|
|
3265
|
+
*/
|
|
3266
|
+
async listSnapshots() {
|
|
3267
|
+
const client = this.requireLifecycleClient("listSnapshots");
|
|
3268
|
+
const all = await client.listSnapshots();
|
|
3269
|
+
return all.filter((s) => s.sandboxId === this.sandboxId);
|
|
3270
|
+
}
|
|
3271
|
+
/** Close the HTTP client. The sandbox keeps running. */
|
|
3137
3272
|
close() {
|
|
3138
3273
|
this.http.close();
|
|
3139
3274
|
}
|
|
3275
|
+
/** Terminate the sandbox and release all resources. */
|
|
3140
3276
|
async terminate() {
|
|
3141
3277
|
const client = this.lifecycleClient;
|
|
3142
3278
|
this.ownsSandbox = false;
|
|
@@ -3189,6 +3325,14 @@ var init_sandbox = __esm({
|
|
|
3189
3325
|
};
|
|
3190
3326
|
}
|
|
3191
3327
|
// --- Process management ---
|
|
3328
|
+
/**
|
|
3329
|
+
* Start a process in the sandbox without waiting for it to exit.
|
|
3330
|
+
*
|
|
3331
|
+
* Returns a `ProcessInfo` with the assigned `pid`. Use `getProcess()` to
|
|
3332
|
+
* poll status, or `followStdout()` / `followOutput()` to stream output
|
|
3333
|
+
* until the process exits. Use `run()` instead to block until completion
|
|
3334
|
+
* and get combined output in one call.
|
|
3335
|
+
*/
|
|
3192
3336
|
async startProcess(command, options) {
|
|
3193
3337
|
const payload = { command };
|
|
3194
3338
|
if (options?.args != null) payload.args = options.args;
|
|
@@ -3210,6 +3354,7 @@ var init_sandbox = __esm({
|
|
|
3210
3354
|
);
|
|
3211
3355
|
return fromSnakeKeys(raw);
|
|
3212
3356
|
}
|
|
3357
|
+
/** List all processes (running and exited) tracked by the sandbox daemon. */
|
|
3213
3358
|
async listProcesses() {
|
|
3214
3359
|
const raw = await this.http.requestJson(
|
|
3215
3360
|
"GET",
|
|
@@ -3217,6 +3362,7 @@ var init_sandbox = __esm({
|
|
|
3217
3362
|
);
|
|
3218
3363
|
return (raw.processes ?? []).map((p) => fromSnakeKeys(p));
|
|
3219
3364
|
}
|
|
3365
|
+
/** Get current status and metadata for a process by PID. */
|
|
3220
3366
|
async getProcess(pid) {
|
|
3221
3367
|
const raw = await this.http.requestJson(
|
|
3222
3368
|
"GET",
|
|
@@ -3224,9 +3370,11 @@ var init_sandbox = __esm({
|
|
|
3224
3370
|
);
|
|
3225
3371
|
return fromSnakeKeys(raw);
|
|
3226
3372
|
}
|
|
3373
|
+
/** Send SIGKILL to a process. */
|
|
3227
3374
|
async killProcess(pid) {
|
|
3228
3375
|
await this.http.requestJson("DELETE", `/api/v1/processes/${pid}`);
|
|
3229
3376
|
}
|
|
3377
|
+
/** Send an arbitrary signal to a process (e.g. `15` for SIGTERM, `9` for SIGKILL). */
|
|
3230
3378
|
async sendSignal(pid, signal) {
|
|
3231
3379
|
const raw = await this.http.requestJson(
|
|
3232
3380
|
"POST",
|
|
@@ -3236,15 +3384,18 @@ var init_sandbox = __esm({
|
|
|
3236
3384
|
return fromSnakeKeys(raw);
|
|
3237
3385
|
}
|
|
3238
3386
|
// --- Process I/O ---
|
|
3387
|
+
/** Write bytes to a process's stdin. The process must have been started with `stdinMode: StdinMode.PIPE`. */
|
|
3239
3388
|
async writeStdin(pid, data) {
|
|
3240
3389
|
await this.http.requestBytes("POST", `/api/v1/processes/${pid}/stdin`, {
|
|
3241
3390
|
body: data,
|
|
3242
3391
|
contentType: "application/octet-stream"
|
|
3243
3392
|
});
|
|
3244
3393
|
}
|
|
3394
|
+
/** Close a process's stdin pipe, signalling EOF to the process. */
|
|
3245
3395
|
async closeStdin(pid) {
|
|
3246
3396
|
await this.http.requestJson("POST", `/api/v1/processes/${pid}/stdin/close`);
|
|
3247
3397
|
}
|
|
3398
|
+
/** Return all captured stdout lines produced so far by a process. */
|
|
3248
3399
|
async getStdout(pid) {
|
|
3249
3400
|
const raw = await this.http.requestJson(
|
|
3250
3401
|
"GET",
|
|
@@ -3252,6 +3403,7 @@ var init_sandbox = __esm({
|
|
|
3252
3403
|
);
|
|
3253
3404
|
return fromSnakeKeys(raw);
|
|
3254
3405
|
}
|
|
3406
|
+
/** Return all captured stderr lines produced so far by a process. */
|
|
3255
3407
|
async getStderr(pid) {
|
|
3256
3408
|
const raw = await this.http.requestJson(
|
|
3257
3409
|
"GET",
|
|
@@ -3259,6 +3411,7 @@ var init_sandbox = __esm({
|
|
|
3259
3411
|
);
|
|
3260
3412
|
return fromSnakeKeys(raw);
|
|
3261
3413
|
}
|
|
3414
|
+
/** Return all captured stdout+stderr lines produced so far by a process. */
|
|
3262
3415
|
async getOutput(pid) {
|
|
3263
3416
|
const raw = await this.http.requestJson(
|
|
3264
3417
|
"GET",
|
|
@@ -3267,6 +3420,7 @@ var init_sandbox = __esm({
|
|
|
3267
3420
|
return fromSnakeKeys(raw);
|
|
3268
3421
|
}
|
|
3269
3422
|
// --- Streaming (SSE) ---
|
|
3423
|
+
/** Stream stdout events from a process until it exits. Yields one `OutputEvent` per line. */
|
|
3270
3424
|
async *followStdout(pid, options) {
|
|
3271
3425
|
const stream = await this.http.requestStream(
|
|
3272
3426
|
"GET",
|
|
@@ -3280,6 +3434,7 @@ var init_sandbox = __esm({
|
|
|
3280
3434
|
yield fromSnakeKeys(raw);
|
|
3281
3435
|
}
|
|
3282
3436
|
}
|
|
3437
|
+
/** Stream stderr events from a process until it exits. Yields one `OutputEvent` per line. */
|
|
3283
3438
|
async *followStderr(pid, options) {
|
|
3284
3439
|
const stream = await this.http.requestStream(
|
|
3285
3440
|
"GET",
|
|
@@ -3293,6 +3448,7 @@ var init_sandbox = __esm({
|
|
|
3293
3448
|
yield fromSnakeKeys(raw);
|
|
3294
3449
|
}
|
|
3295
3450
|
}
|
|
3451
|
+
/** Stream combined stdout+stderr events from a process until it exits. Yields one `OutputEvent` per line. */
|
|
3296
3452
|
async *followOutput(pid, options) {
|
|
3297
3453
|
const stream = await this.http.requestStream(
|
|
3298
3454
|
"GET",
|
|
@@ -3307,12 +3463,14 @@ var init_sandbox = __esm({
|
|
|
3307
3463
|
}
|
|
3308
3464
|
}
|
|
3309
3465
|
// --- File operations ---
|
|
3466
|
+
/** Read a file from the sandbox and return its raw bytes. */
|
|
3310
3467
|
async readFile(path2) {
|
|
3311
3468
|
return this.http.requestBytes(
|
|
3312
3469
|
"GET",
|
|
3313
3470
|
`/api/v1/files?path=${encodeURIComponent(path2)}`
|
|
3314
3471
|
);
|
|
3315
3472
|
}
|
|
3473
|
+
/** Write raw bytes to a file in the sandbox, creating it if it does not exist. */
|
|
3316
3474
|
async writeFile(path2, content) {
|
|
3317
3475
|
await this.http.requestBytes(
|
|
3318
3476
|
"PUT",
|
|
@@ -3320,12 +3478,14 @@ var init_sandbox = __esm({
|
|
|
3320
3478
|
{ body: content, contentType: "application/octet-stream" }
|
|
3321
3479
|
);
|
|
3322
3480
|
}
|
|
3481
|
+
/** Delete a file from the sandbox. */
|
|
3323
3482
|
async deleteFile(path2) {
|
|
3324
3483
|
await this.http.requestJson(
|
|
3325
3484
|
"DELETE",
|
|
3326
3485
|
`/api/v1/files?path=${encodeURIComponent(path2)}`
|
|
3327
3486
|
);
|
|
3328
3487
|
}
|
|
3488
|
+
/** List the contents of a directory in the sandbox. */
|
|
3329
3489
|
async listDirectory(path2) {
|
|
3330
3490
|
const raw = await this.http.requestJson(
|
|
3331
3491
|
"GET",
|
|
@@ -3334,6 +3494,7 @@ var init_sandbox = __esm({
|
|
|
3334
3494
|
return fromSnakeKeys(raw);
|
|
3335
3495
|
}
|
|
3336
3496
|
// --- PTY ---
|
|
3497
|
+
/** Create an interactive PTY session. Returns a `sessionId` and `token` for WebSocket connection via `connectPty()`. */
|
|
3337
3498
|
async createPtySession(options) {
|
|
3338
3499
|
const payload = {
|
|
3339
3500
|
command: options.command,
|
|
@@ -3350,6 +3511,7 @@ var init_sandbox = __esm({
|
|
|
3350
3511
|
);
|
|
3351
3512
|
return fromSnakeKeys(raw);
|
|
3352
3513
|
}
|
|
3514
|
+
/** Create a PTY session and connect to it immediately. Cleans up the session if the WebSocket connection fails. */
|
|
3353
3515
|
async createPty(options) {
|
|
3354
3516
|
const { onData, onExit, ...createOptions } = options;
|
|
3355
3517
|
const session = await this.createPtySession(createOptions);
|
|
@@ -3363,6 +3525,7 @@ var init_sandbox = __esm({
|
|
|
3363
3525
|
throw error;
|
|
3364
3526
|
}
|
|
3365
3527
|
}
|
|
3528
|
+
/** Attach to an existing PTY session by ID and token and return a connected `Pty` handle. */
|
|
3366
3529
|
async connectPty(sessionId, token, options) {
|
|
3367
3530
|
const wsUrl = new URL(this.ptyWsUrl(sessionId, token));
|
|
3368
3531
|
const authToken = wsUrl.searchParams.get("token") ?? token;
|
|
@@ -3387,6 +3550,7 @@ var init_sandbox = __esm({
|
|
|
3387
3550
|
await pty.connect();
|
|
3388
3551
|
return pty;
|
|
3389
3552
|
}
|
|
3553
|
+
/** Open a TCP tunnel to a port inside the sandbox and return the local listener. */
|
|
3390
3554
|
async createTunnel(remotePort, options) {
|
|
3391
3555
|
return TcpTunnel.listen({
|
|
3392
3556
|
baseUrl: this.baseUrl,
|
|
@@ -3397,6 +3561,7 @@ var init_sandbox = __esm({
|
|
|
3397
3561
|
connectTimeout: options?.connectTimeout
|
|
3398
3562
|
});
|
|
3399
3563
|
}
|
|
3564
|
+
/** Connect to a sandbox VNC session for programmatic desktop control. */
|
|
3400
3565
|
async connectDesktop(options) {
|
|
3401
3566
|
return Desktop.connect({
|
|
3402
3567
|
baseUrl: this.baseUrl,
|
|
@@ -3419,6 +3584,7 @@ var init_sandbox = __esm({
|
|
|
3419
3584
|
return `${wsBase}/api/v1/pty/${sessionId}/ws?token=${token}`;
|
|
3420
3585
|
}
|
|
3421
3586
|
// --- Health ---
|
|
3587
|
+
/** Check the sandbox daemon health. */
|
|
3422
3588
|
async health() {
|
|
3423
3589
|
const raw = await this.http.requestJson(
|
|
3424
3590
|
"GET",
|
|
@@ -3426,6 +3592,7 @@ var init_sandbox = __esm({
|
|
|
3426
3592
|
);
|
|
3427
3593
|
return fromSnakeKeys(raw);
|
|
3428
3594
|
}
|
|
3595
|
+
/** Get sandbox daemon info (version, uptime, process counts). */
|
|
3429
3596
|
async info() {
|
|
3430
3597
|
const raw = await this.http.requestJson(
|
|
3431
3598
|
"GET",
|
|
@@ -3438,6 +3605,10 @@ var init_sandbox = __esm({
|
|
|
3438
3605
|
});
|
|
3439
3606
|
|
|
3440
3607
|
// src/client.ts
|
|
3608
|
+
var client_exports = {};
|
|
3609
|
+
__export(client_exports, {
|
|
3610
|
+
SandboxClient: () => SandboxClient
|
|
3611
|
+
});
|
|
3441
3612
|
function sleep2(ms) {
|
|
3442
3613
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3443
3614
|
}
|
|
@@ -3474,7 +3645,13 @@ var init_client = __esm({
|
|
|
3474
3645
|
projectId;
|
|
3475
3646
|
namespace;
|
|
3476
3647
|
local;
|
|
3477
|
-
|
|
3648
|
+
/** @internal Pass `true` to suppress the deprecation warning when used by `Sandbox.create()` / `Sandbox.connect()`. */
|
|
3649
|
+
constructor(options, _internal = false) {
|
|
3650
|
+
if (!_internal) {
|
|
3651
|
+
console.warn(
|
|
3652
|
+
"[tensorlake] SandboxClient is deprecated; use Sandbox.create() / Sandbox.connect() instead."
|
|
3653
|
+
);
|
|
3654
|
+
}
|
|
3478
3655
|
this.apiUrl = options?.apiUrl ?? API_URL;
|
|
3479
3656
|
this.apiKey = options?.apiKey ?? API_KEY;
|
|
3480
3657
|
this.organizationId = options?.organizationId;
|
|
@@ -3514,6 +3691,7 @@ var init_client = __esm({
|
|
|
3514
3691
|
return lifecyclePath(subpath, this.local, this.namespace);
|
|
3515
3692
|
}
|
|
3516
3693
|
// --- Sandbox CRUD ---
|
|
3694
|
+
/** Create a new sandbox. Returns immediately; the sandbox may still be starting. Use `createAndConnect()` for a blocking, ready-to-use handle. */
|
|
3517
3695
|
async create(options) {
|
|
3518
3696
|
const body = {
|
|
3519
3697
|
resources: {
|
|
@@ -3540,8 +3718,10 @@ var init_client = __esm({
|
|
|
3540
3718
|
this.path("sandboxes"),
|
|
3541
3719
|
{ body }
|
|
3542
3720
|
);
|
|
3543
|
-
|
|
3721
|
+
const result = fromSnakeKeys(raw, "sandboxId");
|
|
3722
|
+
return Object.assign(result, { traceId: raw.traceId });
|
|
3544
3723
|
}
|
|
3724
|
+
/** Get current state and metadata for a sandbox by ID. */
|
|
3545
3725
|
async get(sandboxId) {
|
|
3546
3726
|
const raw = await this.http.requestJson(
|
|
3547
3727
|
"GET",
|
|
@@ -3549,6 +3729,7 @@ var init_client = __esm({
|
|
|
3549
3729
|
);
|
|
3550
3730
|
return fromSnakeKeys(raw, "sandboxId");
|
|
3551
3731
|
}
|
|
3732
|
+
/** List all sandboxes in the namespace. */
|
|
3552
3733
|
async list() {
|
|
3553
3734
|
const raw = await this.http.requestJson(
|
|
3554
3735
|
"GET",
|
|
@@ -3558,6 +3739,7 @@ var init_client = __esm({
|
|
|
3558
3739
|
(s) => fromSnakeKeys(s, "sandboxId")
|
|
3559
3740
|
);
|
|
3560
3741
|
}
|
|
3742
|
+
/** Update sandbox properties such as name, exposed ports, and proxy auth settings. */
|
|
3561
3743
|
async update(sandboxId, options) {
|
|
3562
3744
|
const body = {};
|
|
3563
3745
|
if (options.name != null) body.name = options.name;
|
|
@@ -3577,6 +3759,7 @@ var init_client = __esm({
|
|
|
3577
3759
|
);
|
|
3578
3760
|
return fromSnakeKeys(raw, "sandboxId");
|
|
3579
3761
|
}
|
|
3762
|
+
/** Get the current proxy port settings for a sandbox. */
|
|
3580
3763
|
async getPortAccess(sandboxId) {
|
|
3581
3764
|
const info = await this.get(sandboxId);
|
|
3582
3765
|
return {
|
|
@@ -3585,6 +3768,7 @@ var init_client = __esm({
|
|
|
3585
3768
|
sandboxUrl: info.sandboxUrl
|
|
3586
3769
|
};
|
|
3587
3770
|
}
|
|
3771
|
+
/** Add one or more user ports to the sandbox proxy allowlist. */
|
|
3588
3772
|
async exposePorts(sandboxId, ports, options) {
|
|
3589
3773
|
const requestedPorts = normalizeUserPorts(ports);
|
|
3590
3774
|
const current = await this.getPortAccess(sandboxId);
|
|
@@ -3597,6 +3781,7 @@ var init_client = __esm({
|
|
|
3597
3781
|
exposedPorts: desiredPorts
|
|
3598
3782
|
});
|
|
3599
3783
|
}
|
|
3784
|
+
/** Remove one or more user ports from the sandbox proxy allowlist. */
|
|
3600
3785
|
async unexposePorts(sandboxId, ports) {
|
|
3601
3786
|
const requestedPorts = normalizeUserPorts(ports);
|
|
3602
3787
|
const current = await this.getPortAccess(sandboxId);
|
|
@@ -3607,32 +3792,98 @@ var init_client = __esm({
|
|
|
3607
3792
|
exposedPorts: desiredPorts
|
|
3608
3793
|
});
|
|
3609
3794
|
}
|
|
3795
|
+
/** Terminate and delete a sandbox. */
|
|
3610
3796
|
async delete(sandboxId) {
|
|
3611
3797
|
await this.http.requestJson(
|
|
3612
3798
|
"DELETE",
|
|
3613
3799
|
this.path(`sandboxes/${sandboxId}`)
|
|
3614
3800
|
);
|
|
3615
3801
|
}
|
|
3616
|
-
|
|
3802
|
+
/**
|
|
3803
|
+
* Suspend a named sandbox, preserving its state for later resume.
|
|
3804
|
+
*
|
|
3805
|
+
* Only sandboxes created with a `name` can be suspended; ephemeral sandboxes
|
|
3806
|
+
* cannot. By default blocks until the sandbox is fully `Suspended`. Pass
|
|
3807
|
+
* `{ wait: false }` to return immediately after the request is sent
|
|
3808
|
+
* (fire-and-return); the server processes the suspend asynchronously.
|
|
3809
|
+
*
|
|
3810
|
+
* @param sandboxId - ID or name of the sandbox.
|
|
3811
|
+
* @param options.wait - If `true` (default), poll until `Suspended`. Pass `false` to fire-and-return.
|
|
3812
|
+
* @param options.timeout - Max seconds to wait when `wait=true` (default 300).
|
|
3813
|
+
* @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
|
|
3814
|
+
* @throws {SandboxError} If `wait=true` and the sandbox does not reach `Suspended` within `timeout`.
|
|
3815
|
+
*/
|
|
3816
|
+
async suspend(sandboxId, options) {
|
|
3617
3817
|
await this.http.requestResponse(
|
|
3618
3818
|
"POST",
|
|
3619
3819
|
this.path(`sandboxes/${sandboxId}/suspend`)
|
|
3620
3820
|
);
|
|
3821
|
+
if (options?.wait === false) return;
|
|
3822
|
+
const timeout = options?.timeout ?? 300;
|
|
3823
|
+
const pollInterval = options?.pollInterval ?? 1;
|
|
3824
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
3825
|
+
while (Date.now() < deadline) {
|
|
3826
|
+
const info = await this.get(sandboxId);
|
|
3827
|
+
if (info.status === "suspended" /* SUSPENDED */) return;
|
|
3828
|
+
if (info.status === "terminated" /* TERMINATED */) {
|
|
3829
|
+
throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for suspend`);
|
|
3830
|
+
}
|
|
3831
|
+
await sleep2(pollInterval * 1e3);
|
|
3832
|
+
}
|
|
3833
|
+
throw new SandboxError(`Sandbox ${sandboxId} did not suspend within ${timeout}s`);
|
|
3621
3834
|
}
|
|
3622
|
-
|
|
3835
|
+
/**
|
|
3836
|
+
* Resume a suspended sandbox and bring it back to `Running`.
|
|
3837
|
+
*
|
|
3838
|
+
* By default blocks until the sandbox is `Running` and routable. Pass
|
|
3839
|
+
* `{ wait: false }` to return immediately after the request is sent
|
|
3840
|
+
* (fire-and-return); the server processes the resume asynchronously.
|
|
3841
|
+
*
|
|
3842
|
+
* @param sandboxId - ID or name of the sandbox.
|
|
3843
|
+
* @param options.wait - If `true` (default), poll until `Running`. Pass `false` to fire-and-return.
|
|
3844
|
+
* @param options.timeout - Max seconds to wait when `wait=true` (default 300).
|
|
3845
|
+
* @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
|
|
3846
|
+
* @throws {SandboxError} If `wait=true` and the sandbox does not reach `Running` within `timeout`.
|
|
3847
|
+
*/
|
|
3848
|
+
async resume(sandboxId, options) {
|
|
3623
3849
|
await this.http.requestResponse(
|
|
3624
3850
|
"POST",
|
|
3625
3851
|
this.path(`sandboxes/${sandboxId}/resume`)
|
|
3626
3852
|
);
|
|
3853
|
+
if (options?.wait === false) return;
|
|
3854
|
+
const timeout = options?.timeout ?? 300;
|
|
3855
|
+
const pollInterval = options?.pollInterval ?? 1;
|
|
3856
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
3857
|
+
while (Date.now() < deadline) {
|
|
3858
|
+
const info = await this.get(sandboxId);
|
|
3859
|
+
if (info.status === "running" /* RUNNING */) return;
|
|
3860
|
+
if (info.status === "terminated" /* TERMINATED */) {
|
|
3861
|
+
throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for resume`);
|
|
3862
|
+
}
|
|
3863
|
+
await sleep2(pollInterval * 1e3);
|
|
3864
|
+
}
|
|
3865
|
+
throw new SandboxError(`Sandbox ${sandboxId} did not resume within ${timeout}s`);
|
|
3627
3866
|
}
|
|
3867
|
+
/** Claim a warm sandbox from a pool, creating one if no warm containers are available. */
|
|
3628
3868
|
async claim(poolId) {
|
|
3629
3869
|
const raw = await this.http.requestJson(
|
|
3630
3870
|
"POST",
|
|
3631
3871
|
this.path(`sandbox-pools/${poolId}/sandboxes`)
|
|
3632
3872
|
);
|
|
3633
|
-
|
|
3873
|
+
const result = fromSnakeKeys(raw, "sandboxId");
|
|
3874
|
+
return Object.assign(result, { traceId: raw.traceId });
|
|
3634
3875
|
}
|
|
3635
3876
|
// --- Snapshots ---
|
|
3877
|
+
/**
|
|
3878
|
+
* Request a snapshot of a running sandbox's filesystem.
|
|
3879
|
+
*
|
|
3880
|
+
* This call **returns immediately** with a `snapshotId` and `in_progress`
|
|
3881
|
+
* status — the snapshot is created asynchronously. Poll `getSnapshot()` until
|
|
3882
|
+
* `completed` or `failed`, or use `snapshotAndWait()` to block automatically.
|
|
3883
|
+
*
|
|
3884
|
+
* @param options.contentMode - `"filesystem_only"` for cold-boot snapshots (e.g. image builds).
|
|
3885
|
+
* Omit to use the server default (full VM snapshot).
|
|
3886
|
+
*/
|
|
3636
3887
|
async snapshot(sandboxId, options) {
|
|
3637
3888
|
const requestOptions = options?.contentMode != null ? { body: { snapshot_content_mode: options.contentMode } } : void 0;
|
|
3638
3889
|
const raw = await this.http.requestJson(
|
|
@@ -3642,6 +3893,7 @@ var init_client = __esm({
|
|
|
3642
3893
|
);
|
|
3643
3894
|
return fromSnakeKeys(raw, "snapshotId");
|
|
3644
3895
|
}
|
|
3896
|
+
/** Get current status and metadata for a snapshot by ID. */
|
|
3645
3897
|
async getSnapshot(snapshotId) {
|
|
3646
3898
|
const raw = await this.http.requestJson(
|
|
3647
3899
|
"GET",
|
|
@@ -3649,6 +3901,7 @@ var init_client = __esm({
|
|
|
3649
3901
|
);
|
|
3650
3902
|
return fromSnakeKeys(raw, "snapshotId");
|
|
3651
3903
|
}
|
|
3904
|
+
/** List all snapshots in the namespace. */
|
|
3652
3905
|
async listSnapshots() {
|
|
3653
3906
|
const raw = await this.http.requestJson(
|
|
3654
3907
|
"GET",
|
|
@@ -3658,12 +3911,26 @@ var init_client = __esm({
|
|
|
3658
3911
|
(s) => fromSnakeKeys(s, "snapshotId")
|
|
3659
3912
|
);
|
|
3660
3913
|
}
|
|
3914
|
+
/** Delete a snapshot by ID. */
|
|
3661
3915
|
async deleteSnapshot(snapshotId) {
|
|
3662
3916
|
await this.http.requestJson(
|
|
3663
3917
|
"DELETE",
|
|
3664
3918
|
this.path(`snapshots/${snapshotId}`)
|
|
3665
3919
|
);
|
|
3666
3920
|
}
|
|
3921
|
+
/**
|
|
3922
|
+
* Create a snapshot and block until it is committed.
|
|
3923
|
+
*
|
|
3924
|
+
* Combines `snapshot()` with polling `getSnapshot()` until `completed`.
|
|
3925
|
+
* Prefer `sandbox.checkpoint()` on a `Sandbox` handle for the same behavior
|
|
3926
|
+
* without managing the client separately.
|
|
3927
|
+
*
|
|
3928
|
+
* @param sandboxId - ID of the running sandbox to snapshot.
|
|
3929
|
+
* @param options.timeout - Max seconds to wait (default 300).
|
|
3930
|
+
* @param options.pollInterval - Seconds between status polls (default 1).
|
|
3931
|
+
* @param options.contentMode - Content mode passed through to `snapshot()`.
|
|
3932
|
+
* @throws {SandboxError} If the snapshot fails or `timeout` elapses.
|
|
3933
|
+
*/
|
|
3667
3934
|
async snapshotAndWait(sandboxId, options) {
|
|
3668
3935
|
const timeout = options?.timeout ?? 300;
|
|
3669
3936
|
const pollInterval = options?.pollInterval ?? 1;
|
|
@@ -3686,6 +3953,7 @@ var init_client = __esm({
|
|
|
3686
3953
|
);
|
|
3687
3954
|
}
|
|
3688
3955
|
// --- Pools ---
|
|
3956
|
+
/** Create a new sandbox pool with warm pre-booted containers. */
|
|
3689
3957
|
async createPool(options) {
|
|
3690
3958
|
const body = {
|
|
3691
3959
|
image: options.image,
|
|
@@ -3707,6 +3975,7 @@ var init_client = __esm({
|
|
|
3707
3975
|
);
|
|
3708
3976
|
return fromSnakeKeys(raw, "poolId");
|
|
3709
3977
|
}
|
|
3978
|
+
/** Get current state and metadata for a sandbox pool by ID. */
|
|
3710
3979
|
async getPool(poolId) {
|
|
3711
3980
|
const raw = await this.http.requestJson(
|
|
3712
3981
|
"GET",
|
|
@@ -3714,6 +3983,7 @@ var init_client = __esm({
|
|
|
3714
3983
|
);
|
|
3715
3984
|
return fromSnakeKeys(raw, "poolId");
|
|
3716
3985
|
}
|
|
3986
|
+
/** List all sandbox pools in the namespace. */
|
|
3717
3987
|
async listPools() {
|
|
3718
3988
|
const raw = await this.http.requestJson(
|
|
3719
3989
|
"GET",
|
|
@@ -3723,6 +3993,7 @@ var init_client = __esm({
|
|
|
3723
3993
|
(p) => fromSnakeKeys(p, "poolId")
|
|
3724
3994
|
);
|
|
3725
3995
|
}
|
|
3996
|
+
/** Replace the configuration of an existing sandbox pool. */
|
|
3726
3997
|
async updatePool(poolId, options) {
|
|
3727
3998
|
const body = {
|
|
3728
3999
|
image: options.image,
|
|
@@ -3744,6 +4015,7 @@ var init_client = __esm({
|
|
|
3744
4015
|
);
|
|
3745
4016
|
return fromSnakeKeys(raw, "poolId");
|
|
3746
4017
|
}
|
|
4018
|
+
/** Delete a sandbox pool. Fails if the pool has active containers. */
|
|
3747
4019
|
async deletePool(poolId) {
|
|
3748
4020
|
await this.http.requestJson(
|
|
3749
4021
|
"DELETE",
|
|
@@ -3751,6 +4023,7 @@ var init_client = __esm({
|
|
|
3751
4023
|
);
|
|
3752
4024
|
}
|
|
3753
4025
|
// --- Connect ---
|
|
4026
|
+
/** Return a `Sandbox` handle for an existing running sandbox without verifying it exists. */
|
|
3754
4027
|
connect(identifier, proxyUrl, routingHint) {
|
|
3755
4028
|
const resolvedProxy = proxyUrl ?? resolveProxyUrl(this.apiUrl);
|
|
3756
4029
|
return new Sandbox({
|
|
@@ -3762,6 +4035,15 @@ var init_client = __esm({
|
|
|
3762
4035
|
routingHint
|
|
3763
4036
|
});
|
|
3764
4037
|
}
|
|
4038
|
+
/**
|
|
4039
|
+
* Create a sandbox, wait for it to reach `Running`, and return a connected handle.
|
|
4040
|
+
*
|
|
4041
|
+
* Blocks until the sandbox is ready or `startupTimeout` elapses. The returned
|
|
4042
|
+
* `Sandbox` auto-terminates when `terminate()` is called.
|
|
4043
|
+
*
|
|
4044
|
+
* @param options.startupTimeout - Max seconds to wait for `Running` status (default 60).
|
|
4045
|
+
* @throws {SandboxError} If the sandbox terminates during startup or the timeout elapses.
|
|
4046
|
+
*/
|
|
3765
4047
|
async createAndConnect(options) {
|
|
3766
4048
|
const startupTimeout = options?.startupTimeout ?? 60;
|
|
3767
4049
|
let result;
|
|
@@ -3773,6 +4055,7 @@ var init_client = __esm({
|
|
|
3773
4055
|
if (result.status === "running" /* RUNNING */) {
|
|
3774
4056
|
const sandbox = this.connect(result.sandboxId, options?.proxyUrl, result.routingHint);
|
|
3775
4057
|
sandbox._setOwner(this);
|
|
4058
|
+
sandbox.traceId = result.traceId;
|
|
3776
4059
|
return sandbox;
|
|
3777
4060
|
}
|
|
3778
4061
|
const deadline = Date.now() + startupTimeout * 1e3;
|
|
@@ -3781,6 +4064,7 @@ var init_client = __esm({
|
|
|
3781
4064
|
if (info.status === "running" /* RUNNING */) {
|
|
3782
4065
|
const sandbox = this.connect(result.sandboxId, options?.proxyUrl, info.routingHint);
|
|
3783
4066
|
sandbox._setOwner(this);
|
|
4067
|
+
sandbox.traceId = result.traceId;
|
|
3784
4068
|
return sandbox;
|
|
3785
4069
|
}
|
|
3786
4070
|
if (info.status === "terminated" /* TERMINATED */) {
|