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.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
1
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
3
|
var __esm = (fn, res) => function __init() {
|
|
3
4
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
5
|
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
5
10
|
|
|
6
11
|
// src/models.ts
|
|
7
12
|
function snakeToCamel(str) {
|
|
@@ -52,10 +57,11 @@ var init_models = __esm({
|
|
|
52
57
|
});
|
|
53
58
|
|
|
54
59
|
// src/defaults.ts
|
|
55
|
-
var API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TIMEOUT_MS, MAX_RETRIES, RETRY_BACKOFF_MS, RETRYABLE_STATUS_CODES;
|
|
60
|
+
var SDK_VERSION, API_URL, API_KEY, NAMESPACE, SANDBOX_PROXY_URL, DEFAULT_HTTP_TIMEOUT_MS, MAX_RETRIES, RETRY_BACKOFF_MS, RETRYABLE_STATUS_CODES;
|
|
56
61
|
var init_defaults = __esm({
|
|
57
62
|
"src/defaults.ts"() {
|
|
58
63
|
"use strict";
|
|
64
|
+
SDK_VERSION = "0.4.49";
|
|
59
65
|
API_URL = process.env.TENSORLAKE_API_URL ?? "https://api.tensorlake.ai";
|
|
60
66
|
API_KEY = process.env.TENSORLAKE_API_KEY ?? void 0;
|
|
61
67
|
NAMESPACE = process.env.INDEXIFY_NAMESPACE ?? "default";
|
|
@@ -134,6 +140,12 @@ import {
|
|
|
134
140
|
fetch as undiciFetch,
|
|
135
141
|
setGlobalDispatcher
|
|
136
142
|
} from "undici";
|
|
143
|
+
function withTraceId(value, traceId) {
|
|
144
|
+
if (value == null) {
|
|
145
|
+
return { traceId };
|
|
146
|
+
}
|
|
147
|
+
return Object.assign(value, { traceId });
|
|
148
|
+
}
|
|
137
149
|
function hasHeader(headers, name) {
|
|
138
150
|
const lowered = name.toLowerCase();
|
|
139
151
|
return Object.keys(headers).some((key) => key.toLowerCase() === lowered);
|
|
@@ -202,7 +214,7 @@ var init_http = __esm({
|
|
|
202
214
|
allowH2: true
|
|
203
215
|
})
|
|
204
216
|
);
|
|
205
|
-
HttpClient = class {
|
|
217
|
+
HttpClient = class _HttpClient {
|
|
206
218
|
baseUrl;
|
|
207
219
|
headers;
|
|
208
220
|
maxRetries;
|
|
@@ -215,7 +227,9 @@ var init_http = __esm({
|
|
|
215
227
|
this.maxRetries = options.maxRetries ?? MAX_RETRIES;
|
|
216
228
|
this.retryBackoffMs = options.retryBackoffMs ?? RETRY_BACKOFF_MS;
|
|
217
229
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
|
|
218
|
-
this.headers = {
|
|
230
|
+
this.headers = {
|
|
231
|
+
"User-Agent": `tensorlake-typescript-sdk/${SDK_VERSION}`
|
|
232
|
+
};
|
|
219
233
|
if (options.apiKey) {
|
|
220
234
|
this.headers["Authorization"] = `Bearer ${options.apiKey}`;
|
|
221
235
|
}
|
|
@@ -244,8 +258,8 @@ var init_http = __esm({
|
|
|
244
258
|
signal: options?.signal
|
|
245
259
|
});
|
|
246
260
|
const text = await response.text();
|
|
247
|
-
|
|
248
|
-
return
|
|
261
|
+
const data = text ? JSON.parse(text) : void 0;
|
|
262
|
+
return withTraceId(data, response.traceId);
|
|
249
263
|
}
|
|
250
264
|
/** Make a request returning raw bytes. */
|
|
251
265
|
async requestBytes(method, path2, options) {
|
|
@@ -259,7 +273,7 @@ var init_http = __esm({
|
|
|
259
273
|
signal: options?.signal
|
|
260
274
|
});
|
|
261
275
|
const buffer = await response.arrayBuffer();
|
|
262
|
-
return new Uint8Array(buffer);
|
|
276
|
+
return withTraceId(new Uint8Array(buffer), response.traceId);
|
|
263
277
|
}
|
|
264
278
|
/** Make a request and return the response body as an SSE stream. */
|
|
265
279
|
async requestStream(method, path2, options) {
|
|
@@ -274,7 +288,7 @@ var init_http = __esm({
|
|
|
274
288
|
"No response body for SSE stream"
|
|
275
289
|
);
|
|
276
290
|
}
|
|
277
|
-
return response.body;
|
|
291
|
+
return withTraceId(response.body, response.traceId);
|
|
278
292
|
}
|
|
279
293
|
/** Make a request and return the raw Response. */
|
|
280
294
|
async requestResponse(method, path2, options) {
|
|
@@ -287,7 +301,7 @@ var init_http = __esm({
|
|
|
287
301
|
headers["Content-Type"] = "application/json";
|
|
288
302
|
}
|
|
289
303
|
const body = hasJsonBody ? JSON.stringify(options?.json) : normalizeRequestBody(options?.body);
|
|
290
|
-
|
|
304
|
+
const { response, traceId } = await this.doFetch(
|
|
291
305
|
method,
|
|
292
306
|
path2,
|
|
293
307
|
body,
|
|
@@ -295,9 +309,18 @@ var init_http = __esm({
|
|
|
295
309
|
options?.signal,
|
|
296
310
|
options?.allowHttpErrors ?? false
|
|
297
311
|
);
|
|
312
|
+
return withTraceId(response, traceId);
|
|
313
|
+
}
|
|
314
|
+
static makeTraceparent() {
|
|
315
|
+
const randomHex = (bytes) => Array.from(crypto.getRandomValues(new Uint8Array(bytes))).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
316
|
+
const traceId = randomHex(16);
|
|
317
|
+
const spanId = randomHex(8);
|
|
318
|
+
return { traceparent: `00-${traceId}-${spanId}-01`, traceId };
|
|
298
319
|
}
|
|
299
320
|
async doFetch(method, path2, body, headers, signal, allowHttpErrors = false) {
|
|
300
321
|
const url = `${this.baseUrl}${path2}`;
|
|
322
|
+
const { traceparent, traceId } = _HttpClient.makeTraceparent();
|
|
323
|
+
headers["traceparent"] = traceparent;
|
|
301
324
|
let lastError;
|
|
302
325
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
303
326
|
if (attempt > 0) {
|
|
@@ -318,7 +341,7 @@ var init_http = __esm({
|
|
|
318
341
|
signal: combinedSignal
|
|
319
342
|
});
|
|
320
343
|
clearTimeout(timeoutId);
|
|
321
|
-
if (response.ok) return response;
|
|
344
|
+
if (response.ok) return { response, traceId };
|
|
322
345
|
if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < this.maxRetries) {
|
|
323
346
|
lastError = new RemoteAPIError(
|
|
324
347
|
response.status,
|
|
@@ -327,7 +350,7 @@ var init_http = __esm({
|
|
|
327
350
|
continue;
|
|
328
351
|
}
|
|
329
352
|
if (allowHttpErrors) {
|
|
330
|
-
return response;
|
|
353
|
+
return { response, traceId };
|
|
331
354
|
}
|
|
332
355
|
const errorBody = await response.text().catch(() => "");
|
|
333
356
|
throwMappedError(response.status, errorBody, path2);
|
|
@@ -3074,6 +3097,7 @@ var init_sandbox = __esm({
|
|
|
3074
3097
|
};
|
|
3075
3098
|
Sandbox = class {
|
|
3076
3099
|
sandboxId;
|
|
3100
|
+
traceId = null;
|
|
3077
3101
|
http;
|
|
3078
3102
|
baseUrl;
|
|
3079
3103
|
wsHeaders;
|
|
@@ -3111,9 +3135,126 @@ var init_sandbox = __esm({
|
|
|
3111
3135
|
this.ownsSandbox = true;
|
|
3112
3136
|
this.lifecycleClient = client;
|
|
3113
3137
|
}
|
|
3138
|
+
// --- Static factory methods ---
|
|
3139
|
+
/**
|
|
3140
|
+
* Create a new sandbox and return a connected, running handle.
|
|
3141
|
+
*
|
|
3142
|
+
* Covers both fresh sandbox creation and restore-from-snapshot (set
|
|
3143
|
+
* `snapshotId`). Blocks until the sandbox is `Running`.
|
|
3144
|
+
*/
|
|
3145
|
+
static async create(options) {
|
|
3146
|
+
const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3147
|
+
const client = new SandboxClient2(
|
|
3148
|
+
options,
|
|
3149
|
+
/* _internal */
|
|
3150
|
+
true
|
|
3151
|
+
);
|
|
3152
|
+
const sandbox = await client.createAndConnect(options);
|
|
3153
|
+
sandbox.lifecycleClient = client;
|
|
3154
|
+
return sandbox;
|
|
3155
|
+
}
|
|
3156
|
+
/**
|
|
3157
|
+
* Attach to an existing sandbox and return a connected handle.
|
|
3158
|
+
*
|
|
3159
|
+
* Verifies the sandbox exists via a server GET call, then returns a handle
|
|
3160
|
+
* in whatever state the sandbox is in. Does **not** auto-resume a suspended
|
|
3161
|
+
* sandbox — call `sandbox.resume()` explicitly.
|
|
3162
|
+
*/
|
|
3163
|
+
static async connect(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
|
+
await client.get(options.sandboxId);
|
|
3171
|
+
const sandbox = client.connect(options.sandboxId, options.proxyUrl, options.routingHint);
|
|
3172
|
+
sandbox.lifecycleClient = client;
|
|
3173
|
+
return sandbox;
|
|
3174
|
+
}
|
|
3175
|
+
// --- Static snapshot management ---
|
|
3176
|
+
/** Get information about a snapshot by ID. No sandbox handle needed. */
|
|
3177
|
+
static async getSnapshot(snapshotId, 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
|
+
return client.getSnapshot(snapshotId);
|
|
3185
|
+
}
|
|
3186
|
+
/** Delete a snapshot by ID. No sandbox handle needed. */
|
|
3187
|
+
static async deleteSnapshot(snapshotId, options) {
|
|
3188
|
+
const { SandboxClient: SandboxClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3189
|
+
const client = new SandboxClient2(
|
|
3190
|
+
options,
|
|
3191
|
+
/* _internal */
|
|
3192
|
+
true
|
|
3193
|
+
);
|
|
3194
|
+
await client.deleteSnapshot(snapshotId);
|
|
3195
|
+
}
|
|
3196
|
+
// --- Instance lifecycle methods ---
|
|
3197
|
+
requireLifecycleClient(operation) {
|
|
3198
|
+
if (!this.lifecycleClient) {
|
|
3199
|
+
throw new SandboxError(
|
|
3200
|
+
`Cannot ${operation}: no lifecycle client available. Use Sandbox.create() or Sandbox.connect() to get a lifecycle-aware handle.`
|
|
3201
|
+
);
|
|
3202
|
+
}
|
|
3203
|
+
return this.lifecycleClient;
|
|
3204
|
+
}
|
|
3205
|
+
/**
|
|
3206
|
+
* Suspend this sandbox.
|
|
3207
|
+
*
|
|
3208
|
+
* By default blocks until the sandbox is fully `Suspended`. Pass
|
|
3209
|
+
* `{ wait: false }` for fire-and-return.
|
|
3210
|
+
*/
|
|
3211
|
+
async suspend(options) {
|
|
3212
|
+
const client = this.requireLifecycleClient("suspend");
|
|
3213
|
+
await client.suspend(this.sandboxId, options);
|
|
3214
|
+
}
|
|
3215
|
+
/**
|
|
3216
|
+
* Resume this sandbox.
|
|
3217
|
+
*
|
|
3218
|
+
* By default blocks until the sandbox is `Running` and routable. Pass
|
|
3219
|
+
* `{ wait: false }` for fire-and-return.
|
|
3220
|
+
*/
|
|
3221
|
+
async resume(options) {
|
|
3222
|
+
const client = this.requireLifecycleClient("resume");
|
|
3223
|
+
await client.resume(this.sandboxId, options);
|
|
3224
|
+
}
|
|
3225
|
+
/**
|
|
3226
|
+
* Create a snapshot of this sandbox's filesystem and wait for it to
|
|
3227
|
+
* be committed.
|
|
3228
|
+
*
|
|
3229
|
+
* By default blocks until the snapshot artifact is ready and returns
|
|
3230
|
+
* the completed `SnapshotInfo`. Pass `{ wait: false }` to fire-and-return
|
|
3231
|
+
* (returns `undefined`).
|
|
3232
|
+
*/
|
|
3233
|
+
async checkpoint(options) {
|
|
3234
|
+
const client = this.requireLifecycleClient("checkpoint");
|
|
3235
|
+
if (options?.wait === false) {
|
|
3236
|
+
await client.snapshot(this.sandboxId, { contentMode: options.contentMode });
|
|
3237
|
+
return void 0;
|
|
3238
|
+
}
|
|
3239
|
+
return client.snapshotAndWait(this.sandboxId, {
|
|
3240
|
+
timeout: options?.timeout,
|
|
3241
|
+
pollInterval: options?.pollInterval,
|
|
3242
|
+
contentMode: options?.contentMode
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3245
|
+
/**
|
|
3246
|
+
* List snapshots taken from this sandbox.
|
|
3247
|
+
*/
|
|
3248
|
+
async listSnapshots() {
|
|
3249
|
+
const client = this.requireLifecycleClient("listSnapshots");
|
|
3250
|
+
const all = await client.listSnapshots();
|
|
3251
|
+
return all.filter((s) => s.sandboxId === this.sandboxId);
|
|
3252
|
+
}
|
|
3253
|
+
/** Close the HTTP client. The sandbox keeps running. */
|
|
3114
3254
|
close() {
|
|
3115
3255
|
this.http.close();
|
|
3116
3256
|
}
|
|
3257
|
+
/** Terminate the sandbox and release all resources. */
|
|
3117
3258
|
async terminate() {
|
|
3118
3259
|
const client = this.lifecycleClient;
|
|
3119
3260
|
this.ownsSandbox = false;
|
|
@@ -3166,6 +3307,14 @@ var init_sandbox = __esm({
|
|
|
3166
3307
|
};
|
|
3167
3308
|
}
|
|
3168
3309
|
// --- Process management ---
|
|
3310
|
+
/**
|
|
3311
|
+
* Start a process in the sandbox without waiting for it to exit.
|
|
3312
|
+
*
|
|
3313
|
+
* Returns a `ProcessInfo` with the assigned `pid`. Use `getProcess()` to
|
|
3314
|
+
* poll status, or `followStdout()` / `followOutput()` to stream output
|
|
3315
|
+
* until the process exits. Use `run()` instead to block until completion
|
|
3316
|
+
* and get combined output in one call.
|
|
3317
|
+
*/
|
|
3169
3318
|
async startProcess(command, options) {
|
|
3170
3319
|
const payload = { command };
|
|
3171
3320
|
if (options?.args != null) payload.args = options.args;
|
|
@@ -3187,6 +3336,7 @@ var init_sandbox = __esm({
|
|
|
3187
3336
|
);
|
|
3188
3337
|
return fromSnakeKeys(raw);
|
|
3189
3338
|
}
|
|
3339
|
+
/** List all processes (running and exited) tracked by the sandbox daemon. */
|
|
3190
3340
|
async listProcesses() {
|
|
3191
3341
|
const raw = await this.http.requestJson(
|
|
3192
3342
|
"GET",
|
|
@@ -3194,6 +3344,7 @@ var init_sandbox = __esm({
|
|
|
3194
3344
|
);
|
|
3195
3345
|
return (raw.processes ?? []).map((p) => fromSnakeKeys(p));
|
|
3196
3346
|
}
|
|
3347
|
+
/** Get current status and metadata for a process by PID. */
|
|
3197
3348
|
async getProcess(pid) {
|
|
3198
3349
|
const raw = await this.http.requestJson(
|
|
3199
3350
|
"GET",
|
|
@@ -3201,9 +3352,11 @@ var init_sandbox = __esm({
|
|
|
3201
3352
|
);
|
|
3202
3353
|
return fromSnakeKeys(raw);
|
|
3203
3354
|
}
|
|
3355
|
+
/** Send SIGKILL to a process. */
|
|
3204
3356
|
async killProcess(pid) {
|
|
3205
3357
|
await this.http.requestJson("DELETE", `/api/v1/processes/${pid}`);
|
|
3206
3358
|
}
|
|
3359
|
+
/** Send an arbitrary signal to a process (e.g. `15` for SIGTERM, `9` for SIGKILL). */
|
|
3207
3360
|
async sendSignal(pid, signal) {
|
|
3208
3361
|
const raw = await this.http.requestJson(
|
|
3209
3362
|
"POST",
|
|
@@ -3213,15 +3366,18 @@ var init_sandbox = __esm({
|
|
|
3213
3366
|
return fromSnakeKeys(raw);
|
|
3214
3367
|
}
|
|
3215
3368
|
// --- Process I/O ---
|
|
3369
|
+
/** Write bytes to a process's stdin. The process must have been started with `stdinMode: StdinMode.PIPE`. */
|
|
3216
3370
|
async writeStdin(pid, data) {
|
|
3217
3371
|
await this.http.requestBytes("POST", `/api/v1/processes/${pid}/stdin`, {
|
|
3218
3372
|
body: data,
|
|
3219
3373
|
contentType: "application/octet-stream"
|
|
3220
3374
|
});
|
|
3221
3375
|
}
|
|
3376
|
+
/** Close a process's stdin pipe, signalling EOF to the process. */
|
|
3222
3377
|
async closeStdin(pid) {
|
|
3223
3378
|
await this.http.requestJson("POST", `/api/v1/processes/${pid}/stdin/close`);
|
|
3224
3379
|
}
|
|
3380
|
+
/** Return all captured stdout lines produced so far by a process. */
|
|
3225
3381
|
async getStdout(pid) {
|
|
3226
3382
|
const raw = await this.http.requestJson(
|
|
3227
3383
|
"GET",
|
|
@@ -3229,6 +3385,7 @@ var init_sandbox = __esm({
|
|
|
3229
3385
|
);
|
|
3230
3386
|
return fromSnakeKeys(raw);
|
|
3231
3387
|
}
|
|
3388
|
+
/** Return all captured stderr lines produced so far by a process. */
|
|
3232
3389
|
async getStderr(pid) {
|
|
3233
3390
|
const raw = await this.http.requestJson(
|
|
3234
3391
|
"GET",
|
|
@@ -3236,6 +3393,7 @@ var init_sandbox = __esm({
|
|
|
3236
3393
|
);
|
|
3237
3394
|
return fromSnakeKeys(raw);
|
|
3238
3395
|
}
|
|
3396
|
+
/** Return all captured stdout+stderr lines produced so far by a process. */
|
|
3239
3397
|
async getOutput(pid) {
|
|
3240
3398
|
const raw = await this.http.requestJson(
|
|
3241
3399
|
"GET",
|
|
@@ -3244,6 +3402,7 @@ var init_sandbox = __esm({
|
|
|
3244
3402
|
return fromSnakeKeys(raw);
|
|
3245
3403
|
}
|
|
3246
3404
|
// --- Streaming (SSE) ---
|
|
3405
|
+
/** Stream stdout events from a process until it exits. Yields one `OutputEvent` per line. */
|
|
3247
3406
|
async *followStdout(pid, options) {
|
|
3248
3407
|
const stream = await this.http.requestStream(
|
|
3249
3408
|
"GET",
|
|
@@ -3257,6 +3416,7 @@ var init_sandbox = __esm({
|
|
|
3257
3416
|
yield fromSnakeKeys(raw);
|
|
3258
3417
|
}
|
|
3259
3418
|
}
|
|
3419
|
+
/** Stream stderr events from a process until it exits. Yields one `OutputEvent` per line. */
|
|
3260
3420
|
async *followStderr(pid, options) {
|
|
3261
3421
|
const stream = await this.http.requestStream(
|
|
3262
3422
|
"GET",
|
|
@@ -3270,6 +3430,7 @@ var init_sandbox = __esm({
|
|
|
3270
3430
|
yield fromSnakeKeys(raw);
|
|
3271
3431
|
}
|
|
3272
3432
|
}
|
|
3433
|
+
/** Stream combined stdout+stderr events from a process until it exits. Yields one `OutputEvent` per line. */
|
|
3273
3434
|
async *followOutput(pid, options) {
|
|
3274
3435
|
const stream = await this.http.requestStream(
|
|
3275
3436
|
"GET",
|
|
@@ -3284,12 +3445,14 @@ var init_sandbox = __esm({
|
|
|
3284
3445
|
}
|
|
3285
3446
|
}
|
|
3286
3447
|
// --- File operations ---
|
|
3448
|
+
/** Read a file from the sandbox and return its raw bytes. */
|
|
3287
3449
|
async readFile(path2) {
|
|
3288
3450
|
return this.http.requestBytes(
|
|
3289
3451
|
"GET",
|
|
3290
3452
|
`/api/v1/files?path=${encodeURIComponent(path2)}`
|
|
3291
3453
|
);
|
|
3292
3454
|
}
|
|
3455
|
+
/** Write raw bytes to a file in the sandbox, creating it if it does not exist. */
|
|
3293
3456
|
async writeFile(path2, content) {
|
|
3294
3457
|
await this.http.requestBytes(
|
|
3295
3458
|
"PUT",
|
|
@@ -3297,12 +3460,14 @@ var init_sandbox = __esm({
|
|
|
3297
3460
|
{ body: content, contentType: "application/octet-stream" }
|
|
3298
3461
|
);
|
|
3299
3462
|
}
|
|
3463
|
+
/** Delete a file from the sandbox. */
|
|
3300
3464
|
async deleteFile(path2) {
|
|
3301
3465
|
await this.http.requestJson(
|
|
3302
3466
|
"DELETE",
|
|
3303
3467
|
`/api/v1/files?path=${encodeURIComponent(path2)}`
|
|
3304
3468
|
);
|
|
3305
3469
|
}
|
|
3470
|
+
/** List the contents of a directory in the sandbox. */
|
|
3306
3471
|
async listDirectory(path2) {
|
|
3307
3472
|
const raw = await this.http.requestJson(
|
|
3308
3473
|
"GET",
|
|
@@ -3311,6 +3476,7 @@ var init_sandbox = __esm({
|
|
|
3311
3476
|
return fromSnakeKeys(raw);
|
|
3312
3477
|
}
|
|
3313
3478
|
// --- PTY ---
|
|
3479
|
+
/** Create an interactive PTY session. Returns a `sessionId` and `token` for WebSocket connection via `connectPty()`. */
|
|
3314
3480
|
async createPtySession(options) {
|
|
3315
3481
|
const payload = {
|
|
3316
3482
|
command: options.command,
|
|
@@ -3327,6 +3493,7 @@ var init_sandbox = __esm({
|
|
|
3327
3493
|
);
|
|
3328
3494
|
return fromSnakeKeys(raw);
|
|
3329
3495
|
}
|
|
3496
|
+
/** Create a PTY session and connect to it immediately. Cleans up the session if the WebSocket connection fails. */
|
|
3330
3497
|
async createPty(options) {
|
|
3331
3498
|
const { onData, onExit, ...createOptions } = options;
|
|
3332
3499
|
const session = await this.createPtySession(createOptions);
|
|
@@ -3340,6 +3507,7 @@ var init_sandbox = __esm({
|
|
|
3340
3507
|
throw error;
|
|
3341
3508
|
}
|
|
3342
3509
|
}
|
|
3510
|
+
/** Attach to an existing PTY session by ID and token and return a connected `Pty` handle. */
|
|
3343
3511
|
async connectPty(sessionId, token, options) {
|
|
3344
3512
|
const wsUrl = new URL(this.ptyWsUrl(sessionId, token));
|
|
3345
3513
|
const authToken = wsUrl.searchParams.get("token") ?? token;
|
|
@@ -3364,6 +3532,7 @@ var init_sandbox = __esm({
|
|
|
3364
3532
|
await pty.connect();
|
|
3365
3533
|
return pty;
|
|
3366
3534
|
}
|
|
3535
|
+
/** Open a TCP tunnel to a port inside the sandbox and return the local listener. */
|
|
3367
3536
|
async createTunnel(remotePort, options) {
|
|
3368
3537
|
return TcpTunnel.listen({
|
|
3369
3538
|
baseUrl: this.baseUrl,
|
|
@@ -3374,6 +3543,7 @@ var init_sandbox = __esm({
|
|
|
3374
3543
|
connectTimeout: options?.connectTimeout
|
|
3375
3544
|
});
|
|
3376
3545
|
}
|
|
3546
|
+
/** Connect to a sandbox VNC session for programmatic desktop control. */
|
|
3377
3547
|
async connectDesktop(options) {
|
|
3378
3548
|
return Desktop.connect({
|
|
3379
3549
|
baseUrl: this.baseUrl,
|
|
@@ -3396,6 +3566,7 @@ var init_sandbox = __esm({
|
|
|
3396
3566
|
return `${wsBase}/api/v1/pty/${sessionId}/ws?token=${token}`;
|
|
3397
3567
|
}
|
|
3398
3568
|
// --- Health ---
|
|
3569
|
+
/** Check the sandbox daemon health. */
|
|
3399
3570
|
async health() {
|
|
3400
3571
|
const raw = await this.http.requestJson(
|
|
3401
3572
|
"GET",
|
|
@@ -3403,6 +3574,7 @@ var init_sandbox = __esm({
|
|
|
3403
3574
|
);
|
|
3404
3575
|
return fromSnakeKeys(raw);
|
|
3405
3576
|
}
|
|
3577
|
+
/** Get sandbox daemon info (version, uptime, process counts). */
|
|
3406
3578
|
async info() {
|
|
3407
3579
|
const raw = await this.http.requestJson(
|
|
3408
3580
|
"GET",
|
|
@@ -3415,6 +3587,10 @@ var init_sandbox = __esm({
|
|
|
3415
3587
|
});
|
|
3416
3588
|
|
|
3417
3589
|
// src/client.ts
|
|
3590
|
+
var client_exports = {};
|
|
3591
|
+
__export(client_exports, {
|
|
3592
|
+
SandboxClient: () => SandboxClient
|
|
3593
|
+
});
|
|
3418
3594
|
function sleep2(ms) {
|
|
3419
3595
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3420
3596
|
}
|
|
@@ -3451,7 +3627,13 @@ var init_client = __esm({
|
|
|
3451
3627
|
projectId;
|
|
3452
3628
|
namespace;
|
|
3453
3629
|
local;
|
|
3454
|
-
|
|
3630
|
+
/** @internal Pass `true` to suppress the deprecation warning when used by `Sandbox.create()` / `Sandbox.connect()`. */
|
|
3631
|
+
constructor(options, _internal = false) {
|
|
3632
|
+
if (!_internal) {
|
|
3633
|
+
console.warn(
|
|
3634
|
+
"[tensorlake] SandboxClient is deprecated; use Sandbox.create() / Sandbox.connect() instead."
|
|
3635
|
+
);
|
|
3636
|
+
}
|
|
3455
3637
|
this.apiUrl = options?.apiUrl ?? API_URL;
|
|
3456
3638
|
this.apiKey = options?.apiKey ?? API_KEY;
|
|
3457
3639
|
this.organizationId = options?.organizationId;
|
|
@@ -3491,6 +3673,7 @@ var init_client = __esm({
|
|
|
3491
3673
|
return lifecyclePath(subpath, this.local, this.namespace);
|
|
3492
3674
|
}
|
|
3493
3675
|
// --- Sandbox CRUD ---
|
|
3676
|
+
/** Create a new sandbox. Returns immediately; the sandbox may still be starting. Use `createAndConnect()` for a blocking, ready-to-use handle. */
|
|
3494
3677
|
async create(options) {
|
|
3495
3678
|
const body = {
|
|
3496
3679
|
resources: {
|
|
@@ -3517,8 +3700,10 @@ var init_client = __esm({
|
|
|
3517
3700
|
this.path("sandboxes"),
|
|
3518
3701
|
{ body }
|
|
3519
3702
|
);
|
|
3520
|
-
|
|
3703
|
+
const result = fromSnakeKeys(raw, "sandboxId");
|
|
3704
|
+
return Object.assign(result, { traceId: raw.traceId });
|
|
3521
3705
|
}
|
|
3706
|
+
/** Get current state and metadata for a sandbox by ID. */
|
|
3522
3707
|
async get(sandboxId) {
|
|
3523
3708
|
const raw = await this.http.requestJson(
|
|
3524
3709
|
"GET",
|
|
@@ -3526,6 +3711,7 @@ var init_client = __esm({
|
|
|
3526
3711
|
);
|
|
3527
3712
|
return fromSnakeKeys(raw, "sandboxId");
|
|
3528
3713
|
}
|
|
3714
|
+
/** List all sandboxes in the namespace. */
|
|
3529
3715
|
async list() {
|
|
3530
3716
|
const raw = await this.http.requestJson(
|
|
3531
3717
|
"GET",
|
|
@@ -3535,6 +3721,7 @@ var init_client = __esm({
|
|
|
3535
3721
|
(s) => fromSnakeKeys(s, "sandboxId")
|
|
3536
3722
|
);
|
|
3537
3723
|
}
|
|
3724
|
+
/** Update sandbox properties such as name, exposed ports, and proxy auth settings. */
|
|
3538
3725
|
async update(sandboxId, options) {
|
|
3539
3726
|
const body = {};
|
|
3540
3727
|
if (options.name != null) body.name = options.name;
|
|
@@ -3554,6 +3741,7 @@ var init_client = __esm({
|
|
|
3554
3741
|
);
|
|
3555
3742
|
return fromSnakeKeys(raw, "sandboxId");
|
|
3556
3743
|
}
|
|
3744
|
+
/** Get the current proxy port settings for a sandbox. */
|
|
3557
3745
|
async getPortAccess(sandboxId) {
|
|
3558
3746
|
const info = await this.get(sandboxId);
|
|
3559
3747
|
return {
|
|
@@ -3562,6 +3750,7 @@ var init_client = __esm({
|
|
|
3562
3750
|
sandboxUrl: info.sandboxUrl
|
|
3563
3751
|
};
|
|
3564
3752
|
}
|
|
3753
|
+
/** Add one or more user ports to the sandbox proxy allowlist. */
|
|
3565
3754
|
async exposePorts(sandboxId, ports, options) {
|
|
3566
3755
|
const requestedPorts = normalizeUserPorts(ports);
|
|
3567
3756
|
const current = await this.getPortAccess(sandboxId);
|
|
@@ -3574,6 +3763,7 @@ var init_client = __esm({
|
|
|
3574
3763
|
exposedPorts: desiredPorts
|
|
3575
3764
|
});
|
|
3576
3765
|
}
|
|
3766
|
+
/** Remove one or more user ports from the sandbox proxy allowlist. */
|
|
3577
3767
|
async unexposePorts(sandboxId, ports) {
|
|
3578
3768
|
const requestedPorts = normalizeUserPorts(ports);
|
|
3579
3769
|
const current = await this.getPortAccess(sandboxId);
|
|
@@ -3584,32 +3774,98 @@ var init_client = __esm({
|
|
|
3584
3774
|
exposedPorts: desiredPorts
|
|
3585
3775
|
});
|
|
3586
3776
|
}
|
|
3777
|
+
/** Terminate and delete a sandbox. */
|
|
3587
3778
|
async delete(sandboxId) {
|
|
3588
3779
|
await this.http.requestJson(
|
|
3589
3780
|
"DELETE",
|
|
3590
3781
|
this.path(`sandboxes/${sandboxId}`)
|
|
3591
3782
|
);
|
|
3592
3783
|
}
|
|
3593
|
-
|
|
3784
|
+
/**
|
|
3785
|
+
* Suspend a named sandbox, preserving its state for later resume.
|
|
3786
|
+
*
|
|
3787
|
+
* Only sandboxes created with a `name` can be suspended; ephemeral sandboxes
|
|
3788
|
+
* cannot. By default blocks until the sandbox is fully `Suspended`. Pass
|
|
3789
|
+
* `{ wait: false }` to return immediately after the request is sent
|
|
3790
|
+
* (fire-and-return); the server processes the suspend asynchronously.
|
|
3791
|
+
*
|
|
3792
|
+
* @param sandboxId - ID or name of the sandbox.
|
|
3793
|
+
* @param options.wait - If `true` (default), poll until `Suspended`. Pass `false` to fire-and-return.
|
|
3794
|
+
* @param options.timeout - Max seconds to wait when `wait=true` (default 300).
|
|
3795
|
+
* @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
|
|
3796
|
+
* @throws {SandboxError} If `wait=true` and the sandbox does not reach `Suspended` within `timeout`.
|
|
3797
|
+
*/
|
|
3798
|
+
async suspend(sandboxId, options) {
|
|
3594
3799
|
await this.http.requestResponse(
|
|
3595
3800
|
"POST",
|
|
3596
3801
|
this.path(`sandboxes/${sandboxId}/suspend`)
|
|
3597
3802
|
);
|
|
3803
|
+
if (options?.wait === false) return;
|
|
3804
|
+
const timeout = options?.timeout ?? 300;
|
|
3805
|
+
const pollInterval = options?.pollInterval ?? 1;
|
|
3806
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
3807
|
+
while (Date.now() < deadline) {
|
|
3808
|
+
const info = await this.get(sandboxId);
|
|
3809
|
+
if (info.status === "suspended" /* SUSPENDED */) return;
|
|
3810
|
+
if (info.status === "terminated" /* TERMINATED */) {
|
|
3811
|
+
throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for suspend`);
|
|
3812
|
+
}
|
|
3813
|
+
await sleep2(pollInterval * 1e3);
|
|
3814
|
+
}
|
|
3815
|
+
throw new SandboxError(`Sandbox ${sandboxId} did not suspend within ${timeout}s`);
|
|
3598
3816
|
}
|
|
3599
|
-
|
|
3817
|
+
/**
|
|
3818
|
+
* Resume a suspended sandbox and bring it back to `Running`.
|
|
3819
|
+
*
|
|
3820
|
+
* By default blocks until the sandbox is `Running` and routable. Pass
|
|
3821
|
+
* `{ wait: false }` to return immediately after the request is sent
|
|
3822
|
+
* (fire-and-return); the server processes the resume asynchronously.
|
|
3823
|
+
*
|
|
3824
|
+
* @param sandboxId - ID or name of the sandbox.
|
|
3825
|
+
* @param options.wait - If `true` (default), poll until `Running`. Pass `false` to fire-and-return.
|
|
3826
|
+
* @param options.timeout - Max seconds to wait when `wait=true` (default 300).
|
|
3827
|
+
* @param options.pollInterval - Seconds between status polls when `wait=true` (default 1).
|
|
3828
|
+
* @throws {SandboxError} If `wait=true` and the sandbox does not reach `Running` within `timeout`.
|
|
3829
|
+
*/
|
|
3830
|
+
async resume(sandboxId, options) {
|
|
3600
3831
|
await this.http.requestResponse(
|
|
3601
3832
|
"POST",
|
|
3602
3833
|
this.path(`sandboxes/${sandboxId}/resume`)
|
|
3603
3834
|
);
|
|
3835
|
+
if (options?.wait === false) return;
|
|
3836
|
+
const timeout = options?.timeout ?? 300;
|
|
3837
|
+
const pollInterval = options?.pollInterval ?? 1;
|
|
3838
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
3839
|
+
while (Date.now() < deadline) {
|
|
3840
|
+
const info = await this.get(sandboxId);
|
|
3841
|
+
if (info.status === "running" /* RUNNING */) return;
|
|
3842
|
+
if (info.status === "terminated" /* TERMINATED */) {
|
|
3843
|
+
throw new SandboxError(`Sandbox ${sandboxId} terminated while waiting for resume`);
|
|
3844
|
+
}
|
|
3845
|
+
await sleep2(pollInterval * 1e3);
|
|
3846
|
+
}
|
|
3847
|
+
throw new SandboxError(`Sandbox ${sandboxId} did not resume within ${timeout}s`);
|
|
3604
3848
|
}
|
|
3849
|
+
/** Claim a warm sandbox from a pool, creating one if no warm containers are available. */
|
|
3605
3850
|
async claim(poolId) {
|
|
3606
3851
|
const raw = await this.http.requestJson(
|
|
3607
3852
|
"POST",
|
|
3608
3853
|
this.path(`sandbox-pools/${poolId}/sandboxes`)
|
|
3609
3854
|
);
|
|
3610
|
-
|
|
3855
|
+
const result = fromSnakeKeys(raw, "sandboxId");
|
|
3856
|
+
return Object.assign(result, { traceId: raw.traceId });
|
|
3611
3857
|
}
|
|
3612
3858
|
// --- Snapshots ---
|
|
3859
|
+
/**
|
|
3860
|
+
* Request a snapshot of a running sandbox's filesystem.
|
|
3861
|
+
*
|
|
3862
|
+
* This call **returns immediately** with a `snapshotId` and `in_progress`
|
|
3863
|
+
* status — the snapshot is created asynchronously. Poll `getSnapshot()` until
|
|
3864
|
+
* `completed` or `failed`, or use `snapshotAndWait()` to block automatically.
|
|
3865
|
+
*
|
|
3866
|
+
* @param options.contentMode - `"filesystem_only"` for cold-boot snapshots (e.g. image builds).
|
|
3867
|
+
* Omit to use the server default (full VM snapshot).
|
|
3868
|
+
*/
|
|
3613
3869
|
async snapshot(sandboxId, options) {
|
|
3614
3870
|
const requestOptions = options?.contentMode != null ? { body: { snapshot_content_mode: options.contentMode } } : void 0;
|
|
3615
3871
|
const raw = await this.http.requestJson(
|
|
@@ -3619,6 +3875,7 @@ var init_client = __esm({
|
|
|
3619
3875
|
);
|
|
3620
3876
|
return fromSnakeKeys(raw, "snapshotId");
|
|
3621
3877
|
}
|
|
3878
|
+
/** Get current status and metadata for a snapshot by ID. */
|
|
3622
3879
|
async getSnapshot(snapshotId) {
|
|
3623
3880
|
const raw = await this.http.requestJson(
|
|
3624
3881
|
"GET",
|
|
@@ -3626,6 +3883,7 @@ var init_client = __esm({
|
|
|
3626
3883
|
);
|
|
3627
3884
|
return fromSnakeKeys(raw, "snapshotId");
|
|
3628
3885
|
}
|
|
3886
|
+
/** List all snapshots in the namespace. */
|
|
3629
3887
|
async listSnapshots() {
|
|
3630
3888
|
const raw = await this.http.requestJson(
|
|
3631
3889
|
"GET",
|
|
@@ -3635,12 +3893,26 @@ var init_client = __esm({
|
|
|
3635
3893
|
(s) => fromSnakeKeys(s, "snapshotId")
|
|
3636
3894
|
);
|
|
3637
3895
|
}
|
|
3896
|
+
/** Delete a snapshot by ID. */
|
|
3638
3897
|
async deleteSnapshot(snapshotId) {
|
|
3639
3898
|
await this.http.requestJson(
|
|
3640
3899
|
"DELETE",
|
|
3641
3900
|
this.path(`snapshots/${snapshotId}`)
|
|
3642
3901
|
);
|
|
3643
3902
|
}
|
|
3903
|
+
/**
|
|
3904
|
+
* Create a snapshot and block until it is committed.
|
|
3905
|
+
*
|
|
3906
|
+
* Combines `snapshot()` with polling `getSnapshot()` until `completed`.
|
|
3907
|
+
* Prefer `sandbox.checkpoint()` on a `Sandbox` handle for the same behavior
|
|
3908
|
+
* without managing the client separately.
|
|
3909
|
+
*
|
|
3910
|
+
* @param sandboxId - ID of the running sandbox to snapshot.
|
|
3911
|
+
* @param options.timeout - Max seconds to wait (default 300).
|
|
3912
|
+
* @param options.pollInterval - Seconds between status polls (default 1).
|
|
3913
|
+
* @param options.contentMode - Content mode passed through to `snapshot()`.
|
|
3914
|
+
* @throws {SandboxError} If the snapshot fails or `timeout` elapses.
|
|
3915
|
+
*/
|
|
3644
3916
|
async snapshotAndWait(sandboxId, options) {
|
|
3645
3917
|
const timeout = options?.timeout ?? 300;
|
|
3646
3918
|
const pollInterval = options?.pollInterval ?? 1;
|
|
@@ -3663,6 +3935,7 @@ var init_client = __esm({
|
|
|
3663
3935
|
);
|
|
3664
3936
|
}
|
|
3665
3937
|
// --- Pools ---
|
|
3938
|
+
/** Create a new sandbox pool with warm pre-booted containers. */
|
|
3666
3939
|
async createPool(options) {
|
|
3667
3940
|
const body = {
|
|
3668
3941
|
image: options.image,
|
|
@@ -3684,6 +3957,7 @@ var init_client = __esm({
|
|
|
3684
3957
|
);
|
|
3685
3958
|
return fromSnakeKeys(raw, "poolId");
|
|
3686
3959
|
}
|
|
3960
|
+
/** Get current state and metadata for a sandbox pool by ID. */
|
|
3687
3961
|
async getPool(poolId) {
|
|
3688
3962
|
const raw = await this.http.requestJson(
|
|
3689
3963
|
"GET",
|
|
@@ -3691,6 +3965,7 @@ var init_client = __esm({
|
|
|
3691
3965
|
);
|
|
3692
3966
|
return fromSnakeKeys(raw, "poolId");
|
|
3693
3967
|
}
|
|
3968
|
+
/** List all sandbox pools in the namespace. */
|
|
3694
3969
|
async listPools() {
|
|
3695
3970
|
const raw = await this.http.requestJson(
|
|
3696
3971
|
"GET",
|
|
@@ -3700,6 +3975,7 @@ var init_client = __esm({
|
|
|
3700
3975
|
(p) => fromSnakeKeys(p, "poolId")
|
|
3701
3976
|
);
|
|
3702
3977
|
}
|
|
3978
|
+
/** Replace the configuration of an existing sandbox pool. */
|
|
3703
3979
|
async updatePool(poolId, options) {
|
|
3704
3980
|
const body = {
|
|
3705
3981
|
image: options.image,
|
|
@@ -3721,6 +3997,7 @@ var init_client = __esm({
|
|
|
3721
3997
|
);
|
|
3722
3998
|
return fromSnakeKeys(raw, "poolId");
|
|
3723
3999
|
}
|
|
4000
|
+
/** Delete a sandbox pool. Fails if the pool has active containers. */
|
|
3724
4001
|
async deletePool(poolId) {
|
|
3725
4002
|
await this.http.requestJson(
|
|
3726
4003
|
"DELETE",
|
|
@@ -3728,6 +4005,7 @@ var init_client = __esm({
|
|
|
3728
4005
|
);
|
|
3729
4006
|
}
|
|
3730
4007
|
// --- Connect ---
|
|
4008
|
+
/** Return a `Sandbox` handle for an existing running sandbox without verifying it exists. */
|
|
3731
4009
|
connect(identifier, proxyUrl, routingHint) {
|
|
3732
4010
|
const resolvedProxy = proxyUrl ?? resolveProxyUrl(this.apiUrl);
|
|
3733
4011
|
return new Sandbox({
|
|
@@ -3739,6 +4017,15 @@ var init_client = __esm({
|
|
|
3739
4017
|
routingHint
|
|
3740
4018
|
});
|
|
3741
4019
|
}
|
|
4020
|
+
/**
|
|
4021
|
+
* Create a sandbox, wait for it to reach `Running`, and return a connected handle.
|
|
4022
|
+
*
|
|
4023
|
+
* Blocks until the sandbox is ready or `startupTimeout` elapses. The returned
|
|
4024
|
+
* `Sandbox` auto-terminates when `terminate()` is called.
|
|
4025
|
+
*
|
|
4026
|
+
* @param options.startupTimeout - Max seconds to wait for `Running` status (default 60).
|
|
4027
|
+
* @throws {SandboxError} If the sandbox terminates during startup or the timeout elapses.
|
|
4028
|
+
*/
|
|
3742
4029
|
async createAndConnect(options) {
|
|
3743
4030
|
const startupTimeout = options?.startupTimeout ?? 60;
|
|
3744
4031
|
let result;
|
|
@@ -3750,6 +4037,7 @@ var init_client = __esm({
|
|
|
3750
4037
|
if (result.status === "running" /* RUNNING */) {
|
|
3751
4038
|
const sandbox = this.connect(result.sandboxId, options?.proxyUrl, result.routingHint);
|
|
3752
4039
|
sandbox._setOwner(this);
|
|
4040
|
+
sandbox.traceId = result.traceId;
|
|
3753
4041
|
return sandbox;
|
|
3754
4042
|
}
|
|
3755
4043
|
const deadline = Date.now() + startupTimeout * 1e3;
|
|
@@ -3758,6 +4046,7 @@ var init_client = __esm({
|
|
|
3758
4046
|
if (info.status === "running" /* RUNNING */) {
|
|
3759
4047
|
const sandbox = this.connect(result.sandboxId, options?.proxyUrl, info.routingHint);
|
|
3760
4048
|
sandbox._setOwner(this);
|
|
4049
|
+
sandbox.traceId = result.traceId;
|
|
3761
4050
|
return sandbox;
|
|
3762
4051
|
}
|
|
3763
4052
|
if (info.status === "terminated" /* TERMINATED */) {
|