tensorlake 0.4.50 → 0.5.1
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 +388 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +210 -12
- package/dist/index.d.ts +210 -12
- package/dist/index.js +388 -49
- package/dist/index.js.map +1 -1
- package/dist/{sandbox-image-C79FXqQk.d.cts → sandbox-image-CEGsGg4V.d.cts} +22 -2
- package/dist/{sandbox-image-C79FXqQk.d.ts → sandbox-image-CEGsGg4V.d.ts} +22 -2
- package/dist/sandbox-image.cjs +375 -36
- 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 +380 -36
- package/dist/sandbox-image.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -31,10 +31,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
33
|
// src/defaults.ts
|
|
34
|
-
var API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TIMEOUT_MS, MAX_RETRIES, RETRY_BACKOFF_MS, RETRYABLE_STATUS_CODES;
|
|
34
|
+
var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TIMEOUT_MS, MAX_RETRIES, RETRY_BACKOFF_MS, RETRYABLE_STATUS_CODES;
|
|
35
35
|
var init_defaults = __esm({
|
|
36
36
|
"src/defaults.ts"() {
|
|
37
37
|
"use strict";
|
|
38
|
+
SDK_VERSION = "0.4.49";
|
|
38
39
|
API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
|
|
39
40
|
API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
|
|
40
41
|
NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
|
|
@@ -132,6 +133,12 @@ var init_errors = __esm({
|
|
|
132
133
|
});
|
|
133
134
|
|
|
134
135
|
// src/http.ts
|
|
136
|
+
function withTraceId(value, traceId) {
|
|
137
|
+
if (value == null) {
|
|
138
|
+
return { traceId };
|
|
139
|
+
}
|
|
140
|
+
return Object.assign(value, { traceId });
|
|
141
|
+
}
|
|
135
142
|
function hasHeader(headers, name) {
|
|
136
143
|
const lowered = name.toLowerCase();
|
|
137
144
|
return Object.keys(headers).some((key) => key.toLowerCase() === lowered);
|
|
@@ -201,7 +208,7 @@ var init_http = __esm({
|
|
|
201
208
|
allowH2: true
|
|
202
209
|
})
|
|
203
210
|
);
|
|
204
|
-
HttpClient = class {
|
|
211
|
+
HttpClient = class _HttpClient {
|
|
205
212
|
baseUrl;
|
|
206
213
|
headers;
|
|
207
214
|
maxRetries;
|
|
@@ -214,7 +221,9 @@ var init_http = __esm({
|
|
|
214
221
|
this.maxRetries = options.maxRetries ?? MAX_RETRIES;
|
|
215
222
|
this.retryBackoffMs = options.retryBackoffMs ?? RETRY_BACKOFF_MS;
|
|
216
223
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
|
|
217
|
-
this.headers = {
|
|
224
|
+
this.headers = {
|
|
225
|
+
"User-Agent": `tensorlake-typescript-sdk/${SDK_VERSION}`
|
|
226
|
+
};
|
|
218
227
|
if (options.apiKey) {
|
|
219
228
|
this.headers["Authorization"] = `Bearer ${options.apiKey}`;
|
|
220
229
|
}
|
|
@@ -243,8 +252,8 @@ var init_http = __esm({
|
|
|
243
252
|
signal: options?.signal
|
|
244
253
|
});
|
|
245
254
|
const text = await response.text();
|
|
246
|
-
|
|
247
|
-
return
|
|
255
|
+
const data = text ? JSON.parse(text) : void 0;
|
|
256
|
+
return withTraceId(data, response.traceId);
|
|
248
257
|
}
|
|
249
258
|
/** Make a request returning raw bytes. */
|
|
250
259
|
async requestBytes(method, path2, options) {
|
|
@@ -258,7 +267,7 @@ var init_http = __esm({
|
|
|
258
267
|
signal: options?.signal
|
|
259
268
|
});
|
|
260
269
|
const buffer = await response.arrayBuffer();
|
|
261
|
-
return new Uint8Array(buffer);
|
|
270
|
+
return withTraceId(new Uint8Array(buffer), response.traceId);
|
|
262
271
|
}
|
|
263
272
|
/** Make a request and return the response body as an SSE stream. */
|
|
264
273
|
async requestStream(method, path2, options) {
|
|
@@ -273,7 +282,7 @@ var init_http = __esm({
|
|
|
273
282
|
"No response body for SSE stream"
|
|
274
283
|
);
|
|
275
284
|
}
|
|
276
|
-
return response.body;
|
|
285
|
+
return withTraceId(response.body, response.traceId);
|
|
277
286
|
}
|
|
278
287
|
/** Make a request and return the raw Response. */
|
|
279
288
|
async requestResponse(method, path2, options) {
|
|
@@ -286,7 +295,7 @@ var init_http = __esm({
|
|
|
286
295
|
headers["Content-Type"] = "application/json";
|
|
287
296
|
}
|
|
288
297
|
const body = hasJsonBody ? JSON.stringify(options?.json) : normalizeRequestBody(options?.body);
|
|
289
|
-
|
|
298
|
+
const { response, traceId } = await this.doFetch(
|
|
290
299
|
method,
|
|
291
300
|
path2,
|
|
292
301
|
body,
|
|
@@ -294,9 +303,18 @@ var init_http = __esm({
|
|
|
294
303
|
options?.signal,
|
|
295
304
|
options?.allowHttpErrors ?? false
|
|
296
305
|
);
|
|
306
|
+
return withTraceId(response, traceId);
|
|
307
|
+
}
|
|
308
|
+
static makeTraceparent() {
|
|
309
|
+
const randomHex = (bytes) => Array.from(crypto.getRandomValues(new Uint8Array(bytes))).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
310
|
+
const traceId = randomHex(16);
|
|
311
|
+
const spanId = randomHex(8);
|
|
312
|
+
return { traceparent: `00-${traceId}-${spanId}-01`, traceId };
|
|
297
313
|
}
|
|
298
314
|
async doFetch(method, path2, body, headers, signal, allowHttpErrors = false) {
|
|
299
315
|
const url = `${this.baseUrl}${path2}`;
|
|
316
|
+
const { traceparent, traceId } = _HttpClient.makeTraceparent();
|
|
317
|
+
headers["traceparent"] = traceparent;
|
|
300
318
|
let lastError;
|
|
301
319
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
302
320
|
if (attempt > 0) {
|
|
@@ -317,7 +335,7 @@ var init_http = __esm({
|
|
|
317
335
|
signal: combinedSignal
|
|
318
336
|
});
|
|
319
337
|
clearTimeout(timeoutId);
|
|
320
|
-
if (response.ok) return response;
|
|
338
|
+
if (response.ok) return { response, traceId };
|
|
321
339
|
if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < this.maxRetries) {
|
|
322
340
|
lastError = new RemoteAPIError(
|
|
323
341
|
response.status,
|
|
@@ -326,7 +344,7 @@ var init_http = __esm({
|
|
|
326
344
|
continue;
|
|
327
345
|
}
|
|
328
346
|
if (allowHttpErrors) {
|
|
329
|
-
return response;
|
|
347
|
+
return { response, traceId };
|
|
330
348
|
}
|
|
331
349
|
const errorBody = await response.text().catch(() => "");
|
|
332
350
|
throwMappedError(response.status, errorBody, path2);
|
|
@@ -396,20 +414,20 @@ var SandboxStatus, SnapshotStatus, ProcessStatus, StdinMode, OutputMode, Contain
|
|
|
396
414
|
var init_models = __esm({
|
|
397
415
|
"src/models.ts"() {
|
|
398
416
|
"use strict";
|
|
399
|
-
SandboxStatus = /* @__PURE__ */ ((
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
return
|
|
417
|
+
SandboxStatus = /* @__PURE__ */ ((SandboxStatus3) => {
|
|
418
|
+
SandboxStatus3["PENDING"] = "pending";
|
|
419
|
+
SandboxStatus3["RUNNING"] = "running";
|
|
420
|
+
SandboxStatus3["SNAPSHOTTING"] = "snapshotting";
|
|
421
|
+
SandboxStatus3["SUSPENDING"] = "suspending";
|
|
422
|
+
SandboxStatus3["SUSPENDED"] = "suspended";
|
|
423
|
+
SandboxStatus3["TERMINATED"] = "terminated";
|
|
424
|
+
return SandboxStatus3;
|
|
407
425
|
})(SandboxStatus || {});
|
|
408
|
-
SnapshotStatus = /* @__PURE__ */ ((
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
return
|
|
426
|
+
SnapshotStatus = /* @__PURE__ */ ((SnapshotStatus3) => {
|
|
427
|
+
SnapshotStatus3["IN_PROGRESS"] = "in_progress";
|
|
428
|
+
SnapshotStatus3["COMPLETED"] = "completed";
|
|
429
|
+
SnapshotStatus3["FAILED"] = "failed";
|
|
430
|
+
return SnapshotStatus3;
|
|
413
431
|
})(SnapshotStatus || {});
|
|
414
432
|
ProcessStatus = /* @__PURE__ */ ((ProcessStatus2) => {
|
|
415
433
|
ProcessStatus2["RUNNING"] = "running";
|
|
@@ -3158,13 +3176,17 @@ var init_sandbox = __esm({
|
|
|
3158
3176
|
};
|
|
3159
3177
|
Sandbox = class {
|
|
3160
3178
|
sandboxId;
|
|
3179
|
+
traceId = null;
|
|
3161
3180
|
http;
|
|
3162
3181
|
baseUrl;
|
|
3163
3182
|
wsHeaders;
|
|
3164
3183
|
ownsSandbox = false;
|
|
3165
3184
|
lifecycleClient = null;
|
|
3185
|
+
lifecycleIdentifier;
|
|
3186
|
+
sandboxName = null;
|
|
3166
3187
|
constructor(options) {
|
|
3167
3188
|
this.sandboxId = options.sandboxId;
|
|
3189
|
+
this.lifecycleIdentifier = options.sandboxId;
|
|
3168
3190
|
const proxyUrl = options.proxyUrl ?? SANDBOX_PROXY_URL;
|
|
3169
3191
|
const { baseUrl, hostHeader } = resolveProxyTarget(proxyUrl, options.sandboxId);
|
|
3170
3192
|
this.baseUrl = baseUrl;
|
|
@@ -3190,21 +3212,182 @@ var init_sandbox = __esm({
|
|
|
3190
3212
|
routingHint: options.routingHint
|
|
3191
3213
|
});
|
|
3192
3214
|
}
|
|
3215
|
+
get name() {
|
|
3216
|
+
return this.sandboxName;
|
|
3217
|
+
}
|
|
3218
|
+
/** @internal Used by client wiring to keep locally cached name in sync. */
|
|
3219
|
+
_setName(name) {
|
|
3220
|
+
this.sandboxName = name;
|
|
3221
|
+
}
|
|
3222
|
+
/** @internal Used by lifecycle operations to pin to canonical sandbox ID. */
|
|
3223
|
+
_setLifecycleIdentifier(identifier) {
|
|
3224
|
+
this.lifecycleIdentifier = identifier;
|
|
3225
|
+
}
|
|
3193
3226
|
/** @internal Used by SandboxClient.createAndConnect to set ownership. */
|
|
3194
3227
|
_setOwner(client) {
|
|
3195
3228
|
this.ownsSandbox = true;
|
|
3196
3229
|
this.lifecycleClient = client;
|
|
3197
3230
|
}
|
|
3231
|
+
// --- Static factory methods ---
|
|
3232
|
+
/**
|
|
3233
|
+
* Create a new sandbox and return a connected, running handle.
|
|
3234
|
+
*
|
|
3235
|
+
* Covers both fresh sandbox creation and restore-from-snapshot (set
|
|
3236
|
+
* `snapshotId`). Blocks until the sandbox is `Running`.
|
|
3237
|
+
*/
|
|
3238
|
+
static async create(options) {
|
|
3239
|
+
const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3240
|
+
const client = new SandboxClient2(
|
|
3241
|
+
options,
|
|
3242
|
+
/* _internal */
|
|
3243
|
+
true
|
|
3244
|
+
);
|
|
3245
|
+
const sandbox = await client.createAndConnect(options);
|
|
3246
|
+
sandbox.lifecycleClient = client;
|
|
3247
|
+
return sandbox;
|
|
3248
|
+
}
|
|
3249
|
+
/**
|
|
3250
|
+
* Attach to an existing sandbox and return a connected handle.
|
|
3251
|
+
*
|
|
3252
|
+
* Verifies the sandbox exists via a server GET call, then returns a handle
|
|
3253
|
+
* in whatever state the sandbox is in. Does **not** auto-resume a suspended
|
|
3254
|
+
* sandbox — call `sandbox.resume()` explicitly.
|
|
3255
|
+
*/
|
|
3256
|
+
static async connect(options) {
|
|
3257
|
+
const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3258
|
+
const client = new SandboxClient2(
|
|
3259
|
+
options,
|
|
3260
|
+
/* _internal */
|
|
3261
|
+
true
|
|
3262
|
+
);
|
|
3263
|
+
const info = await client.get(options.sandboxId);
|
|
3264
|
+
const sandbox = client.connect(
|
|
3265
|
+
info.sandboxId,
|
|
3266
|
+
options.proxyUrl,
|
|
3267
|
+
options.routingHint ?? info.routingHint
|
|
3268
|
+
);
|
|
3269
|
+
sandbox.lifecycleClient = client;
|
|
3270
|
+
sandbox._setLifecycleIdentifier(info.sandboxId);
|
|
3271
|
+
sandbox._setName(info.name ?? null);
|
|
3272
|
+
return sandbox;
|
|
3273
|
+
}
|
|
3274
|
+
// --- Static snapshot management ---
|
|
3275
|
+
/** Get information about a snapshot by ID. No sandbox handle needed. */
|
|
3276
|
+
static async getSnapshot(snapshotId, options) {
|
|
3277
|
+
const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3278
|
+
const client = new SandboxClient2(
|
|
3279
|
+
options,
|
|
3280
|
+
/* _internal */
|
|
3281
|
+
true
|
|
3282
|
+
);
|
|
3283
|
+
return client.getSnapshot(snapshotId);
|
|
3284
|
+
}
|
|
3285
|
+
/** Delete a snapshot by ID. No sandbox handle needed. */
|
|
3286
|
+
static async deleteSnapshot(snapshotId, options) {
|
|
3287
|
+
const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3288
|
+
const client = new SandboxClient2(
|
|
3289
|
+
options,
|
|
3290
|
+
/* _internal */
|
|
3291
|
+
true
|
|
3292
|
+
);
|
|
3293
|
+
await client.deleteSnapshot(snapshotId);
|
|
3294
|
+
}
|
|
3295
|
+
// --- Instance lifecycle methods ---
|
|
3296
|
+
requireLifecycleClient(operation) {
|
|
3297
|
+
if (!this.lifecycleClient) {
|
|
3298
|
+
throw new SandboxError(
|
|
3299
|
+
`Cannot ${operation}: no lifecycle client available. Use Sandbox.create() or Sandbox.connect() to get a lifecycle-aware handle.`
|
|
3300
|
+
);
|
|
3301
|
+
}
|
|
3302
|
+
return this.lifecycleClient;
|
|
3303
|
+
}
|
|
3304
|
+
/**
|
|
3305
|
+
* Fetch the current sandbox status from the server.
|
|
3306
|
+
*
|
|
3307
|
+
* Always hits the network — the value is not cached locally because the
|
|
3308
|
+
* status changes over the sandbox's lifecycle.
|
|
3309
|
+
*/
|
|
3310
|
+
async status() {
|
|
3311
|
+
const client = this.requireLifecycleClient("read_status");
|
|
3312
|
+
const info = await client.get(this.lifecycleIdentifier);
|
|
3313
|
+
this._setLifecycleIdentifier(info.sandboxId);
|
|
3314
|
+
this._setName(info.name ?? null);
|
|
3315
|
+
return info.status;
|
|
3316
|
+
}
|
|
3317
|
+
/**
|
|
3318
|
+
* Update this sandbox's properties (name, exposed ports, proxy auth).
|
|
3319
|
+
*
|
|
3320
|
+
* Naming an ephemeral sandbox makes it non-ephemeral and enables
|
|
3321
|
+
* suspend/resume.
|
|
3322
|
+
*/
|
|
3323
|
+
async update(options) {
|
|
3324
|
+
const client = this.requireLifecycleClient("update");
|
|
3325
|
+
const info = await client.update(this.lifecycleIdentifier, options);
|
|
3326
|
+
this._setLifecycleIdentifier(info.sandboxId);
|
|
3327
|
+
this._setName(info.name ?? null);
|
|
3328
|
+
return info;
|
|
3329
|
+
}
|
|
3330
|
+
/**
|
|
3331
|
+
* Suspend this sandbox.
|
|
3332
|
+
*
|
|
3333
|
+
* By default blocks until the sandbox is fully `Suspended`. Pass
|
|
3334
|
+
* `{ wait: false }` for fire-and-return.
|
|
3335
|
+
*/
|
|
3336
|
+
async suspend(options) {
|
|
3337
|
+
const client = this.requireLifecycleClient("suspend");
|
|
3338
|
+
await client.suspend(this.lifecycleIdentifier, options);
|
|
3339
|
+
}
|
|
3340
|
+
/**
|
|
3341
|
+
* Resume this sandbox.
|
|
3342
|
+
*
|
|
3343
|
+
* By default blocks until the sandbox is `Running` and routable. Pass
|
|
3344
|
+
* `{ wait: false }` for fire-and-return.
|
|
3345
|
+
*/
|
|
3346
|
+
async resume(options) {
|
|
3347
|
+
const client = this.requireLifecycleClient("resume");
|
|
3348
|
+
await client.resume(this.lifecycleIdentifier, options);
|
|
3349
|
+
}
|
|
3350
|
+
/**
|
|
3351
|
+
* Create a snapshot of this sandbox's filesystem and wait for it to
|
|
3352
|
+
* be committed.
|
|
3353
|
+
*
|
|
3354
|
+
* By default blocks until the snapshot artifact is ready and returns
|
|
3355
|
+
* the completed `SnapshotInfo`. Pass `{ wait: false }` to fire-and-return
|
|
3356
|
+
* (returns `undefined`).
|
|
3357
|
+
*/
|
|
3358
|
+
async checkpoint(options) {
|
|
3359
|
+
const client = this.requireLifecycleClient("checkpoint");
|
|
3360
|
+
if (options?.wait === false) {
|
|
3361
|
+
await client.snapshot(this.lifecycleIdentifier, { contentMode: options.contentMode });
|
|
3362
|
+
return void 0;
|
|
3363
|
+
}
|
|
3364
|
+
return client.snapshotAndWait(this.lifecycleIdentifier, {
|
|
3365
|
+
timeout: options?.timeout,
|
|
3366
|
+
pollInterval: options?.pollInterval,
|
|
3367
|
+
contentMode: options?.contentMode
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
/**
|
|
3371
|
+
* List snapshots taken from this sandbox.
|
|
3372
|
+
*/
|
|
3373
|
+
async listSnapshots() {
|
|
3374
|
+
const client = this.requireLifecycleClient("listSnapshots");
|
|
3375
|
+
const all = await client.listSnapshots();
|
|
3376
|
+
const filtered = all.filter((s) => s.sandboxId === this.lifecycleIdentifier);
|
|
3377
|
+
return Object.assign(filtered, { traceId: all.traceId });
|
|
3378
|
+
}
|
|
3379
|
+
/** Close the HTTP client. The sandbox keeps running. */
|
|
3198
3380
|
close() {
|
|
3199
3381
|
this.http.close();
|
|
3200
3382
|
}
|
|
3383
|
+
/** Terminate the sandbox and release all resources. */
|
|
3201
3384
|
async terminate() {
|
|
3202
3385
|
const client = this.lifecycleClient;
|
|
3203
3386
|
this.ownsSandbox = false;
|
|
3204
3387
|
this.lifecycleClient = null;
|
|
3205
3388
|
this.close();
|
|
3206
3389
|
if (client) {
|
|
3207
|
-
await client.delete(this.
|
|
3390
|
+
await client.delete(this.lifecycleIdentifier);
|
|
3208
3391
|
}
|
|
3209
3392
|
}
|
|
3210
3393
|
// --- High-level convenience ---
|
|
@@ -3250,6 +3433,14 @@ var init_sandbox = __esm({
|
|
|
3250
3433
|
};
|
|
3251
3434
|
}
|
|
3252
3435
|
// --- Process management ---
|
|
3436
|
+
/**
|
|
3437
|
+
* Start a process in the sandbox without waiting for it to exit.
|
|
3438
|
+
*
|
|
3439
|
+
* Returns a `ProcessInfo` with the assigned `pid`. Use `getProcess()` to
|
|
3440
|
+
* poll status, or `followStdout()` / `followOutput()` to stream output
|
|
3441
|
+
* until the process exits. Use `run()` instead to block until completion
|
|
3442
|
+
* and get combined output in one call.
|
|
3443
|
+
*/
|
|
3253
3444
|
async startProcess(command, options) {
|
|
3254
3445
|
const payload = { command };
|
|
3255
3446
|
if (options?.args != null) payload.args = options.args;
|
|
@@ -3271,13 +3462,16 @@ var init_sandbox = __esm({
|
|
|
3271
3462
|
);
|
|
3272
3463
|
return fromSnakeKeys(raw);
|
|
3273
3464
|
}
|
|
3465
|
+
/** List all processes (running and exited) tracked by the sandbox daemon. */
|
|
3274
3466
|
async listProcesses() {
|
|
3275
3467
|
const raw = await this.http.requestJson(
|
|
3276
3468
|
"GET",
|
|
3277
3469
|
"/api/v1/processes"
|
|
3278
3470
|
);
|
|
3279
|
-
|
|
3471
|
+
const processes = (raw.processes ?? []).map((p) => fromSnakeKeys(p));
|
|
3472
|
+
return Object.assign(processes, { traceId: raw.traceId });
|
|
3280
3473
|
}
|
|
3474
|
+
/** Get current status and metadata for a process by PID. */
|
|
3281
3475
|
async getProcess(pid) {
|
|
3282
3476
|
const raw = await this.http.requestJson(
|
|
3283
3477
|
"GET",
|
|
@@ -3285,9 +3479,11 @@ var init_sandbox = __esm({
|
|
|
3285
3479
|
);
|
|
3286
3480
|
return fromSnakeKeys(raw);
|
|
3287
3481
|
}
|
|
3482
|
+
/** Send SIGKILL to a process. */
|
|
3288
3483
|
async killProcess(pid) {
|
|
3289
3484
|
await this.http.requestJson("DELETE", `/api/v1/processes/${pid}`);
|
|
3290
3485
|
}
|
|
3486
|
+
/** Send an arbitrary signal to a process (e.g. `15` for SIGTERM, `9` for SIGKILL). */
|
|
3291
3487
|
async sendSignal(pid, signal) {
|
|
3292
3488
|
const raw = await this.http.requestJson(
|
|
3293
3489
|
"POST",
|
|
@@ -3297,15 +3493,18 @@ var init_sandbox = __esm({
|
|
|
3297
3493
|
return fromSnakeKeys(raw);
|
|
3298
3494
|
}
|
|
3299
3495
|
// --- Process I/O ---
|
|
3496
|
+
/** Write bytes to a process's stdin. The process must have been started with `stdinMode: StdinMode.PIPE`. */
|
|
3300
3497
|
async writeStdin(pid, data) {
|
|
3301
3498
|
await this.http.requestBytes("POST", `/api/v1/processes/${pid}/stdin`, {
|
|
3302
3499
|
body: data,
|
|
3303
3500
|
contentType: "application/octet-stream"
|
|
3304
3501
|
});
|
|
3305
3502
|
}
|
|
3503
|
+
/** Close a process's stdin pipe, signalling EOF to the process. */
|
|
3306
3504
|
async closeStdin(pid) {
|
|
3307
3505
|
await this.http.requestJson("POST", `/api/v1/processes/${pid}/stdin/close`);
|
|
3308
3506
|
}
|
|
3507
|
+
/** Return all captured stdout lines produced so far by a process. */
|
|
3309
3508
|
async getStdout(pid) {
|
|
3310
3509
|
const raw = await this.http.requestJson(
|
|
3311
3510
|
"GET",
|
|
@@ -3313,6 +3512,7 @@ var init_sandbox = __esm({
|
|
|
3313
3512
|
);
|
|
3314
3513
|
return fromSnakeKeys(raw);
|
|
3315
3514
|
}
|
|
3515
|
+
/** Return all captured stderr lines produced so far by a process. */
|
|
3316
3516
|
async getStderr(pid) {
|
|
3317
3517
|
const raw = await this.http.requestJson(
|
|
3318
3518
|
"GET",
|
|
@@ -3320,6 +3520,7 @@ var init_sandbox = __esm({
|
|
|
3320
3520
|
);
|
|
3321
3521
|
return fromSnakeKeys(raw);
|
|
3322
3522
|
}
|
|
3523
|
+
/** Return all captured stdout+stderr lines produced so far by a process. */
|
|
3323
3524
|
async getOutput(pid) {
|
|
3324
3525
|
const raw = await this.http.requestJson(
|
|
3325
3526
|
"GET",
|
|
@@ -3328,6 +3529,7 @@ var init_sandbox = __esm({
|
|
|
3328
3529
|
return fromSnakeKeys(raw);
|
|
3329
3530
|
}
|
|
3330
3531
|
// --- Streaming (SSE) ---
|
|
3532
|
+
/** Stream stdout events from a process until it exits. Yields one `OutputEvent` per line. */
|
|
3331
3533
|
async *followStdout(pid, options) {
|
|
3332
3534
|
const stream = await this.http.requestStream(
|
|
3333
3535
|
"GET",
|
|
@@ -3341,6 +3543,7 @@ var init_sandbox = __esm({
|
|
|
3341
3543
|
yield fromSnakeKeys(raw);
|
|
3342
3544
|
}
|
|
3343
3545
|
}
|
|
3546
|
+
/** Stream stderr events from a process until it exits. Yields one `OutputEvent` per line. */
|
|
3344
3547
|
async *followStderr(pid, options) {
|
|
3345
3548
|
const stream = await this.http.requestStream(
|
|
3346
3549
|
"GET",
|
|
@@ -3354,6 +3557,7 @@ var init_sandbox = __esm({
|
|
|
3354
3557
|
yield fromSnakeKeys(raw);
|
|
3355
3558
|
}
|
|
3356
3559
|
}
|
|
3560
|
+
/** Stream combined stdout+stderr events from a process until it exits. Yields one `OutputEvent` per line. */
|
|
3357
3561
|
async *followOutput(pid, options) {
|
|
3358
3562
|
const stream = await this.http.requestStream(
|
|
3359
3563
|
"GET",
|
|
@@ -3368,12 +3572,14 @@ var init_sandbox = __esm({
|
|
|
3368
3572
|
}
|
|
3369
3573
|
}
|
|
3370
3574
|
// --- File operations ---
|
|
3575
|
+
/** Read a file from the sandbox and return its raw bytes. */
|
|
3371
3576
|
async readFile(path2) {
|
|
3372
3577
|
return this.http.requestBytes(
|
|
3373
3578
|
"GET",
|
|
3374
3579
|
`/api/v1/files?path=${encodeURIComponent(path2)}`
|
|
3375
3580
|
);
|
|
3376
3581
|
}
|
|
3582
|
+
/** Write raw bytes to a file in the sandbox, creating it if it does not exist. */
|
|
3377
3583
|
async writeFile(path2, content) {
|
|
3378
3584
|
await this.http.requestBytes(
|
|
3379
3585
|
"PUT",
|
|
@@ -3381,12 +3587,14 @@ var init_sandbox = __esm({
|
|
|
3381
3587
|
{ body: content, contentType: "application/octet-stream" }
|
|
3382
3588
|
);
|
|
3383
3589
|
}
|
|
3590
|
+
/** Delete a file from the sandbox. */
|
|
3384
3591
|
async deleteFile(path2) {
|
|
3385
3592
|
await this.http.requestJson(
|
|
3386
3593
|
"DELETE",
|
|
3387
3594
|
`/api/v1/files?path=${encodeURIComponent(path2)}`
|
|
3388
3595
|
);
|
|
3389
3596
|
}
|
|
3597
|
+
/** List the contents of a directory in the sandbox. */
|
|
3390
3598
|
async listDirectory(path2) {
|
|
3391
3599
|
const raw = await this.http.requestJson(
|
|
3392
3600
|
"GET",
|
|
@@ -3395,6 +3603,7 @@ var init_sandbox = __esm({
|
|
|
3395
3603
|
return fromSnakeKeys(raw);
|
|
3396
3604
|
}
|
|
3397
3605
|
// --- PTY ---
|
|
3606
|
+
/** Create an interactive PTY session. Returns a `sessionId` and `token` for WebSocket connection via `connectPty()`. */
|
|
3398
3607
|
async createPtySession(options) {
|
|
3399
3608
|
const payload = {
|
|
3400
3609
|
command: options.command,
|
|
@@ -3411,6 +3620,7 @@ var init_sandbox = __esm({
|
|
|
3411
3620
|
);
|
|
3412
3621
|
return fromSnakeKeys(raw);
|
|
3413
3622
|
}
|
|
3623
|
+
/** Create a PTY session and connect to it immediately. Cleans up the session if the WebSocket connection fails. */
|
|
3414
3624
|
async createPty(options) {
|
|
3415
3625
|
const { onData, onExit, ...createOptions } = options;
|
|
3416
3626
|
const session = await this.createPtySession(createOptions);
|
|
@@ -3424,6 +3634,7 @@ var init_sandbox = __esm({
|
|
|
3424
3634
|
throw error;
|
|
3425
3635
|
}
|
|
3426
3636
|
}
|
|
3637
|
+
/** Attach to an existing PTY session by ID and token and return a connected `Pty` handle. */
|
|
3427
3638
|
async connectPty(sessionId, token, options) {
|
|
3428
3639
|
const wsUrl = new URL(this.ptyWsUrl(sessionId, token));
|
|
3429
3640
|
const authToken = wsUrl.searchParams.get("token") ?? token;
|
|
@@ -3448,6 +3659,7 @@ var init_sandbox = __esm({
|
|
|
3448
3659
|
await pty.connect();
|
|
3449
3660
|
return pty;
|
|
3450
3661
|
}
|
|
3662
|
+
/** Open a TCP tunnel to a port inside the sandbox and return the local listener. */
|
|
3451
3663
|
async createTunnel(remotePort, options) {
|
|
3452
3664
|
return TcpTunnel.listen({
|
|
3453
3665
|
baseUrl: this.baseUrl,
|
|
@@ -3458,6 +3670,7 @@ var init_sandbox = __esm({
|
|
|
3458
3670
|
connectTimeout: options?.connectTimeout
|
|
3459
3671
|
});
|
|
3460
3672
|
}
|
|
3673
|
+
/** Connect to a sandbox VNC session for programmatic desktop control. */
|
|
3461
3674
|
async connectDesktop(options) {
|
|
3462
3675
|
return Desktop.connect({
|
|
3463
3676
|
baseUrl: this.baseUrl,
|
|
@@ -3480,6 +3693,7 @@ var init_sandbox = __esm({
|
|
|
3480
3693
|
return `${wsBase}/api/v1/pty/${sessionId}/ws?token=${token}`;
|
|
3481
3694
|
}
|
|
3482
3695
|
// --- Health ---
|
|
3696
|
+
/** Check the sandbox daemon health. */
|
|
3483
3697
|
async health() {
|
|
3484
3698
|
const raw = await this.http.requestJson(
|
|
3485
3699
|
"GET",
|
|
@@ -3487,6 +3701,7 @@ var init_sandbox = __esm({
|
|
|
3487
3701
|
);
|
|
3488
3702
|
return fromSnakeKeys(raw);
|
|
3489
3703
|
}
|
|
3704
|
+
/** Get sandbox daemon info (version, uptime, process counts). */
|
|
3490
3705
|
async info() {
|
|
3491
3706
|
const raw = await this.http.requestJson(
|
|
3492
3707
|
"GET",
|
|
@@ -3499,6 +3714,10 @@ var init_sandbox = __esm({
|
|
|
3499
3714
|
});
|
|
3500
3715
|
|
|
3501
3716
|
// src/client.ts
|
|
3717
|
+
var client_exports = {};
|
|
3718
|
+
__export(client_exports, {
|
|
3719
|
+
SandboxClient: () => SandboxClient
|
|
3720
|
+
});
|
|
3502
3721
|
function sleep2(ms) {
|
|
3503
3722
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3504
3723
|
}
|
|
@@ -3535,7 +3754,13 @@ var init_client = __esm({
|
|
|
3535
3754
|
projectId;
|
|
3536
3755
|
namespace;
|
|
3537
3756
|
local;
|
|
3538
|
-
|
|
3757
|
+
/** @internal Pass `true` to suppress the deprecation warning when used by `Sandbox.create()` / `Sandbox.connect()`. */
|
|
3758
|
+
constructor(options, _internal = false) {
|
|
3759
|
+
if (!_internal) {
|
|
3760
|
+
console.warn(
|
|
3761
|
+
"[tensorlake] SandboxClient is deprecated; use Sandbox.create() / Sandbox.connect() instead."
|
|
3762
|
+
);
|
|
3763
|
+
}
|
|
3539
3764
|
this.apiUrl = options?.apiUrl ?? API_URL;
|
|
3540
3765
|
this.apiKey = options?.apiKey ?? API_KEY;
|
|
3541
3766
|
this.organizationId = options?.organizationId;
|
|
@@ -3575,12 +3800,13 @@ var init_client = __esm({
|
|
|
3575
3800
|
return lifecyclePath(subpath, this.local, this.namespace);
|
|
3576
3801
|
}
|
|
3577
3802
|
// --- Sandbox CRUD ---
|
|
3803
|
+
/** Create a new sandbox. Returns immediately; the sandbox may still be starting. Use `createAndConnect()` for a blocking, ready-to-use handle. */
|
|
3578
3804
|
async create(options) {
|
|
3579
3805
|
const body = {
|
|
3580
3806
|
resources: {
|
|
3581
3807
|
cpus: options?.cpus ?? 1,
|
|
3582
3808
|
memory_mb: options?.memoryMb ?? 1024,
|
|
3583
|
-
|
|
3809
|
+
...options?.diskMb != null ? { disk_mb: options.diskMb } : {}
|
|
3584
3810
|
}
|
|
3585
3811
|
};
|
|
3586
3812
|
if (options?.image != null) body.image = options.image;
|
|
@@ -3601,8 +3827,10 @@ var init_client = __esm({
|
|
|
3601
3827
|
this.path("sandboxes"),
|
|
3602
3828
|
{ body }
|
|
3603
3829
|
);
|
|
3604
|
-
|
|
3830
|
+
const result = fromSnakeKeys(raw, "sandboxId");
|
|
3831
|
+
return Object.assign(result, { traceId: raw.traceId });
|
|
3605
3832
|
}
|
|
3833
|
+
/** Get current state and metadata for a sandbox by ID. */
|
|
3606
3834
|
async get(sandboxId) {
|
|
3607
3835
|
const raw = await this.http.requestJson(
|
|
3608
3836
|
"GET",
|
|
@@ -3610,15 +3838,18 @@ var init_client = __esm({
|
|
|
3610
3838
|
);
|
|
3611
3839
|
return fromSnakeKeys(raw, "sandboxId");
|
|
3612
3840
|
}
|
|
3841
|
+
/** List all sandboxes in the namespace. */
|
|
3613
3842
|
async list() {
|
|
3614
3843
|
const raw = await this.http.requestJson(
|
|
3615
3844
|
"GET",
|
|
3616
3845
|
this.path("sandboxes")
|
|
3617
3846
|
);
|
|
3618
|
-
|
|
3847
|
+
const sandboxes = (raw.sandboxes ?? []).map(
|
|
3619
3848
|
(s) => fromSnakeKeys(s, "sandboxId")
|
|
3620
3849
|
);
|
|
3850
|
+
return Object.assign(sandboxes, { traceId: raw.traceId });
|
|
3621
3851
|
}
|
|
3852
|
+
/** Update sandbox properties such as name, exposed ports, and proxy auth settings. */
|
|
3622
3853
|
async update(sandboxId, options) {
|
|
3623
3854
|
const body = {};
|
|
3624
3855
|
if (options.name != null) body.name = options.name;
|
|
@@ -3638,6 +3869,7 @@ var init_client = __esm({
|
|
|
3638
3869
|
);
|
|
3639
3870
|
return fromSnakeKeys(raw, "sandboxId");
|
|
3640
3871
|
}
|
|
3872
|
+
/** Get the current proxy port settings for a sandbox. */
|
|
3641
3873
|
async getPortAccess(sandboxId) {
|
|
3642
3874
|
const info = await this.get(sandboxId);
|
|
3643
3875
|
return {
|
|
@@ -3646,6 +3878,7 @@ var init_client = __esm({
|
|
|
3646
3878
|
sandboxUrl: info.sandboxUrl
|
|
3647
3879
|
};
|
|
3648
3880
|
}
|
|
3881
|
+
/** Add one or more user ports to the sandbox proxy allowlist. */
|
|
3649
3882
|
async exposePorts(sandboxId, ports, options) {
|
|
3650
3883
|
const requestedPorts = normalizeUserPorts(ports);
|
|
3651
3884
|
const current = await this.getPortAccess(sandboxId);
|
|
@@ -3658,6 +3891,7 @@ var init_client = __esm({
|
|
|
3658
3891
|
exposedPorts: desiredPorts
|
|
3659
3892
|
});
|
|
3660
3893
|
}
|
|
3894
|
+
/** Remove one or more user ports from the sandbox proxy allowlist. */
|
|
3661
3895
|
async unexposePorts(sandboxId, ports) {
|
|
3662
3896
|
const requestedPorts = normalizeUserPorts(ports);
|
|
3663
3897
|
const current = await this.getPortAccess(sandboxId);
|
|
@@ -3668,32 +3902,98 @@ var init_client = __esm({
|
|
|
3668
3902
|
exposedPorts: desiredPorts
|
|
3669
3903
|
});
|
|
3670
3904
|
}
|
|
3905
|
+
/** Terminate and delete a sandbox. */
|
|
3671
3906
|
async delete(sandboxId) {
|
|
3672
3907
|
await this.http.requestJson(
|
|
3673
3908
|
"DELETE",
|
|
3674
3909
|
this.path(`sandboxes/${sandboxId}`)
|
|
3675
3910
|
);
|
|
3676
3911
|
}
|
|
3677
|
-
|
|
3912
|
+
/**
|
|
3913
|
+
* Suspend a named sandbox, preserving its state for later resume.
|
|
3914
|
+
*
|
|
3915
|
+
* Only sandboxes created with a `name` can be suspended; ephemeral sandboxes
|
|
3916
|
+
* cannot. By default blocks until the sandbox is fully `Suspended`. Pass
|
|
3917
|
+
* `{ wait: false }` to return immediately after the request is sent
|
|
3918
|
+
* (fire-and-return); the server processes the suspend asynchronously.
|
|
3919
|
+
*
|
|
3920
|
+
* @param sandboxId - ID or name of the sandbox.
|
|
3921
|
+
* @param options.wait - If `true` (default), poll until `Suspended`. Pass `false` to fire-and-return.
|
|
3922
|
+
* @param options.timeout - Max seconds to wait when `wait=true` (default 300).
|
|
3923
|
+
* @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
|
|
3924
|
+
* @throws {SandboxError} If `wait=true` and the sandbox does not reach `Suspended` within `timeout`.
|
|
3925
|
+
*/
|
|
3926
|
+
async suspend(sandboxId, options) {
|
|
3678
3927
|
await this.http.requestResponse(
|
|
3679
3928
|
"POST",
|
|
3680
3929
|
this.path(`sandboxes/${sandboxId}/suspend`)
|
|
3681
3930
|
);
|
|
3931
|
+
if (options?.wait === false) return;
|
|
3932
|
+
const timeout = options?.timeout ?? 300;
|
|
3933
|
+
const pollInterval = options?.pollInterval ?? 1;
|
|
3934
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
3935
|
+
while (Date.now() < deadline) {
|
|
3936
|
+
const info = await this.get(sandboxId);
|
|
3937
|
+
if (info.status === "suspended" /* SUSPENDED */) return;
|
|
3938
|
+
if (info.status === "terminated" /* TERMINATED */) {
|
|
3939
|
+
throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for suspend`);
|
|
3940
|
+
}
|
|
3941
|
+
await sleep2(pollInterval * 1e3);
|
|
3942
|
+
}
|
|
3943
|
+
throw new SandboxError(`Sandbox ${sandboxId} did not suspend within ${timeout}s`);
|
|
3682
3944
|
}
|
|
3683
|
-
|
|
3945
|
+
/**
|
|
3946
|
+
* Resume a suspended sandbox and bring it back to `Running`.
|
|
3947
|
+
*
|
|
3948
|
+
* By default blocks until the sandbox is `Running` and routable. Pass
|
|
3949
|
+
* `{ wait: false }` to return immediately after the request is sent
|
|
3950
|
+
* (fire-and-return); the server processes the resume asynchronously.
|
|
3951
|
+
*
|
|
3952
|
+
* @param sandboxId - ID or name of the sandbox.
|
|
3953
|
+
* @param options.wait - If `true` (default), poll until `Running`. Pass `false` to fire-and-return.
|
|
3954
|
+
* @param options.timeout - Max seconds to wait when `wait=true` (default 300).
|
|
3955
|
+
* @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
|
|
3956
|
+
* @throws {SandboxError} If `wait=true` and the sandbox does not reach `Running` within `timeout`.
|
|
3957
|
+
*/
|
|
3958
|
+
async resume(sandboxId, options) {
|
|
3684
3959
|
await this.http.requestResponse(
|
|
3685
3960
|
"POST",
|
|
3686
3961
|
this.path(`sandboxes/${sandboxId}/resume`)
|
|
3687
3962
|
);
|
|
3963
|
+
if (options?.wait === false) return;
|
|
3964
|
+
const timeout = options?.timeout ?? 300;
|
|
3965
|
+
const pollInterval = options?.pollInterval ?? 1;
|
|
3966
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
3967
|
+
while (Date.now() < deadline) {
|
|
3968
|
+
const info = await this.get(sandboxId);
|
|
3969
|
+
if (info.status === "running" /* RUNNING */) return;
|
|
3970
|
+
if (info.status === "terminated" /* TERMINATED */) {
|
|
3971
|
+
throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for resume`);
|
|
3972
|
+
}
|
|
3973
|
+
await sleep2(pollInterval * 1e3);
|
|
3974
|
+
}
|
|
3975
|
+
throw new SandboxError(`Sandbox ${sandboxId} did not resume within ${timeout}s`);
|
|
3688
3976
|
}
|
|
3977
|
+
/** Claim a warm sandbox from a pool, creating one if no warm containers are available. */
|
|
3689
3978
|
async claim(poolId) {
|
|
3690
3979
|
const raw = await this.http.requestJson(
|
|
3691
3980
|
"POST",
|
|
3692
3981
|
this.path(`sandbox-pools/${poolId}/sandboxes`)
|
|
3693
3982
|
);
|
|
3694
|
-
|
|
3983
|
+
const result = fromSnakeKeys(raw, "sandboxId");
|
|
3984
|
+
return Object.assign(result, { traceId: raw.traceId });
|
|
3695
3985
|
}
|
|
3696
3986
|
// --- Snapshots ---
|
|
3987
|
+
/**
|
|
3988
|
+
* Request a snapshot of a running sandbox's filesystem.
|
|
3989
|
+
*
|
|
3990
|
+
* This call **returns immediately** with a `snapshotId` and `in_progress`
|
|
3991
|
+
* status — the snapshot is created asynchronously. Poll `getSnapshot()` until
|
|
3992
|
+
* `completed` or `failed`, or use `snapshotAndWait()` to block automatically.
|
|
3993
|
+
*
|
|
3994
|
+
* @param options.contentMode - `"filesystem_only"` for cold-boot snapshots (e.g. image builds).
|
|
3995
|
+
* Omit to use the server default (full VM snapshot).
|
|
3996
|
+
*/
|
|
3697
3997
|
async snapshot(sandboxId, options) {
|
|
3698
3998
|
const requestOptions = options?.contentMode != null ? { body: { snapshot_content_mode: options.contentMode } } : void 0;
|
|
3699
3999
|
const raw = await this.http.requestJson(
|
|
@@ -3703,6 +4003,7 @@ var init_client = __esm({
|
|
|
3703
4003
|
);
|
|
3704
4004
|
return fromSnakeKeys(raw, "snapshotId");
|
|
3705
4005
|
}
|
|
4006
|
+
/** Get current status and metadata for a snapshot by ID. */
|
|
3706
4007
|
async getSnapshot(snapshotId) {
|
|
3707
4008
|
const raw = await this.http.requestJson(
|
|
3708
4009
|
"GET",
|
|
@@ -3710,21 +4011,37 @@ var init_client = __esm({
|
|
|
3710
4011
|
);
|
|
3711
4012
|
return fromSnakeKeys(raw, "snapshotId");
|
|
3712
4013
|
}
|
|
4014
|
+
/** List all snapshots in the namespace. */
|
|
3713
4015
|
async listSnapshots() {
|
|
3714
4016
|
const raw = await this.http.requestJson(
|
|
3715
4017
|
"GET",
|
|
3716
4018
|
this.path("snapshots")
|
|
3717
4019
|
);
|
|
3718
|
-
|
|
4020
|
+
const snapshots = (raw.snapshots ?? []).map(
|
|
3719
4021
|
(s) => fromSnakeKeys(s, "snapshotId")
|
|
3720
4022
|
);
|
|
4023
|
+
return Object.assign(snapshots, { traceId: raw.traceId });
|
|
3721
4024
|
}
|
|
4025
|
+
/** Delete a snapshot by ID. */
|
|
3722
4026
|
async deleteSnapshot(snapshotId) {
|
|
3723
4027
|
await this.http.requestJson(
|
|
3724
4028
|
"DELETE",
|
|
3725
4029
|
this.path(`snapshots/${snapshotId}`)
|
|
3726
4030
|
);
|
|
3727
4031
|
}
|
|
4032
|
+
/**
|
|
4033
|
+
* Create a snapshot and block until it is committed.
|
|
4034
|
+
*
|
|
4035
|
+
* Combines `snapshot()` with polling `getSnapshot()` until `completed`.
|
|
4036
|
+
* Prefer `sandbox.checkpoint()` on a `Sandbox` handle for the same behavior
|
|
4037
|
+
* without managing the client separately.
|
|
4038
|
+
*
|
|
4039
|
+
* @param sandboxId - ID of the running sandbox to snapshot.
|
|
4040
|
+
* @param options.timeout - Max seconds to wait (default 300).
|
|
4041
|
+
* @param options.pollInterval - Seconds between status polls (default 1).
|
|
4042
|
+
* @param options.contentMode - Content mode passed through to `snapshot()`.
|
|
4043
|
+
* @throws {SandboxError} If the snapshot fails or `timeout` elapses.
|
|
4044
|
+
*/
|
|
3728
4045
|
async snapshotAndWait(sandboxId, options) {
|
|
3729
4046
|
const timeout = options?.timeout ?? 300;
|
|
3730
4047
|
const pollInterval = options?.pollInterval ?? 1;
|
|
@@ -3747,6 +4064,7 @@ var init_client = __esm({
|
|
|
3747
4064
|
);
|
|
3748
4065
|
}
|
|
3749
4066
|
// --- Pools ---
|
|
4067
|
+
/** Create a new sandbox pool with warm pre-booted containers. */
|
|
3750
4068
|
async createPool(options) {
|
|
3751
4069
|
const body = {
|
|
3752
4070
|
image: options.image,
|
|
@@ -3768,6 +4086,7 @@ var init_client = __esm({
|
|
|
3768
4086
|
);
|
|
3769
4087
|
return fromSnakeKeys(raw, "poolId");
|
|
3770
4088
|
}
|
|
4089
|
+
/** Get current state and metadata for a sandbox pool by ID. */
|
|
3771
4090
|
async getPool(poolId) {
|
|
3772
4091
|
const raw = await this.http.requestJson(
|
|
3773
4092
|
"GET",
|
|
@@ -3775,15 +4094,18 @@ var init_client = __esm({
|
|
|
3775
4094
|
);
|
|
3776
4095
|
return fromSnakeKeys(raw, "poolId");
|
|
3777
4096
|
}
|
|
4097
|
+
/** List all sandbox pools in the namespace. */
|
|
3778
4098
|
async listPools() {
|
|
3779
4099
|
const raw = await this.http.requestJson(
|
|
3780
4100
|
"GET",
|
|
3781
4101
|
this.path("sandbox-pools")
|
|
3782
4102
|
);
|
|
3783
|
-
|
|
4103
|
+
const pools = (raw.pools ?? []).map(
|
|
3784
4104
|
(p) => fromSnakeKeys(p, "poolId")
|
|
3785
4105
|
);
|
|
4106
|
+
return Object.assign(pools, { traceId: raw.traceId });
|
|
3786
4107
|
}
|
|
4108
|
+
/** Replace the configuration of an existing sandbox pool. */
|
|
3787
4109
|
async updatePool(poolId, options) {
|
|
3788
4110
|
const body = {
|
|
3789
4111
|
image: options.image,
|
|
@@ -3805,6 +4127,7 @@ var init_client = __esm({
|
|
|
3805
4127
|
);
|
|
3806
4128
|
return fromSnakeKeys(raw, "poolId");
|
|
3807
4129
|
}
|
|
4130
|
+
/** Delete a sandbox pool. Fails if the pool has active containers. */
|
|
3808
4131
|
async deletePool(poolId) {
|
|
3809
4132
|
await this.http.requestJson(
|
|
3810
4133
|
"DELETE",
|
|
@@ -3812,6 +4135,7 @@ var init_client = __esm({
|
|
|
3812
4135
|
);
|
|
3813
4136
|
}
|
|
3814
4137
|
// --- Connect ---
|
|
4138
|
+
/** Return a `Sandbox` handle for an existing running sandbox without verifying it exists. */
|
|
3815
4139
|
connect(identifier, proxyUrl, routingHint) {
|
|
3816
4140
|
const resolvedProxy = proxyUrl ?? resolveProxyUrl(this.apiUrl);
|
|
3817
4141
|
return new Sandbox({
|
|
@@ -3823,26 +4147,35 @@ var init_client = __esm({
|
|
|
3823
4147
|
routingHint
|
|
3824
4148
|
});
|
|
3825
4149
|
}
|
|
4150
|
+
/**
|
|
4151
|
+
* Create a sandbox, wait for it to reach `Running`, and return a connected handle.
|
|
4152
|
+
*
|
|
4153
|
+
* Blocks until the sandbox is ready or `startupTimeout` elapses. The returned
|
|
4154
|
+
* `Sandbox` auto-terminates when `terminate()` is called.
|
|
4155
|
+
*
|
|
4156
|
+
* @param options.startupTimeout - Max seconds to wait for `Running` status (default 60).
|
|
4157
|
+
* @throws {SandboxError} If the sandbox terminates during startup or the timeout elapses.
|
|
4158
|
+
*/
|
|
3826
4159
|
async createAndConnect(options) {
|
|
3827
4160
|
const startupTimeout = options?.startupTimeout ?? 60;
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
result = await this.create(options);
|
|
3833
|
-
}
|
|
3834
|
-
if (result.status === "running" /* RUNNING */) {
|
|
3835
|
-
const sandbox = this.connect(result.sandboxId, options?.proxyUrl, result.routingHint);
|
|
4161
|
+
const result = options?.poolId != null ? await this.claim(options.poolId) : await this.create(options);
|
|
4162
|
+
const requestedName = options?.poolId != null ? null : options?.name ?? null;
|
|
4163
|
+
const finishConnect = (routingHint, name) => {
|
|
4164
|
+
const sandbox = this.connect(result.sandboxId, options?.proxyUrl, routingHint);
|
|
3836
4165
|
sandbox._setOwner(this);
|
|
4166
|
+
sandbox.traceId = result.traceId;
|
|
4167
|
+
sandbox._setLifecycleIdentifier(result.sandboxId);
|
|
4168
|
+
sandbox._setName(name ?? requestedName);
|
|
3837
4169
|
return sandbox;
|
|
4170
|
+
};
|
|
4171
|
+
if (result.status === "running" /* RUNNING */) {
|
|
4172
|
+
return finishConnect(result.routingHint, result.name);
|
|
3838
4173
|
}
|
|
3839
4174
|
const deadline = Date.now() + startupTimeout * 1e3;
|
|
3840
4175
|
while (Date.now() < deadline) {
|
|
3841
4176
|
const info = await this.get(result.sandboxId);
|
|
3842
4177
|
if (info.status === "running" /* RUNNING */) {
|
|
3843
|
-
|
|
3844
|
-
sandbox._setOwner(this);
|
|
3845
|
-
return sandbox;
|
|
4178
|
+
return finishConnect(info.routingHint, info.name);
|
|
3846
4179
|
}
|
|
3847
4180
|
if (info.status === "terminated" /* TERMINATED */) {
|
|
3848
4181
|
throw new SandboxError(
|
|
@@ -4681,7 +5014,8 @@ async function createSandboxImage(source, options = {}, deps = {}) {
|
|
|
4681
5014
|
sandbox = await client.createAndConnect({
|
|
4682
5015
|
...plan.baseImage == null ? {} : { image: plan.baseImage },
|
|
4683
5016
|
cpus: options.cpus ?? 2,
|
|
4684
|
-
memoryMb: options.memoryMb ?? 4096
|
|
5017
|
+
memoryMb: options.memoryMb ?? 4096,
|
|
5018
|
+
...options.diskMb != null ? { diskMb: options.diskMb } : {}
|
|
4685
5019
|
});
|
|
4686
5020
|
emit({
|
|
4687
5021
|
type: "status",
|
|
@@ -4694,8 +5028,7 @@ async function createSandboxImage(source, options = {}, deps = {}) {
|
|
|
4694
5028
|
});
|
|
4695
5029
|
emit({
|
|
4696
5030
|
type: "snapshot_created",
|
|
4697
|
-
snapshot_id: snapshot.snapshotId
|
|
4698
|
-
snapshot_uri: snapshot.snapshotUri ?? null
|
|
5031
|
+
snapshot_id: snapshot.snapshotId
|
|
4699
5032
|
});
|
|
4700
5033
|
if (!snapshot.snapshotUri) {
|
|
4701
5034
|
throw new Error(
|
|
@@ -4739,27 +5072,33 @@ async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
|
|
|
4739
5072
|
name: { type: "string", short: "n" },
|
|
4740
5073
|
cpus: { type: "string" },
|
|
4741
5074
|
memory: { type: "string" },
|
|
5075
|
+
disk: { type: "string" },
|
|
4742
5076
|
public: { type: "boolean", default: false }
|
|
4743
5077
|
}
|
|
4744
5078
|
});
|
|
4745
5079
|
const dockerfilePath = parsed.positionals[0];
|
|
4746
5080
|
if (!dockerfilePath) {
|
|
4747
|
-
throw new Error("Usage: tensorlake-create-sandbox-image <dockerfile_path> [--name NAME] [--cpus N] [--memory MB] [--public]");
|
|
5081
|
+
throw new Error("Usage: tensorlake-create-sandbox-image <dockerfile_path> [--name NAME] [--cpus N] [--memory MB] [--disk GB] [--public]");
|
|
4748
5082
|
}
|
|
4749
5083
|
const cpus = parsed.values.cpus != null ? Number(parsed.values.cpus) : void 0;
|
|
4750
5084
|
const memoryMb = parsed.values.memory != null ? Number(parsed.values.memory) : void 0;
|
|
5085
|
+
const diskGb = parsed.values.disk != null ? Number(parsed.values.disk) : void 0;
|
|
4751
5086
|
if (cpus != null && !Number.isFinite(cpus)) {
|
|
4752
5087
|
throw new Error(`Invalid --cpus value: ${parsed.values.cpus}`);
|
|
4753
5088
|
}
|
|
4754
5089
|
if (memoryMb != null && !Number.isInteger(memoryMb)) {
|
|
4755
5090
|
throw new Error(`Invalid --memory value: ${parsed.values.memory}`);
|
|
4756
5091
|
}
|
|
5092
|
+
if (diskGb != null && !Number.isInteger(diskGb)) {
|
|
5093
|
+
throw new Error(`Invalid --disk value: ${parsed.values.disk}`);
|
|
5094
|
+
}
|
|
4757
5095
|
await createSandboxImage(
|
|
4758
5096
|
dockerfilePath,
|
|
4759
5097
|
{
|
|
4760
5098
|
registeredName: parsed.values.name,
|
|
4761
5099
|
cpus,
|
|
4762
5100
|
memoryMb,
|
|
5101
|
+
diskMb: diskGb != null ? diskGb * 1024 : void 0,
|
|
4763
5102
|
isPublic: parsed.values.public
|
|
4764
5103
|
},
|
|
4765
5104
|
{ emit: ndjsonStdoutEmit }
|