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/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,13 +3115,17 @@ 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;
|
|
3103
3122
|
ownsSandbox = false;
|
|
3104
3123
|
lifecycleClient = null;
|
|
3124
|
+
lifecycleIdentifier;
|
|
3125
|
+
sandboxName = null;
|
|
3105
3126
|
constructor(options) {
|
|
3106
3127
|
this.sandboxId = options.sandboxId;
|
|
3128
|
+
this.lifecycleIdentifier = options.sandboxId;
|
|
3107
3129
|
const proxyUrl = options.proxyUrl ?? SANDBOX_PROXY_URL;
|
|
3108
3130
|
const { baseUrl, hostHeader } = resolveProxyTarget(proxyUrl, options.sandboxId);
|
|
3109
3131
|
this.baseUrl = baseUrl;
|
|
@@ -3129,21 +3151,182 @@ var init_sandbox = __esm({
|
|
|
3129
3151
|
routingHint: options.routingHint
|
|
3130
3152
|
});
|
|
3131
3153
|
}
|
|
3154
|
+
get name() {
|
|
3155
|
+
return this.sandboxName;
|
|
3156
|
+
}
|
|
3157
|
+
/** @internal Used by client wiring to keep locally cached name in sync. */
|
|
3158
|
+
_setName(name) {
|
|
3159
|
+
this.sandboxName = name;
|
|
3160
|
+
}
|
|
3161
|
+
/** @internal Used by lifecycle operations to pin to canonical sandbox ID. */
|
|
3162
|
+
_setLifecycleIdentifier(identifier) {
|
|
3163
|
+
this.lifecycleIdentifier = identifier;
|
|
3164
|
+
}
|
|
3132
3165
|
/** @internal Used by SandboxClient.createAndConnect to set ownership. */
|
|
3133
3166
|
_setOwner(client) {
|
|
3134
3167
|
this.ownsSandbox = true;
|
|
3135
3168
|
this.lifecycleClient = client;
|
|
3136
3169
|
}
|
|
3170
|
+
// --- Static factory methods ---
|
|
3171
|
+
/**
|
|
3172
|
+
* Create a new sandbox and return a connected, running handle.
|
|
3173
|
+
*
|
|
3174
|
+
* Covers both fresh sandbox creation and restore-from-snapshot (set
|
|
3175
|
+
* `snapshotId`). Blocks until the sandbox is `Running`.
|
|
3176
|
+
*/
|
|
3177
|
+
static async create(options) {
|
|
3178
|
+
const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3179
|
+
const client = new SandboxClient2(
|
|
3180
|
+
options,
|
|
3181
|
+
/* _internal */
|
|
3182
|
+
true
|
|
3183
|
+
);
|
|
3184
|
+
const sandbox = await client.createAndConnect(options);
|
|
3185
|
+
sandbox.lifecycleClient = client;
|
|
3186
|
+
return sandbox;
|
|
3187
|
+
}
|
|
3188
|
+
/**
|
|
3189
|
+
* Attach to an existing sandbox and return a connected handle.
|
|
3190
|
+
*
|
|
3191
|
+
* Verifies the sandbox exists via a server GET call, then returns a handle
|
|
3192
|
+
* in whatever state the sandbox is in. Does **not** auto-resume a suspended
|
|
3193
|
+
* sandbox — call `sandbox.resume()` explicitly.
|
|
3194
|
+
*/
|
|
3195
|
+
static async connect(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
|
+
const info = await client.get(options.sandboxId);
|
|
3203
|
+
const sandbox = client.connect(
|
|
3204
|
+
info.sandboxId,
|
|
3205
|
+
options.proxyUrl,
|
|
3206
|
+
options.routingHint ?? info.routingHint
|
|
3207
|
+
);
|
|
3208
|
+
sandbox.lifecycleClient = client;
|
|
3209
|
+
sandbox._setLifecycleIdentifier(info.sandboxId);
|
|
3210
|
+
sandbox._setName(info.name ?? null);
|
|
3211
|
+
return sandbox;
|
|
3212
|
+
}
|
|
3213
|
+
// --- Static snapshot management ---
|
|
3214
|
+
/** Get information about a snapshot by ID. No sandbox handle needed. */
|
|
3215
|
+
static async getSnapshot(snapshotId, options) {
|
|
3216
|
+
const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3217
|
+
const client = new SandboxClient2(
|
|
3218
|
+
options,
|
|
3219
|
+
/* _internal */
|
|
3220
|
+
true
|
|
3221
|
+
);
|
|
3222
|
+
return client.getSnapshot(snapshotId);
|
|
3223
|
+
}
|
|
3224
|
+
/** Delete a snapshot by ID. No sandbox handle needed. */
|
|
3225
|
+
static async deleteSnapshot(snapshotId, options) {
|
|
3226
|
+
const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3227
|
+
const client = new SandboxClient2(
|
|
3228
|
+
options,
|
|
3229
|
+
/* _internal */
|
|
3230
|
+
true
|
|
3231
|
+
);
|
|
3232
|
+
await client.deleteSnapshot(snapshotId);
|
|
3233
|
+
}
|
|
3234
|
+
// --- Instance lifecycle methods ---
|
|
3235
|
+
requireLifecycleClient(operation) {
|
|
3236
|
+
if (!this.lifecycleClient) {
|
|
3237
|
+
throw new SandboxError(
|
|
3238
|
+
`Cannot ${operation}: no lifecycle client available. Use Sandbox.create() or Sandbox.connect() to get a lifecycle-aware handle.`
|
|
3239
|
+
);
|
|
3240
|
+
}
|
|
3241
|
+
return this.lifecycleClient;
|
|
3242
|
+
}
|
|
3243
|
+
/**
|
|
3244
|
+
* Fetch the current sandbox status from the server.
|
|
3245
|
+
*
|
|
3246
|
+
* Always hits the network — the value is not cached locally because the
|
|
3247
|
+
* status changes over the sandbox's lifecycle.
|
|
3248
|
+
*/
|
|
3249
|
+
async status() {
|
|
3250
|
+
const client = this.requireLifecycleClient("read_status");
|
|
3251
|
+
const info = await client.get(this.lifecycleIdentifier);
|
|
3252
|
+
this._setLifecycleIdentifier(info.sandboxId);
|
|
3253
|
+
this._setName(info.name ?? null);
|
|
3254
|
+
return info.status;
|
|
3255
|
+
}
|
|
3256
|
+
/**
|
|
3257
|
+
* Update this sandbox's properties (name, exposed ports, proxy auth).
|
|
3258
|
+
*
|
|
3259
|
+
* Naming an ephemeral sandbox makes it non-ephemeral and enables
|
|
3260
|
+
* suspend/resume.
|
|
3261
|
+
*/
|
|
3262
|
+
async update(options) {
|
|
3263
|
+
const client = this.requireLifecycleClient("update");
|
|
3264
|
+
const info = await client.update(this.lifecycleIdentifier, options);
|
|
3265
|
+
this._setLifecycleIdentifier(info.sandboxId);
|
|
3266
|
+
this._setName(info.name ?? null);
|
|
3267
|
+
return info;
|
|
3268
|
+
}
|
|
3269
|
+
/**
|
|
3270
|
+
* Suspend this sandbox.
|
|
3271
|
+
*
|
|
3272
|
+
* By default blocks until the sandbox is fully `Suspended`. Pass
|
|
3273
|
+
* `{ wait: false }` for fire-and-return.
|
|
3274
|
+
*/
|
|
3275
|
+
async suspend(options) {
|
|
3276
|
+
const client = this.requireLifecycleClient("suspend");
|
|
3277
|
+
await client.suspend(this.lifecycleIdentifier, options);
|
|
3278
|
+
}
|
|
3279
|
+
/**
|
|
3280
|
+
* Resume this sandbox.
|
|
3281
|
+
*
|
|
3282
|
+
* By default blocks until the sandbox is `Running` and routable. Pass
|
|
3283
|
+
* `{ wait: false }` for fire-and-return.
|
|
3284
|
+
*/
|
|
3285
|
+
async resume(options) {
|
|
3286
|
+
const client = this.requireLifecycleClient("resume");
|
|
3287
|
+
await client.resume(this.lifecycleIdentifier, options);
|
|
3288
|
+
}
|
|
3289
|
+
/**
|
|
3290
|
+
* Create a snapshot of this sandbox's filesystem and wait for it to
|
|
3291
|
+
* be committed.
|
|
3292
|
+
*
|
|
3293
|
+
* By default blocks until the snapshot artifact is ready and returns
|
|
3294
|
+
* the completed `SnapshotInfo`. Pass `{ wait: false }` to fire-and-return
|
|
3295
|
+
* (returns `undefined`).
|
|
3296
|
+
*/
|
|
3297
|
+
async checkpoint(options) {
|
|
3298
|
+
const client = this.requireLifecycleClient("checkpoint");
|
|
3299
|
+
if (options?.wait === false) {
|
|
3300
|
+
await client.snapshot(this.lifecycleIdentifier, { contentMode: options.contentMode });
|
|
3301
|
+
return void 0;
|
|
3302
|
+
}
|
|
3303
|
+
return client.snapshotAndWait(this.lifecycleIdentifier, {
|
|
3304
|
+
timeout: options?.timeout,
|
|
3305
|
+
pollInterval: options?.pollInterval,
|
|
3306
|
+
contentMode: options?.contentMode
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
/**
|
|
3310
|
+
* List snapshots taken from this sandbox.
|
|
3311
|
+
*/
|
|
3312
|
+
async listSnapshots() {
|
|
3313
|
+
const client = this.requireLifecycleClient("listSnapshots");
|
|
3314
|
+
const all = await client.listSnapshots();
|
|
3315
|
+
const filtered = all.filter((s) => s.sandboxId === this.lifecycleIdentifier);
|
|
3316
|
+
return Object.assign(filtered, { traceId: all.traceId });
|
|
3317
|
+
}
|
|
3318
|
+
/** Close the HTTP client. The sandbox keeps running. */
|
|
3137
3319
|
close() {
|
|
3138
3320
|
this.http.close();
|
|
3139
3321
|
}
|
|
3322
|
+
/** Terminate the sandbox and release all resources. */
|
|
3140
3323
|
async terminate() {
|
|
3141
3324
|
const client = this.lifecycleClient;
|
|
3142
3325
|
this.ownsSandbox = false;
|
|
3143
3326
|
this.lifecycleClient = null;
|
|
3144
3327
|
this.close();
|
|
3145
3328
|
if (client) {
|
|
3146
|
-
await client.delete(this.
|
|
3329
|
+
await client.delete(this.lifecycleIdentifier);
|
|
3147
3330
|
}
|
|
3148
3331
|
}
|
|
3149
3332
|
// --- High-level convenience ---
|
|
@@ -3189,6 +3372,14 @@ var init_sandbox = __esm({
|
|
|
3189
3372
|
};
|
|
3190
3373
|
}
|
|
3191
3374
|
// --- Process management ---
|
|
3375
|
+
/**
|
|
3376
|
+
* Start a process in the sandbox without waiting for it to exit.
|
|
3377
|
+
*
|
|
3378
|
+
* Returns a `ProcessInfo` with the assigned `pid`. Use `getProcess()` to
|
|
3379
|
+
* poll status, or `followStdout()` / `followOutput()` to stream output
|
|
3380
|
+
* until the process exits. Use `run()` instead to block until completion
|
|
3381
|
+
* and get combined output in one call.
|
|
3382
|
+
*/
|
|
3192
3383
|
async startProcess(command, options) {
|
|
3193
3384
|
const payload = { command };
|
|
3194
3385
|
if (options?.args != null) payload.args = options.args;
|
|
@@ -3210,13 +3401,16 @@ var init_sandbox = __esm({
|
|
|
3210
3401
|
);
|
|
3211
3402
|
return fromSnakeKeys(raw);
|
|
3212
3403
|
}
|
|
3404
|
+
/** List all processes (running and exited) tracked by the sandbox daemon. */
|
|
3213
3405
|
async listProcesses() {
|
|
3214
3406
|
const raw = await this.http.requestJson(
|
|
3215
3407
|
"GET",
|
|
3216
3408
|
"/api/v1/processes"
|
|
3217
3409
|
);
|
|
3218
|
-
|
|
3410
|
+
const processes = (raw.processes ?? []).map((p) => fromSnakeKeys(p));
|
|
3411
|
+
return Object.assign(processes, { traceId: raw.traceId });
|
|
3219
3412
|
}
|
|
3413
|
+
/** Get current status and metadata for a process by PID. */
|
|
3220
3414
|
async getProcess(pid) {
|
|
3221
3415
|
const raw = await this.http.requestJson(
|
|
3222
3416
|
"GET",
|
|
@@ -3224,9 +3418,11 @@ var init_sandbox = __esm({
|
|
|
3224
3418
|
);
|
|
3225
3419
|
return fromSnakeKeys(raw);
|
|
3226
3420
|
}
|
|
3421
|
+
/** Send SIGKILL to a process. */
|
|
3227
3422
|
async killProcess(pid) {
|
|
3228
3423
|
await this.http.requestJson("DELETE", `/api/v1/processes/${pid}`);
|
|
3229
3424
|
}
|
|
3425
|
+
/** Send an arbitrary signal to a process (e.g. `15` for SIGTERM, `9` for SIGKILL). */
|
|
3230
3426
|
async sendSignal(pid, signal) {
|
|
3231
3427
|
const raw = await this.http.requestJson(
|
|
3232
3428
|
"POST",
|
|
@@ -3236,15 +3432,18 @@ var init_sandbox = __esm({
|
|
|
3236
3432
|
return fromSnakeKeys(raw);
|
|
3237
3433
|
}
|
|
3238
3434
|
// --- Process I/O ---
|
|
3435
|
+
/** Write bytes to a process's stdin. The process must have been started with `stdinMode: StdinMode.PIPE`. */
|
|
3239
3436
|
async writeStdin(pid, data) {
|
|
3240
3437
|
await this.http.requestBytes("POST", `/api/v1/processes/${pid}/stdin`, {
|
|
3241
3438
|
body: data,
|
|
3242
3439
|
contentType: "application/octet-stream"
|
|
3243
3440
|
});
|
|
3244
3441
|
}
|
|
3442
|
+
/** Close a process's stdin pipe, signalling EOF to the process. */
|
|
3245
3443
|
async closeStdin(pid) {
|
|
3246
3444
|
await this.http.requestJson("POST", `/api/v1/processes/${pid}/stdin/close`);
|
|
3247
3445
|
}
|
|
3446
|
+
/** Return all captured stdout lines produced so far by a process. */
|
|
3248
3447
|
async getStdout(pid) {
|
|
3249
3448
|
const raw = await this.http.requestJson(
|
|
3250
3449
|
"GET",
|
|
@@ -3252,6 +3451,7 @@ var init_sandbox = __esm({
|
|
|
3252
3451
|
);
|
|
3253
3452
|
return fromSnakeKeys(raw);
|
|
3254
3453
|
}
|
|
3454
|
+
/** Return all captured stderr lines produced so far by a process. */
|
|
3255
3455
|
async getStderr(pid) {
|
|
3256
3456
|
const raw = await this.http.requestJson(
|
|
3257
3457
|
"GET",
|
|
@@ -3259,6 +3459,7 @@ var init_sandbox = __esm({
|
|
|
3259
3459
|
);
|
|
3260
3460
|
return fromSnakeKeys(raw);
|
|
3261
3461
|
}
|
|
3462
|
+
/** Return all captured stdout+stderr lines produced so far by a process. */
|
|
3262
3463
|
async getOutput(pid) {
|
|
3263
3464
|
const raw = await this.http.requestJson(
|
|
3264
3465
|
"GET",
|
|
@@ -3267,6 +3468,7 @@ var init_sandbox = __esm({
|
|
|
3267
3468
|
return fromSnakeKeys(raw);
|
|
3268
3469
|
}
|
|
3269
3470
|
// --- Streaming (SSE) ---
|
|
3471
|
+
/** Stream stdout events from a process until it exits. Yields one `OutputEvent` per line. */
|
|
3270
3472
|
async *followStdout(pid, options) {
|
|
3271
3473
|
const stream = await this.http.requestStream(
|
|
3272
3474
|
"GET",
|
|
@@ -3280,6 +3482,7 @@ var init_sandbox = __esm({
|
|
|
3280
3482
|
yield fromSnakeKeys(raw);
|
|
3281
3483
|
}
|
|
3282
3484
|
}
|
|
3485
|
+
/** Stream stderr events from a process until it exits. Yields one `OutputEvent` per line. */
|
|
3283
3486
|
async *followStderr(pid, options) {
|
|
3284
3487
|
const stream = await this.http.requestStream(
|
|
3285
3488
|
"GET",
|
|
@@ -3293,6 +3496,7 @@ var init_sandbox = __esm({
|
|
|
3293
3496
|
yield fromSnakeKeys(raw);
|
|
3294
3497
|
}
|
|
3295
3498
|
}
|
|
3499
|
+
/** Stream combined stdout+stderr events from a process until it exits. Yields one `OutputEvent` per line. */
|
|
3296
3500
|
async *followOutput(pid, options) {
|
|
3297
3501
|
const stream = await this.http.requestStream(
|
|
3298
3502
|
"GET",
|
|
@@ -3307,12 +3511,14 @@ var init_sandbox = __esm({
|
|
|
3307
3511
|
}
|
|
3308
3512
|
}
|
|
3309
3513
|
// --- File operations ---
|
|
3514
|
+
/** Read a file from the sandbox and return its raw bytes. */
|
|
3310
3515
|
async readFile(path2) {
|
|
3311
3516
|
return this.http.requestBytes(
|
|
3312
3517
|
"GET",
|
|
3313
3518
|
`/api/v1/files?path=${encodeURIComponent(path2)}`
|
|
3314
3519
|
);
|
|
3315
3520
|
}
|
|
3521
|
+
/** Write raw bytes to a file in the sandbox, creating it if it does not exist. */
|
|
3316
3522
|
async writeFile(path2, content) {
|
|
3317
3523
|
await this.http.requestBytes(
|
|
3318
3524
|
"PUT",
|
|
@@ -3320,12 +3526,14 @@ var init_sandbox = __esm({
|
|
|
3320
3526
|
{ body: content, contentType: "application/octet-stream" }
|
|
3321
3527
|
);
|
|
3322
3528
|
}
|
|
3529
|
+
/** Delete a file from the sandbox. */
|
|
3323
3530
|
async deleteFile(path2) {
|
|
3324
3531
|
await this.http.requestJson(
|
|
3325
3532
|
"DELETE",
|
|
3326
3533
|
`/api/v1/files?path=${encodeURIComponent(path2)}`
|
|
3327
3534
|
);
|
|
3328
3535
|
}
|
|
3536
|
+
/** List the contents of a directory in the sandbox. */
|
|
3329
3537
|
async listDirectory(path2) {
|
|
3330
3538
|
const raw = await this.http.requestJson(
|
|
3331
3539
|
"GET",
|
|
@@ -3334,6 +3542,7 @@ var init_sandbox = __esm({
|
|
|
3334
3542
|
return fromSnakeKeys(raw);
|
|
3335
3543
|
}
|
|
3336
3544
|
// --- PTY ---
|
|
3545
|
+
/** Create an interactive PTY session. Returns a `sessionId` and `token` for WebSocket connection via `connectPty()`. */
|
|
3337
3546
|
async createPtySession(options) {
|
|
3338
3547
|
const payload = {
|
|
3339
3548
|
command: options.command,
|
|
@@ -3350,6 +3559,7 @@ var init_sandbox = __esm({
|
|
|
3350
3559
|
);
|
|
3351
3560
|
return fromSnakeKeys(raw);
|
|
3352
3561
|
}
|
|
3562
|
+
/** Create a PTY session and connect to it immediately. Cleans up the session if the WebSocket connection fails. */
|
|
3353
3563
|
async createPty(options) {
|
|
3354
3564
|
const { onData, onExit, ...createOptions } = options;
|
|
3355
3565
|
const session = await this.createPtySession(createOptions);
|
|
@@ -3363,6 +3573,7 @@ var init_sandbox = __esm({
|
|
|
3363
3573
|
throw error;
|
|
3364
3574
|
}
|
|
3365
3575
|
}
|
|
3576
|
+
/** Attach to an existing PTY session by ID and token and return a connected `Pty` handle. */
|
|
3366
3577
|
async connectPty(sessionId, token, options) {
|
|
3367
3578
|
const wsUrl = new URL(this.ptyWsUrl(sessionId, token));
|
|
3368
3579
|
const authToken = wsUrl.searchParams.get("token") ?? token;
|
|
@@ -3387,6 +3598,7 @@ var init_sandbox = __esm({
|
|
|
3387
3598
|
await pty.connect();
|
|
3388
3599
|
return pty;
|
|
3389
3600
|
}
|
|
3601
|
+
/** Open a TCP tunnel to a port inside the sandbox and return the local listener. */
|
|
3390
3602
|
async createTunnel(remotePort, options) {
|
|
3391
3603
|
return TcpTunnel.listen({
|
|
3392
3604
|
baseUrl: this.baseUrl,
|
|
@@ -3397,6 +3609,7 @@ var init_sandbox = __esm({
|
|
|
3397
3609
|
connectTimeout: options?.connectTimeout
|
|
3398
3610
|
});
|
|
3399
3611
|
}
|
|
3612
|
+
/** Connect to a sandbox VNC session for programmatic desktop control. */
|
|
3400
3613
|
async connectDesktop(options) {
|
|
3401
3614
|
return Desktop.connect({
|
|
3402
3615
|
baseUrl: this.baseUrl,
|
|
@@ -3419,6 +3632,7 @@ var init_sandbox = __esm({
|
|
|
3419
3632
|
return `${wsBase}/api/v1/pty/${sessionId}/ws?token=${token}`;
|
|
3420
3633
|
}
|
|
3421
3634
|
// --- Health ---
|
|
3635
|
+
/** Check the sandbox daemon health. */
|
|
3422
3636
|
async health() {
|
|
3423
3637
|
const raw = await this.http.requestJson(
|
|
3424
3638
|
"GET",
|
|
@@ -3426,6 +3640,7 @@ var init_sandbox = __esm({
|
|
|
3426
3640
|
);
|
|
3427
3641
|
return fromSnakeKeys(raw);
|
|
3428
3642
|
}
|
|
3643
|
+
/** Get sandbox daemon info (version, uptime, process counts). */
|
|
3429
3644
|
async info() {
|
|
3430
3645
|
const raw = await this.http.requestJson(
|
|
3431
3646
|
"GET",
|
|
@@ -3438,6 +3653,10 @@ var init_sandbox = __esm({
|
|
|
3438
3653
|
});
|
|
3439
3654
|
|
|
3440
3655
|
// src/client.ts
|
|
3656
|
+
var client_exports = {};
|
|
3657
|
+
__export(client_exports, {
|
|
3658
|
+
SandboxClient: () => SandboxClient
|
|
3659
|
+
});
|
|
3441
3660
|
function sleep2(ms) {
|
|
3442
3661
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3443
3662
|
}
|
|
@@ -3474,7 +3693,13 @@ var init_client = __esm({
|
|
|
3474
3693
|
projectId;
|
|
3475
3694
|
namespace;
|
|
3476
3695
|
local;
|
|
3477
|
-
|
|
3696
|
+
/** @internal Pass `true` to suppress the deprecation warning when used by `Sandbox.create()` / `Sandbox.connect()`. */
|
|
3697
|
+
constructor(options, _internal = false) {
|
|
3698
|
+
if (!_internal) {
|
|
3699
|
+
console.warn(
|
|
3700
|
+
"[tensorlake] SandboxClient is deprecated; use Sandbox.create() / Sandbox.connect() instead."
|
|
3701
|
+
);
|
|
3702
|
+
}
|
|
3478
3703
|
this.apiUrl = options?.apiUrl ?? API_URL;
|
|
3479
3704
|
this.apiKey = options?.apiKey ?? API_KEY;
|
|
3480
3705
|
this.organizationId = options?.organizationId;
|
|
@@ -3514,12 +3739,13 @@ var init_client = __esm({
|
|
|
3514
3739
|
return lifecyclePath(subpath, this.local, this.namespace);
|
|
3515
3740
|
}
|
|
3516
3741
|
// --- Sandbox CRUD ---
|
|
3742
|
+
/** Create a new sandbox. Returns immediately; the sandbox may still be starting. Use `createAndConnect()` for a blocking, ready-to-use handle. */
|
|
3517
3743
|
async create(options) {
|
|
3518
3744
|
const body = {
|
|
3519
3745
|
resources: {
|
|
3520
3746
|
cpus: options?.cpus ?? 1,
|
|
3521
3747
|
memory_mb: options?.memoryMb ?? 1024,
|
|
3522
|
-
|
|
3748
|
+
...options?.diskMb != null ? { disk_mb: options.diskMb } : {}
|
|
3523
3749
|
}
|
|
3524
3750
|
};
|
|
3525
3751
|
if (options?.image != null) body.image = options.image;
|
|
@@ -3540,8 +3766,10 @@ var init_client = __esm({
|
|
|
3540
3766
|
this.path("sandboxes"),
|
|
3541
3767
|
{ body }
|
|
3542
3768
|
);
|
|
3543
|
-
|
|
3769
|
+
const result = fromSnakeKeys(raw, "sandboxId");
|
|
3770
|
+
return Object.assign(result, { traceId: raw.traceId });
|
|
3544
3771
|
}
|
|
3772
|
+
/** Get current state and metadata for a sandbox by ID. */
|
|
3545
3773
|
async get(sandboxId) {
|
|
3546
3774
|
const raw = await this.http.requestJson(
|
|
3547
3775
|
"GET",
|
|
@@ -3549,15 +3777,18 @@ var init_client = __esm({
|
|
|
3549
3777
|
);
|
|
3550
3778
|
return fromSnakeKeys(raw, "sandboxId");
|
|
3551
3779
|
}
|
|
3780
|
+
/** List all sandboxes in the namespace. */
|
|
3552
3781
|
async list() {
|
|
3553
3782
|
const raw = await this.http.requestJson(
|
|
3554
3783
|
"GET",
|
|
3555
3784
|
this.path("sandboxes")
|
|
3556
3785
|
);
|
|
3557
|
-
|
|
3786
|
+
const sandboxes = (raw.sandboxes ?? []).map(
|
|
3558
3787
|
(s) => fromSnakeKeys(s, "sandboxId")
|
|
3559
3788
|
);
|
|
3789
|
+
return Object.assign(sandboxes, { traceId: raw.traceId });
|
|
3560
3790
|
}
|
|
3791
|
+
/** Update sandbox properties such as name, exposed ports, and proxy auth settings. */
|
|
3561
3792
|
async update(sandboxId, options) {
|
|
3562
3793
|
const body = {};
|
|
3563
3794
|
if (options.name != null) body.name = options.name;
|
|
@@ -3577,6 +3808,7 @@ var init_client = __esm({
|
|
|
3577
3808
|
);
|
|
3578
3809
|
return fromSnakeKeys(raw, "sandboxId");
|
|
3579
3810
|
}
|
|
3811
|
+
/** Get the current proxy port settings for a sandbox. */
|
|
3580
3812
|
async getPortAccess(sandboxId) {
|
|
3581
3813
|
const info = await this.get(sandboxId);
|
|
3582
3814
|
return {
|
|
@@ -3585,6 +3817,7 @@ var init_client = __esm({
|
|
|
3585
3817
|
sandboxUrl: info.sandboxUrl
|
|
3586
3818
|
};
|
|
3587
3819
|
}
|
|
3820
|
+
/** Add one or more user ports to the sandbox proxy allowlist. */
|
|
3588
3821
|
async exposePorts(sandboxId, ports, options) {
|
|
3589
3822
|
const requestedPorts = normalizeUserPorts(ports);
|
|
3590
3823
|
const current = await this.getPortAccess(sandboxId);
|
|
@@ -3597,6 +3830,7 @@ var init_client = __esm({
|
|
|
3597
3830
|
exposedPorts: desiredPorts
|
|
3598
3831
|
});
|
|
3599
3832
|
}
|
|
3833
|
+
/** Remove one or more user ports from the sandbox proxy allowlist. */
|
|
3600
3834
|
async unexposePorts(sandboxId, ports) {
|
|
3601
3835
|
const requestedPorts = normalizeUserPorts(ports);
|
|
3602
3836
|
const current = await this.getPortAccess(sandboxId);
|
|
@@ -3607,32 +3841,98 @@ var init_client = __esm({
|
|
|
3607
3841
|
exposedPorts: desiredPorts
|
|
3608
3842
|
});
|
|
3609
3843
|
}
|
|
3844
|
+
/** Terminate and delete a sandbox. */
|
|
3610
3845
|
async delete(sandboxId) {
|
|
3611
3846
|
await this.http.requestJson(
|
|
3612
3847
|
"DELETE",
|
|
3613
3848
|
this.path(`sandboxes/${sandboxId}`)
|
|
3614
3849
|
);
|
|
3615
3850
|
}
|
|
3616
|
-
|
|
3851
|
+
/**
|
|
3852
|
+
* Suspend a named sandbox, preserving its state for later resume.
|
|
3853
|
+
*
|
|
3854
|
+
* Only sandboxes created with a `name` can be suspended; ephemeral sandboxes
|
|
3855
|
+
* cannot. By default blocks until the sandbox is fully `Suspended`. Pass
|
|
3856
|
+
* `{ wait: false }` to return immediately after the request is sent
|
|
3857
|
+
* (fire-and-return); the server processes the suspend asynchronously.
|
|
3858
|
+
*
|
|
3859
|
+
* @param sandboxId - ID or name of the sandbox.
|
|
3860
|
+
* @param options.wait - If `true` (default), poll until `Suspended`. Pass `false` to fire-and-return.
|
|
3861
|
+
* @param options.timeout - Max seconds to wait when `wait=true` (default 300).
|
|
3862
|
+
* @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
|
|
3863
|
+
* @throws {SandboxError} If `wait=true` and the sandbox does not reach `Suspended` within `timeout`.
|
|
3864
|
+
*/
|
|
3865
|
+
async suspend(sandboxId, options) {
|
|
3617
3866
|
await this.http.requestResponse(
|
|
3618
3867
|
"POST",
|
|
3619
3868
|
this.path(`sandboxes/${sandboxId}/suspend`)
|
|
3620
3869
|
);
|
|
3870
|
+
if (options?.wait === false) return;
|
|
3871
|
+
const timeout = options?.timeout ?? 300;
|
|
3872
|
+
const pollInterval = options?.pollInterval ?? 1;
|
|
3873
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
3874
|
+
while (Date.now() < deadline) {
|
|
3875
|
+
const info = await this.get(sandboxId);
|
|
3876
|
+
if (info.status === "suspended" /* SUSPENDED */) return;
|
|
3877
|
+
if (info.status === "terminated" /* TERMINATED */) {
|
|
3878
|
+
throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for suspend`);
|
|
3879
|
+
}
|
|
3880
|
+
await sleep2(pollInterval * 1e3);
|
|
3881
|
+
}
|
|
3882
|
+
throw new SandboxError(`Sandbox ${sandboxId} did not suspend within ${timeout}s`);
|
|
3621
3883
|
}
|
|
3622
|
-
|
|
3884
|
+
/**
|
|
3885
|
+
* Resume a suspended sandbox and bring it back to `Running`.
|
|
3886
|
+
*
|
|
3887
|
+
* By default blocks until the sandbox is `Running` and routable. Pass
|
|
3888
|
+
* `{ wait: false }` to return immediately after the request is sent
|
|
3889
|
+
* (fire-and-return); the server processes the resume asynchronously.
|
|
3890
|
+
*
|
|
3891
|
+
* @param sandboxId - ID or name of the sandbox.
|
|
3892
|
+
* @param options.wait - If `true` (default), poll until `Running`. Pass `false` to fire-and-return.
|
|
3893
|
+
* @param options.timeout - Max seconds to wait when `wait=true` (default 300).
|
|
3894
|
+
* @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
|
|
3895
|
+
* @throws {SandboxError} If `wait=true` and the sandbox does not reach `Running` within `timeout`.
|
|
3896
|
+
*/
|
|
3897
|
+
async resume(sandboxId, options) {
|
|
3623
3898
|
await this.http.requestResponse(
|
|
3624
3899
|
"POST",
|
|
3625
3900
|
this.path(`sandboxes/${sandboxId}/resume`)
|
|
3626
3901
|
);
|
|
3902
|
+
if (options?.wait === false) return;
|
|
3903
|
+
const timeout = options?.timeout ?? 300;
|
|
3904
|
+
const pollInterval = options?.pollInterval ?? 1;
|
|
3905
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
3906
|
+
while (Date.now() < deadline) {
|
|
3907
|
+
const info = await this.get(sandboxId);
|
|
3908
|
+
if (info.status === "running" /* RUNNING */) return;
|
|
3909
|
+
if (info.status === "terminated" /* TERMINATED */) {
|
|
3910
|
+
throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for resume`);
|
|
3911
|
+
}
|
|
3912
|
+
await sleep2(pollInterval * 1e3);
|
|
3913
|
+
}
|
|
3914
|
+
throw new SandboxError(`Sandbox ${sandboxId} did not resume within ${timeout}s`);
|
|
3627
3915
|
}
|
|
3916
|
+
/** Claim a warm sandbox from a pool, creating one if no warm containers are available. */
|
|
3628
3917
|
async claim(poolId) {
|
|
3629
3918
|
const raw = await this.http.requestJson(
|
|
3630
3919
|
"POST",
|
|
3631
3920
|
this.path(`sandbox-pools/${poolId}/sandboxes`)
|
|
3632
3921
|
);
|
|
3633
|
-
|
|
3922
|
+
const result = fromSnakeKeys(raw, "sandboxId");
|
|
3923
|
+
return Object.assign(result, { traceId: raw.traceId });
|
|
3634
3924
|
}
|
|
3635
3925
|
// --- Snapshots ---
|
|
3926
|
+
/**
|
|
3927
|
+
* Request a snapshot of a running sandbox's filesystem.
|
|
3928
|
+
*
|
|
3929
|
+
* This call **returns immediately** with a `snapshotId` and `in_progress`
|
|
3930
|
+
* status — the snapshot is created asynchronously. Poll `getSnapshot()` until
|
|
3931
|
+
* `completed` or `failed`, or use `snapshotAndWait()` to block automatically.
|
|
3932
|
+
*
|
|
3933
|
+
* @param options.contentMode - `"filesystem_only"` for cold-boot snapshots (e.g. image builds).
|
|
3934
|
+
* Omit to use the server default (full VM snapshot).
|
|
3935
|
+
*/
|
|
3636
3936
|
async snapshot(sandboxId, options) {
|
|
3637
3937
|
const requestOptions = options?.contentMode != null ? { body: { snapshot_content_mode: options.contentMode } } : void 0;
|
|
3638
3938
|
const raw = await this.http.requestJson(
|
|
@@ -3642,6 +3942,7 @@ var init_client = __esm({
|
|
|
3642
3942
|
);
|
|
3643
3943
|
return fromSnakeKeys(raw, "snapshotId");
|
|
3644
3944
|
}
|
|
3945
|
+
/** Get current status and metadata for a snapshot by ID. */
|
|
3645
3946
|
async getSnapshot(snapshotId) {
|
|
3646
3947
|
const raw = await this.http.requestJson(
|
|
3647
3948
|
"GET",
|
|
@@ -3649,21 +3950,37 @@ var init_client = __esm({
|
|
|
3649
3950
|
);
|
|
3650
3951
|
return fromSnakeKeys(raw, "snapshotId");
|
|
3651
3952
|
}
|
|
3953
|
+
/** List all snapshots in the namespace. */
|
|
3652
3954
|
async listSnapshots() {
|
|
3653
3955
|
const raw = await this.http.requestJson(
|
|
3654
3956
|
"GET",
|
|
3655
3957
|
this.path("snapshots")
|
|
3656
3958
|
);
|
|
3657
|
-
|
|
3959
|
+
const snapshots = (raw.snapshots ?? []).map(
|
|
3658
3960
|
(s) => fromSnakeKeys(s, "snapshotId")
|
|
3659
3961
|
);
|
|
3962
|
+
return Object.assign(snapshots, { traceId: raw.traceId });
|
|
3660
3963
|
}
|
|
3964
|
+
/** Delete a snapshot by ID. */
|
|
3661
3965
|
async deleteSnapshot(snapshotId) {
|
|
3662
3966
|
await this.http.requestJson(
|
|
3663
3967
|
"DELETE",
|
|
3664
3968
|
this.path(`snapshots/${snapshotId}`)
|
|
3665
3969
|
);
|
|
3666
3970
|
}
|
|
3971
|
+
/**
|
|
3972
|
+
* Create a snapshot and block until it is committed.
|
|
3973
|
+
*
|
|
3974
|
+
* Combines `snapshot()` with polling `getSnapshot()` until `completed`.
|
|
3975
|
+
* Prefer `sandbox.checkpoint()` on a `Sandbox` handle for the same behavior
|
|
3976
|
+
* without managing the client separately.
|
|
3977
|
+
*
|
|
3978
|
+
* @param sandboxId - ID of the running sandbox to snapshot.
|
|
3979
|
+
* @param options.timeout - Max seconds to wait (default 300).
|
|
3980
|
+
* @param options.pollInterval - Seconds between status polls (default 1).
|
|
3981
|
+
* @param options.contentMode - Content mode passed through to `snapshot()`.
|
|
3982
|
+
* @throws {SandboxError} If the snapshot fails or `timeout` elapses.
|
|
3983
|
+
*/
|
|
3667
3984
|
async snapshotAndWait(sandboxId, options) {
|
|
3668
3985
|
const timeout = options?.timeout ?? 300;
|
|
3669
3986
|
const pollInterval = options?.pollInterval ?? 1;
|
|
@@ -3686,6 +4003,7 @@ var init_client = __esm({
|
|
|
3686
4003
|
);
|
|
3687
4004
|
}
|
|
3688
4005
|
// --- Pools ---
|
|
4006
|
+
/** Create a new sandbox pool with warm pre-booted containers. */
|
|
3689
4007
|
async createPool(options) {
|
|
3690
4008
|
const body = {
|
|
3691
4009
|
image: options.image,
|
|
@@ -3707,6 +4025,7 @@ var init_client = __esm({
|
|
|
3707
4025
|
);
|
|
3708
4026
|
return fromSnakeKeys(raw, "poolId");
|
|
3709
4027
|
}
|
|
4028
|
+
/** Get current state and metadata for a sandbox pool by ID. */
|
|
3710
4029
|
async getPool(poolId) {
|
|
3711
4030
|
const raw = await this.http.requestJson(
|
|
3712
4031
|
"GET",
|
|
@@ -3714,15 +4033,18 @@ var init_client = __esm({
|
|
|
3714
4033
|
);
|
|
3715
4034
|
return fromSnakeKeys(raw, "poolId");
|
|
3716
4035
|
}
|
|
4036
|
+
/** List all sandbox pools in the namespace. */
|
|
3717
4037
|
async listPools() {
|
|
3718
4038
|
const raw = await this.http.requestJson(
|
|
3719
4039
|
"GET",
|
|
3720
4040
|
this.path("sandbox-pools")
|
|
3721
4041
|
);
|
|
3722
|
-
|
|
4042
|
+
const pools = (raw.pools ?? []).map(
|
|
3723
4043
|
(p) => fromSnakeKeys(p, "poolId")
|
|
3724
4044
|
);
|
|
4045
|
+
return Object.assign(pools, { traceId: raw.traceId });
|
|
3725
4046
|
}
|
|
4047
|
+
/** Replace the configuration of an existing sandbox pool. */
|
|
3726
4048
|
async updatePool(poolId, options) {
|
|
3727
4049
|
const body = {
|
|
3728
4050
|
image: options.image,
|
|
@@ -3744,6 +4066,7 @@ var init_client = __esm({
|
|
|
3744
4066
|
);
|
|
3745
4067
|
return fromSnakeKeys(raw, "poolId");
|
|
3746
4068
|
}
|
|
4069
|
+
/** Delete a sandbox pool. Fails if the pool has active containers. */
|
|
3747
4070
|
async deletePool(poolId) {
|
|
3748
4071
|
await this.http.requestJson(
|
|
3749
4072
|
"DELETE",
|
|
@@ -3751,6 +4074,7 @@ var init_client = __esm({
|
|
|
3751
4074
|
);
|
|
3752
4075
|
}
|
|
3753
4076
|
// --- Connect ---
|
|
4077
|
+
/** Return a `Sandbox` handle for an existing running sandbox without verifying it exists. */
|
|
3754
4078
|
connect(identifier, proxyUrl, routingHint) {
|
|
3755
4079
|
const resolvedProxy = proxyUrl ?? resolveProxyUrl(this.apiUrl);
|
|
3756
4080
|
return new Sandbox({
|
|
@@ -3762,26 +4086,35 @@ var init_client = __esm({
|
|
|
3762
4086
|
routingHint
|
|
3763
4087
|
});
|
|
3764
4088
|
}
|
|
4089
|
+
/**
|
|
4090
|
+
* Create a sandbox, wait for it to reach `Running`, and return a connected handle.
|
|
4091
|
+
*
|
|
4092
|
+
* Blocks until the sandbox is ready or `startupTimeout` elapses. The returned
|
|
4093
|
+
* `Sandbox` auto-terminates when `terminate()` is called.
|
|
4094
|
+
*
|
|
4095
|
+
* @param options.startupTimeout - Max seconds to wait for `Running` status (default 60).
|
|
4096
|
+
* @throws {SandboxError} If the sandbox terminates during startup or the timeout elapses.
|
|
4097
|
+
*/
|
|
3765
4098
|
async createAndConnect(options) {
|
|
3766
4099
|
const startupTimeout = options?.startupTimeout ?? 60;
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
result = await this.create(options);
|
|
3772
|
-
}
|
|
3773
|
-
if (result.status === "running" /* RUNNING */) {
|
|
3774
|
-
const sandbox = this.connect(result.sandboxId, options?.proxyUrl, result.routingHint);
|
|
4100
|
+
const result = options?.poolId != null ? await this.claim(options.poolId) : await this.create(options);
|
|
4101
|
+
const requestedName = options?.poolId != null ? null : options?.name ?? null;
|
|
4102
|
+
const finishConnect = (routingHint, name) => {
|
|
4103
|
+
const sandbox = this.connect(result.sandboxId, options?.proxyUrl, routingHint);
|
|
3775
4104
|
sandbox._setOwner(this);
|
|
4105
|
+
sandbox.traceId = result.traceId;
|
|
4106
|
+
sandbox._setLifecycleIdentifier(result.sandboxId);
|
|
4107
|
+
sandbox._setName(name ?? requestedName);
|
|
3776
4108
|
return sandbox;
|
|
4109
|
+
};
|
|
4110
|
+
if (result.status === "running" /* RUNNING */) {
|
|
4111
|
+
return finishConnect(result.routingHint, result.name);
|
|
3777
4112
|
}
|
|
3778
4113
|
const deadline = Date.now() + startupTimeout * 1e3;
|
|
3779
4114
|
while (Date.now() < deadline) {
|
|
3780
4115
|
const info = await this.get(result.sandboxId);
|
|
3781
4116
|
if (info.status === "running" /* RUNNING */) {
|
|
3782
|
-
|
|
3783
|
-
sandbox._setOwner(this);
|
|
3784
|
-
return sandbox;
|
|
4117
|
+
return finishConnect(info.routingHint, info.name);
|
|
3785
4118
|
}
|
|
3786
4119
|
if (info.status === "terminated" /* TERMINATED */) {
|
|
3787
4120
|
throw new SandboxError(
|
|
@@ -4527,7 +4860,8 @@ async function createSandboxImage(source, options = {}, deps = {}) {
|
|
|
4527
4860
|
sandbox = await client.createAndConnect({
|
|
4528
4861
|
...plan.baseImage == null ? {} : { image: plan.baseImage },
|
|
4529
4862
|
cpus: options.cpus ?? 2,
|
|
4530
|
-
memoryMb: options.memoryMb ?? 4096
|
|
4863
|
+
memoryMb: options.memoryMb ?? 4096,
|
|
4864
|
+
...options.diskMb != null ? { diskMb: options.diskMb } : {}
|
|
4531
4865
|
});
|
|
4532
4866
|
emit({
|
|
4533
4867
|
type: "status",
|
|
@@ -4540,8 +4874,7 @@ async function createSandboxImage(source, options = {}, deps = {}) {
|
|
|
4540
4874
|
});
|
|
4541
4875
|
emit({
|
|
4542
4876
|
type: "snapshot_created",
|
|
4543
|
-
snapshot_id: snapshot.snapshotId
|
|
4544
|
-
snapshot_uri: snapshot.snapshotUri ?? null
|
|
4877
|
+
snapshot_id: snapshot.snapshotId
|
|
4545
4878
|
});
|
|
4546
4879
|
if (!snapshot.snapshotUri) {
|
|
4547
4880
|
throw new Error(
|
|
@@ -4585,27 +4918,33 @@ async function runCreateSandboxImageCli(argv = process.argv.slice(2)) {
|
|
|
4585
4918
|
name: { type: "string", short: "n" },
|
|
4586
4919
|
cpus: { type: "string" },
|
|
4587
4920
|
memory: { type: "string" },
|
|
4921
|
+
disk: { type: "string" },
|
|
4588
4922
|
public: { type: "boolean", default: false }
|
|
4589
4923
|
}
|
|
4590
4924
|
});
|
|
4591
4925
|
const dockerfilePath = parsed.positionals[0];
|
|
4592
4926
|
if (!dockerfilePath) {
|
|
4593
|
-
throw new Error("Usage: tensorlake-create-sandbox-image <dockerfile_path> [--name NAME] [--cpus N] [--memory MB] [--public]");
|
|
4927
|
+
throw new Error("Usage: tensorlake-create-sandbox-image <dockerfile_path> [--name NAME] [--cpus N] [--memory MB] [--disk GB] [--public]");
|
|
4594
4928
|
}
|
|
4595
4929
|
const cpus = parsed.values.cpus != null ? Number(parsed.values.cpus) : void 0;
|
|
4596
4930
|
const memoryMb = parsed.values.memory != null ? Number(parsed.values.memory) : void 0;
|
|
4931
|
+
const diskGb = parsed.values.disk != null ? Number(parsed.values.disk) : void 0;
|
|
4597
4932
|
if (cpus != null && !Number.isFinite(cpus)) {
|
|
4598
4933
|
throw new Error(`Invalid --cpus value: ${parsed.values.cpus}`);
|
|
4599
4934
|
}
|
|
4600
4935
|
if (memoryMb != null && !Number.isInteger(memoryMb)) {
|
|
4601
4936
|
throw new Error(`Invalid --memory value: ${parsed.values.memory}`);
|
|
4602
4937
|
}
|
|
4938
|
+
if (diskGb != null && !Number.isInteger(diskGb)) {
|
|
4939
|
+
throw new Error(`Invalid --disk value: ${parsed.values.disk}`);
|
|
4940
|
+
}
|
|
4603
4941
|
await createSandboxImage(
|
|
4604
4942
|
dockerfilePath,
|
|
4605
4943
|
{
|
|
4606
4944
|
registeredName: parsed.values.name,
|
|
4607
4945
|
cpus,
|
|
4608
4946
|
memoryMb,
|
|
4947
|
+
diskMb: diskGb != null ? diskGb * 1024 : void 0,
|
|
4609
4948
|
isPublic: parsed.values.public
|
|
4610
4949
|
},
|
|
4611
4950
|
{ emit: ndjsonStdoutEmit }
|