withruntime 0.11.0 → 0.11.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/BLAXEL.md +7 -8
- package/DAYTONA.md +7 -8
- package/E2B.md +16 -2
- package/README.md +3 -3
- package/README.src.md +3 -3
- package/VERCEL.md +3 -2
- package/compatibility-lock.json +55 -0
- package/dist/api-defaults.d.ts +1 -1
- package/dist/api-defaults.js +1 -1
- package/dist/blaxel/lookup.d.ts +2 -1
- package/dist/blaxel/lookup.js +2 -1
- package/dist/blaxel/sandbox.d.ts +7 -5
- package/dist/blaxel/sandbox.js +15 -10
- package/dist/cli.js +39 -15
- package/dist/client.d.ts +5 -1
- package/dist/client.js +19 -3
- package/dist/compat/files.js +5 -1
- package/dist/compat/lease.d.ts +1 -0
- package/dist/compat/lease.js +14 -8
- package/dist/credentials.d.ts +9 -0
- package/dist/credentials.js +26 -4
- package/dist/daytona/daytona.js +29 -12
- package/dist/daytona/sandbox.d.ts +8 -3
- package/dist/daytona/sandbox.js +23 -4
- package/dist/e2b/sandbox.d.ts +7 -3
- package/dist/e2b/sandbox.js +21 -7
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +6 -0
- package/dist/identity.js +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/modal/index.js +6 -2
- package/dist/network-cli.js +7 -7
- package/dist/products/browser.d.ts +2 -1
- package/dist/products/browser.js +9 -19
- package/dist/products/desktop.d.ts +2 -1
- package/dist/products/desktop.js +5 -30
- package/dist/products/jobs.d.ts +7 -5
- package/dist/products/jobs.js +7 -3
- package/dist/products/mcp.d.ts +5 -1
- package/dist/products/mcp.js +29 -8
- package/dist/products/network-products.d.ts +1 -1
- package/dist/products/network-products.js +1 -1
- package/dist/products/observability.d.ts +1 -1
- package/dist/products/private-network.d.ts +1 -1
- package/dist/products/secrets.d.ts +2 -2
- package/dist/runloop/index.js +6 -2
- package/dist/sandbox.d.ts +27 -19
- package/dist/sandbox.js +121 -54
- package/dist/tar.d.ts +2 -0
- package/dist/tar.js +74 -16
- package/dist/transport.d.ts +33 -7
- package/dist/transport.js +171 -36
- package/dist/types.d.ts +54 -9
- package/dist/vercel/filesystem.js +3 -1
- package/dist/vercel/sandbox.d.ts +17 -10
- package/dist/vercel/sandbox.js +26 -13
- package/dist/wait.d.ts +12 -0
- package/dist/wait.js +47 -0
- package/package.json +1 -1
package/dist/transport.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ConnectionError, DELIBERATE, errorFor, RuntimeError, WAITS_FOR_ROOM } from "./errors.js";
|
|
1
|
+
import { ConnectionError, DELIBERATE, errorFor, NOTHING_RAN, RuntimeError, WAITS_FOR_ROOM, } from "./errors.js";
|
|
2
2
|
import { describeRoute, envFetch, openWebSocket } from "./proxy.js";
|
|
3
|
-
export const VERSION = "0.11.
|
|
3
|
+
export const VERSION = "0.11.1";
|
|
4
4
|
export const DEFAULT_BASE_URL = "https://api.withruntime.com";
|
|
5
5
|
/** Runtime's API as code inside a Runtime sandbox reaches it: the sandbox's
|
|
6
6
|
* own host sends each request on to DEFAULT_BASE_URL over HTTPS. The API runs
|
|
@@ -8,10 +8,11 @@ export const DEFAULT_BASE_URL = "https://api.withruntime.com";
|
|
|
8
8
|
* because the hop never leaves the machine: it goes from the program to the
|
|
9
9
|
* guest's own proxy and over the sandbox's private channel to its host. */
|
|
10
10
|
export const SANDBOX_BASE_URL = "http://runtime.internal";
|
|
11
|
-
/** Every Runtime sandbox has this file; guest-net.py keeps it current.
|
|
12
|
-
|
|
11
|
+
/** Every Runtime sandbox has this file; guest-net.py keeps it current. Tests
|
|
12
|
+
* move it, so the machine running them does not decide what they see. */
|
|
13
|
+
export const sandboxMarker = { path: "/run/runtime/environment.json" };
|
|
13
14
|
/** True in a Runtime sandbox. Node and Bun only; a browser is never one. */
|
|
14
|
-
export function inRuntimeSandbox(marker =
|
|
15
|
+
export function inRuntimeSandbox(marker = sandboxMarker.path) {
|
|
15
16
|
try {
|
|
16
17
|
const host = globalThis.process;
|
|
17
18
|
return host?.getBuiltinModule?.("node:fs")?.existsSync?.(marker) === true;
|
|
@@ -34,6 +35,18 @@ export function reachable(origin, inSandbox = inRuntimeSandbox) {
|
|
|
34
35
|
function processEnv() {
|
|
35
36
|
return (globalThis.process?.env ?? {});
|
|
36
37
|
}
|
|
38
|
+
/** How long an answer that ran nothing (NOTHING_RAN) is retried for a call
|
|
39
|
+
* that has no deadline of its own. */
|
|
40
|
+
const NOTHING_RAN_MS = 300_000;
|
|
41
|
+
/** How long one attempt waits for its answer to start. The API starts every
|
|
42
|
+
* answer within 120 s: Caddy answers 503 itself past that, and a longer call
|
|
43
|
+
* gets its 200 at 90 s and then its JSON (runtime-late-answer). An attempt
|
|
44
|
+
* with no answer by then is talking to a server that is gone, such as a
|
|
45
|
+
* controller that died with the call in flight while its address moved to
|
|
46
|
+
* the new leader: a client that only waits sends nothing the new leader could
|
|
47
|
+
* refuse, so it waited out the whole deadline and failed (the leader drill on
|
|
48
|
+
* vin-5, 4 October 2026). It is sent again under the same key. */
|
|
49
|
+
export const ANSWER_START_MS = 125_000;
|
|
37
50
|
export function apiOrigin(value) {
|
|
38
51
|
const url = new URL(value);
|
|
39
52
|
// runtime.internal is reserved and never resolves outside a sandbox, so a
|
|
@@ -141,14 +154,23 @@ function slots(maximum) {
|
|
|
141
154
|
},
|
|
142
155
|
};
|
|
143
156
|
}
|
|
157
|
+
/** Connections a client holds at once by default: under the 64 one address
|
|
158
|
+
* may hold to the API (api.md, "Limits"), with room for a second client. */
|
|
159
|
+
export const DEFAULT_MAX_CONNECTIONS = 48;
|
|
160
|
+
/** Connections a client keeps for answers read whole, however many streams
|
|
161
|
+
* are open: a stream's reader that makes a call (an output callback writing a
|
|
162
|
+
* file) never waits behind streams, its own included. */
|
|
163
|
+
const KEPT_FOR_CALLS = 8;
|
|
144
164
|
/** One connection pool, one retry policy, one error shape for every product.
|
|
145
165
|
* Node's and Bun's fetch keep connections alive, so a client reused across
|
|
146
166
|
* calls pays TLS once. Over HTTP/1.1 every call in flight holds its own
|
|
147
|
-
* connection,
|
|
148
|
-
*
|
|
149
|
-
* `maxConnections` at once
|
|
150
|
-
*
|
|
151
|
-
*
|
|
167
|
+
* connection, and one address may hold 64 to the API before it has used a
|
|
168
|
+
* valid key: past that the API's edge holds new ones back until one closes.
|
|
169
|
+
* So a client holds at most `maxConnections` at once,
|
|
170
|
+
* answers and streams together, and the rest queue here, first come first
|
|
171
|
+
* served, and wait rather than fail. Streams (a command's output, a download,
|
|
172
|
+
* a watch) take at most `maxConnections` less KEPT_FOR_CALLS of them. A
|
|
173
|
+
* terminal or tunnel's WebSocket is not counted. */
|
|
152
174
|
export class Transport {
|
|
153
175
|
baseUrl;
|
|
154
176
|
#apiKey;
|
|
@@ -157,9 +179,11 @@ export class Transport {
|
|
|
157
179
|
/** Whether calls go through envFetch, so a failure can name its proxy. */
|
|
158
180
|
#routed;
|
|
159
181
|
#timeoutMs;
|
|
182
|
+
#answerStartMs;
|
|
160
183
|
#maxRetries;
|
|
161
184
|
#client;
|
|
162
185
|
#slots;
|
|
186
|
+
#streams;
|
|
163
187
|
/** What `sandboxes.create` waits for room by default. */
|
|
164
188
|
waitForCapacityMs;
|
|
165
189
|
constructor(options) {
|
|
@@ -175,9 +199,12 @@ export class Transport {
|
|
|
175
199
|
this.#fetch = options.fetch ?? envFetch;
|
|
176
200
|
this.#routed = options.fetch === undefined;
|
|
177
201
|
this.#timeoutMs = options.timeoutMs ?? 300_000;
|
|
202
|
+
this.#answerStartMs = options.answerStartMs ?? ANSWER_START_MS;
|
|
178
203
|
this.#maxRetries = options.maxRetries ?? 4;
|
|
179
204
|
this.#client = options.client ?? `sdk-js/${VERSION}`;
|
|
180
|
-
|
|
205
|
+
const most = Math.max(1, options.maxConnections ?? DEFAULT_MAX_CONNECTIONS);
|
|
206
|
+
this.#slots = slots(most);
|
|
207
|
+
this.#streams = slots(Math.max(1, most - KEPT_FOR_CALLS));
|
|
181
208
|
this.waitForCapacityMs = Math.max(0, options.waitForCapacityMs ?? 120_000);
|
|
182
209
|
}
|
|
183
210
|
/** The key, found once on first use when it was not given. */
|
|
@@ -225,6 +252,7 @@ export class Transport {
|
|
|
225
252
|
return {
|
|
226
253
|
...call,
|
|
227
254
|
timeoutMs: 0,
|
|
255
|
+
...(timeoutMs === 0 ? {} : { deadlineAt: performance.now() + timeoutMs + roomMs }),
|
|
228
256
|
...(signal ? { signal } : {}),
|
|
229
257
|
...(call.method !== "GET"
|
|
230
258
|
? { idempotencyKey: call.idempotencyKey ?? crypto.randomUUID() }
|
|
@@ -251,7 +279,17 @@ export class Transport {
|
|
|
251
279
|
cause,
|
|
252
280
|
});
|
|
253
281
|
}
|
|
254
|
-
|
|
282
|
+
/** Sleeps between tries. A call holding a connection slot (`gate`) lends
|
|
283
|
+
* it out while it sleeps, since a sleep holds no connection, and takes a
|
|
284
|
+
* slot again before it sends: a thousand creates waiting out a full region
|
|
285
|
+
* or a 429 would otherwise keep every other call of this client, the stop
|
|
286
|
+
* that frees room included, queued behind them. */
|
|
287
|
+
async #rest(ms, signal, gate) {
|
|
288
|
+
gate?.lend();
|
|
289
|
+
await sleep(ms, signal);
|
|
290
|
+
await gate?.take();
|
|
291
|
+
}
|
|
292
|
+
async #send(call, consume, gate) {
|
|
255
293
|
let apiKey;
|
|
256
294
|
try {
|
|
257
295
|
call.signal?.throwIfAborted();
|
|
@@ -271,6 +309,9 @@ export class Transport {
|
|
|
271
309
|
const body = call.bytes ?? (call.body === undefined ? undefined : JSON.stringify(call.body));
|
|
272
310
|
const retry = call.retry ?? true;
|
|
273
311
|
let attempt = 0;
|
|
312
|
+
// An answer that ran nothing is sent again until the call's deadline.
|
|
313
|
+
const idleUntil = call.deadlineAt ?? performance.now() + NOTHING_RAN_MS;
|
|
314
|
+
let idleAttempt = 0;
|
|
274
315
|
for (;;) {
|
|
275
316
|
let response;
|
|
276
317
|
let inCallback = false;
|
|
@@ -282,13 +323,20 @@ export class Transport {
|
|
|
282
323
|
// The API never redirects, so a redirect is refused here exactly as
|
|
283
324
|
// "error" refused it: thrown, retried, then a connection_error.
|
|
284
325
|
signal?.throwIfAborted();
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
326
|
+
const started = new AbortController();
|
|
327
|
+
const unanswered = setTimeout(() => started.abort(new DOMException("No answer started.", "TimeoutError")), this.#answerStartMs);
|
|
328
|
+
try {
|
|
329
|
+
response = await send(url, {
|
|
330
|
+
method: call.method,
|
|
331
|
+
headers: this.#headers(call, key, apiKey),
|
|
332
|
+
redirect: "manual",
|
|
333
|
+
signal: signal ? AbortSignal.any([signal, started.signal]) : started.signal,
|
|
334
|
+
...(body === undefined ? {} : { body: body }),
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
finally {
|
|
338
|
+
clearTimeout(unanswered);
|
|
339
|
+
}
|
|
292
340
|
if (response.type === "opaqueredirect" ||
|
|
293
341
|
(response.status >= 300 && response.status < 400)) {
|
|
294
342
|
await response.body?.cancel().catch(() => { });
|
|
@@ -350,7 +398,23 @@ export class Transport {
|
|
|
350
398
|
inCallback = true;
|
|
351
399
|
call.onCapacityWait?.(error, pause);
|
|
352
400
|
inCallback = false;
|
|
353
|
-
await
|
|
401
|
+
await this.#rest(pause, signal, gate);
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
// Nothing ran: safe to send again for any call, past maxRetries,
|
|
405
|
+
// with growing waits, until the deadline; then this refusal stands.
|
|
406
|
+
// A late answer's failure came after the work began, so only a
|
|
407
|
+
// guest's own refusal (`guest_busy`, before anything is journaled)
|
|
408
|
+
// still says nothing ran.
|
|
409
|
+
if ([429, 503].includes(status) &&
|
|
410
|
+
NOTHING_RAN.has(error.code) &&
|
|
411
|
+
(!late || error.code === "guest_busy")) {
|
|
412
|
+
const header = Number(response.headers.get("retry-after"));
|
|
413
|
+
const base = error.retryAfterMs ?? (Number.isFinite(header) && header > 0 ? header * 1000 : 250);
|
|
414
|
+
const pause = Math.min(30_000, base * 2 ** Math.min(idleAttempt++, 3)) * (0.9 + Math.random() * 0.2);
|
|
415
|
+
if (pause >= idleUntil - performance.now())
|
|
416
|
+
throw error;
|
|
417
|
+
await this.#rest(pause, signal, gate);
|
|
354
418
|
continue;
|
|
355
419
|
}
|
|
356
420
|
const retryable = [429, 502, 503, 504].includes(status) && !DELIBERATE.has(error.code);
|
|
@@ -359,7 +423,7 @@ export class Transport {
|
|
|
359
423
|
const header = Number(response.headers.get("retry-after"));
|
|
360
424
|
const wait = error.retryAfterMs ??
|
|
361
425
|
(Number.isFinite(header) && header > 0 ? header * 1000 : backoff(attempt));
|
|
362
|
-
await
|
|
426
|
+
await this.#rest(Math.min(30_000, wait) * (0.9 + Math.random() * 0.2), signal, gate);
|
|
363
427
|
}
|
|
364
428
|
catch (cause) {
|
|
365
429
|
// Deliberate API refusals and unusable proxy settings cannot be
|
|
@@ -373,25 +437,42 @@ export class Transport {
|
|
|
373
437
|
throw cause;
|
|
374
438
|
if (signal?.aborted || !retry || attempt >= this.#maxRetries)
|
|
375
439
|
throw this.#connectionError(call, cause);
|
|
376
|
-
await
|
|
377
|
-
throw this.#connectionError(call, cause);
|
|
440
|
+
await this.#rest(backoff(attempt), signal, gate).catch((cause) => {
|
|
441
|
+
throw cause instanceof RuntimeError ? cause : this.#connectionError(call, cause);
|
|
378
442
|
});
|
|
379
443
|
}
|
|
380
444
|
attempt++;
|
|
381
445
|
}
|
|
382
446
|
}
|
|
447
|
+
/** A hold on `kinds` of slot, taken in order and given back together. */
|
|
448
|
+
#gate(call, kinds) {
|
|
449
|
+
let held = 0;
|
|
450
|
+
const lend = () => {
|
|
451
|
+
while (held > 0)
|
|
452
|
+
kinds[--held].give();
|
|
453
|
+
};
|
|
454
|
+
return {
|
|
455
|
+
take: async () => {
|
|
456
|
+
try {
|
|
457
|
+
for (; held < kinds.length; held++)
|
|
458
|
+
await kinds[held].take(call.signal);
|
|
459
|
+
}
|
|
460
|
+
catch (cause) {
|
|
461
|
+
lend();
|
|
462
|
+
throw this.#connectionError(call, cause);
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
lend,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
383
468
|
async #queued(call, consume) {
|
|
469
|
+
const gate = this.#gate(call, [this.#slots]);
|
|
470
|
+
await gate.take();
|
|
384
471
|
try {
|
|
385
|
-
await this.#
|
|
386
|
-
}
|
|
387
|
-
catch (cause) {
|
|
388
|
-
throw this.#connectionError(call, cause);
|
|
389
|
-
}
|
|
390
|
-
try {
|
|
391
|
-
return await this.#send(call, consume);
|
|
472
|
+
return await this.#send(call, consume, gate);
|
|
392
473
|
}
|
|
393
474
|
finally {
|
|
394
|
-
|
|
475
|
+
gate.lend();
|
|
395
476
|
}
|
|
396
477
|
}
|
|
397
478
|
async json(call) {
|
|
@@ -426,11 +507,12 @@ export class Transport {
|
|
|
426
507
|
* errors with `download_incomplete` if it ends short of the length the API
|
|
427
508
|
* promised; it is never retried part way. */
|
|
428
509
|
async fileStream(call) {
|
|
429
|
-
const response = await this
|
|
510
|
+
const { response, release } = await this.#opened(this.#prepare(call));
|
|
430
511
|
const expected = promisedLength(response.headers);
|
|
431
512
|
const body = response.body ?? new ReadableStream({ start: (c) => c.close() });
|
|
432
513
|
let got = 0;
|
|
433
|
-
|
|
514
|
+
const reader = body
|
|
515
|
+
.pipeThrough(new TransformStream({
|
|
434
516
|
transform(chunk, controller) {
|
|
435
517
|
got += chunk.byteLength;
|
|
436
518
|
if (expected !== undefined && got > expected)
|
|
@@ -442,13 +524,65 @@ export class Transport {
|
|
|
442
524
|
if (expected !== undefined && got !== expected)
|
|
443
525
|
controller.error(incompleteDownload(got, expected));
|
|
444
526
|
},
|
|
445
|
-
}))
|
|
527
|
+
}))
|
|
528
|
+
.getReader();
|
|
529
|
+
// The connection is given back when the body ends, fails or is cancelled.
|
|
530
|
+
return new ReadableStream({
|
|
531
|
+
async pull(controller) {
|
|
532
|
+
try {
|
|
533
|
+
const { value, done } = await reader.read();
|
|
534
|
+
if (done) {
|
|
535
|
+
release();
|
|
536
|
+
controller.close();
|
|
537
|
+
}
|
|
538
|
+
else
|
|
539
|
+
controller.enqueue(value);
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
release();
|
|
543
|
+
controller.error(error);
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
async cancel(reason) {
|
|
547
|
+
release();
|
|
548
|
+
await reader.cancel(reason);
|
|
549
|
+
},
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
/** Sends a call whose answer is a stream, holding a stream slot and a
|
|
553
|
+
* connection slot from before it is sent until `release`: once its body is
|
|
554
|
+
* read, failed or cancelled, or its deadline or signal ends it, whichever
|
|
555
|
+
* comes first, so a stream nobody finishes still gives its slots back at
|
|
556
|
+
* its deadline. */
|
|
557
|
+
async #opened(call) {
|
|
558
|
+
const gate = this.#gate(call, [this.#streams, this.#slots]);
|
|
559
|
+
await gate.take();
|
|
560
|
+
let released = false;
|
|
561
|
+
const release = () => {
|
|
562
|
+
if (released)
|
|
563
|
+
return;
|
|
564
|
+
released = true;
|
|
565
|
+
call.signal?.removeEventListener("abort", release);
|
|
566
|
+
gate.lend();
|
|
567
|
+
};
|
|
568
|
+
try {
|
|
569
|
+
const response = await this.#send(call, async (response) => response, gate);
|
|
570
|
+
if (call.signal?.aborted)
|
|
571
|
+
release();
|
|
572
|
+
else
|
|
573
|
+
call.signal?.addEventListener("abort", release, { once: true });
|
|
574
|
+
return { response, release };
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
release();
|
|
578
|
+
throw error;
|
|
579
|
+
}
|
|
446
580
|
}
|
|
447
581
|
/** Newline-delimited JSON events, as they arrive. */
|
|
448
582
|
async *events(call) {
|
|
449
|
-
const response = await this
|
|
583
|
+
const { response, release } = await this.#opened(this.#prepare({ ...call, accept: "application/x-ndjson" }));
|
|
450
584
|
if (!response.body)
|
|
451
|
-
return;
|
|
585
|
+
return release();
|
|
452
586
|
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
453
587
|
let buffer = "";
|
|
454
588
|
try {
|
|
@@ -477,6 +611,7 @@ export class Transport {
|
|
|
477
611
|
// Cleanup must not replace a parse, network or callback error.
|
|
478
612
|
await reader.cancel().catch(() => undefined);
|
|
479
613
|
reader.releaseLock();
|
|
614
|
+
release();
|
|
480
615
|
}
|
|
481
616
|
}
|
|
482
617
|
/** A WebSocket to an API path, authenticated with the same key. */
|
package/dist/types.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export type SandboxInfo = {
|
|
|
5
5
|
kind: "sandbox";
|
|
6
6
|
name: string | null;
|
|
7
7
|
labels: Record<string, string>;
|
|
8
|
+
/** @deprecated Read `state`. An older, coarser name for it, still sent for
|
|
9
|
+
* older clients. */
|
|
8
10
|
status: "pending" | "active" | "paused" | "stopping" | "stopped";
|
|
9
11
|
state: "starting" | "running" | "pausing" | "paused" | "resuming" | "stopping" | "stopped";
|
|
10
12
|
region: string;
|
|
@@ -14,7 +16,10 @@ export type SandboxInfo = {
|
|
|
14
16
|
diskMiB: number;
|
|
15
17
|
cpu: "shared" | "reserved";
|
|
16
18
|
cpuFloorMillis: number;
|
|
19
|
+
/** @deprecated Always true for a sandbox created since 5 October 2026:
|
|
20
|
+
* every sandbox can pause. */
|
|
17
21
|
pausable: boolean;
|
|
22
|
+
/** Its time limit in seconds; 0 is none: it runs while it works. */
|
|
18
23
|
timeoutSeconds: number;
|
|
19
24
|
/** Pauses after this many idle seconds; 0 is never. */
|
|
20
25
|
idlePauseSeconds?: number;
|
|
@@ -23,19 +28,31 @@ export type SandboxInfo = {
|
|
|
23
28
|
idlePauseUnusedOnly?: boolean;
|
|
24
29
|
/** A request (exec, files, terminal, a visit to a shared port) wakes it when paused. */
|
|
25
30
|
autoWake?: boolean;
|
|
26
|
-
/**
|
|
31
|
+
/** Runs until stopped while credit lasts, never paused for idleness or a
|
|
32
|
+
* time limit, and its disk is kept after a stop. */
|
|
27
33
|
persistent?: boolean;
|
|
28
34
|
/** The last exec, file, terminal, desktop or preview request, to within a minute. */
|
|
29
35
|
lastActiveAt?: string | null;
|
|
30
36
|
onLeaseEnd: "pause" | "stop";
|
|
31
37
|
createdAt: string;
|
|
32
38
|
readyAt: string | null;
|
|
39
|
+
/** Paid up to: a time ahead of now that moves on by itself while it runs.
|
|
40
|
+
* Not when it ends; that is `endsAt`. */
|
|
33
41
|
expiresAt: string;
|
|
42
|
+
/** When it stops or pauses by itself: its time limit, or where its funding
|
|
43
|
+
* ends once credit or a spending limit stops its renewal. Null when it
|
|
44
|
+
* never will, and when it is not running. Absent from older servers. */
|
|
45
|
+
endsAt?: string | null;
|
|
34
46
|
endedAt: string | null;
|
|
47
|
+
/** Why it stopped or paused, such as `requested`, `idle` or `time_limit`
|
|
48
|
+
* (its `timeoutSeconds` ran out). A server before 4 October 2026 sent
|
|
49
|
+
* `lease_expired` for `time_limit`. */
|
|
35
50
|
stopReason: string | null;
|
|
36
51
|
pausedAt: string | null;
|
|
37
52
|
pausedExpiresAt: string | null;
|
|
38
53
|
chargedMicros: number;
|
|
54
|
+
/** @deprecated Read `chargedMicros` and `runtime.usage()`. Still sent for
|
|
55
|
+
* older clients. */
|
|
39
56
|
heldMicros: number;
|
|
40
57
|
simulated?: boolean;
|
|
41
58
|
replayed?: boolean;
|
|
@@ -76,25 +93,31 @@ export type CreateSandbox = {
|
|
|
76
93
|
diskMiB?: number;
|
|
77
94
|
cpu?: "shared" | "reserved";
|
|
78
95
|
cpuFloorMillis?: number;
|
|
79
|
-
/**
|
|
96
|
+
/** A time limit, 60 to 3600 seconds. Leave it out (or 0) for none: the
|
|
97
|
+
* sandbox runs while it works and pauses when idle, until you stop it or
|
|
98
|
+
* credit runs out. */
|
|
80
99
|
timeoutSeconds?: number;
|
|
100
|
+
/** @deprecated Ignored since 5 October 2026: every sandbox can pause. */
|
|
81
101
|
pausable?: boolean;
|
|
82
|
-
/** What happens when
|
|
102
|
+
/** What happens when its time limit or credit runs out: "pause" (default) or "stop". */
|
|
83
103
|
onLeaseEnd?: "pause" | "stop";
|
|
84
104
|
/** Pause after this many seconds in which nothing happens in it: no
|
|
85
105
|
* request, no command or terminal running, no open connection, no network
|
|
86
|
-
* traffic and no CPU use. Default 60
|
|
106
|
+
* traffic and no CPU use. Default 60 unless persistent; 0 never;
|
|
87
107
|
* otherwise 10 to 86400. A request wakes it again. */
|
|
88
108
|
idlePauseSeconds?: number;
|
|
89
109
|
/** A request to a paused sandbox wakes it. Default true. */
|
|
90
110
|
autoWake?: boolean;
|
|
91
|
-
/** Keep it running
|
|
92
|
-
*
|
|
111
|
+
/** Keep it running until you stop it, while credit lasts, with no idle
|
|
112
|
+
* pause unless idlePauseSeconds asks for one. Its disk is billed as any
|
|
113
|
+
* sandbox's, and kept after a stop, for restart(), as any sandbox's is.
|
|
114
|
+
* Paid only. */
|
|
93
115
|
persistent?: boolean;
|
|
94
116
|
/** The most it may cost over its whole life, in microdollars. */
|
|
95
117
|
maxTotalCostMicros?: number;
|
|
96
118
|
/** With name: return the sandbox that already has the name, woken if paused. */
|
|
97
119
|
getOrCreate?: boolean;
|
|
120
|
+
/** @deprecated Use `maxTotalCostMicros`. Still accepted for older clients. */
|
|
98
121
|
maxCostMicros?: number;
|
|
99
122
|
/** Network rules from the first start; the same shape as sandbox.network.set.
|
|
100
123
|
* Omit for the public web on ports 80 and 443. */
|
|
@@ -197,6 +220,7 @@ export type OutputEvent = {
|
|
|
197
220
|
error: {
|
|
198
221
|
code: string;
|
|
199
222
|
message: string;
|
|
223
|
+
status?: number;
|
|
200
224
|
requestId?: string;
|
|
201
225
|
};
|
|
202
226
|
};
|
|
@@ -227,6 +251,8 @@ export type Usage = {
|
|
|
227
251
|
unit: "microdollars";
|
|
228
252
|
credited: string;
|
|
229
253
|
spent: string;
|
|
254
|
+
/** What running sandboxes and the hour ahead of stored items are about to
|
|
255
|
+
* use, already taken out of `available`. */
|
|
230
256
|
held: string;
|
|
231
257
|
/** Credit that expired, or grant credit taken back. */
|
|
232
258
|
expired: string;
|
|
@@ -237,6 +263,7 @@ export type Usage = {
|
|
|
237
263
|
trial: {
|
|
238
264
|
totalMs: number;
|
|
239
265
|
usedMs: number;
|
|
266
|
+
/** @deprecated Read `availableMs`. Still sent for older clients. */
|
|
240
267
|
reservedMs: number;
|
|
241
268
|
availableMs: number;
|
|
242
269
|
} | null;
|
|
@@ -253,6 +280,23 @@ export type Usage = {
|
|
|
253
280
|
/** What the balance or a spending limit could not cover; never charged. */
|
|
254
281
|
writtenOffMicros: string;
|
|
255
282
|
} | null;
|
|
283
|
+
/** The account's pilot while it serves its sandboxes, a free run agreed
|
|
284
|
+
* with Runtime: `hours` sandbox-hours of running time, `usedMs` and
|
|
285
|
+
* `leftMs` of it used and left. Null on every other account. */
|
|
286
|
+
pilot?: {
|
|
287
|
+
id: string;
|
|
288
|
+
sandboxes: number;
|
|
289
|
+
vcpu: number;
|
|
290
|
+
memoryMiB: number;
|
|
291
|
+
diskMiB: number;
|
|
292
|
+
startsAt: string;
|
|
293
|
+
endsAt: string;
|
|
294
|
+
graceEndsAt: string;
|
|
295
|
+
endedAt: string | null;
|
|
296
|
+
hours: number;
|
|
297
|
+
usedMs: number;
|
|
298
|
+
leftMs: number;
|
|
299
|
+
} | null;
|
|
256
300
|
resources: Array<Record<string, unknown>>;
|
|
257
301
|
[key: string]: unknown;
|
|
258
302
|
};
|
|
@@ -269,7 +313,8 @@ export type SandboxSettings = {
|
|
|
269
313
|
/** Pause after this many seconds with no activity, counted from now; 0
|
|
270
314
|
* never, otherwise 10 to 86400. */
|
|
271
315
|
idlePauseSeconds?: number;
|
|
272
|
-
/** Keep it running while credit lasts and keep its disk
|
|
316
|
+
/** Keep it running until you stop it, while credit lasts, and keep its disk
|
|
317
|
+
* after a stop. Paid only. */
|
|
273
318
|
persistent?: boolean;
|
|
274
319
|
/** Lifetime cap in microdollars; null removes it. */
|
|
275
320
|
maxTotalCostMicros?: number | null;
|
|
@@ -288,11 +333,11 @@ export type DeletedSandbox = {
|
|
|
288
333
|
endedAt: string | null;
|
|
289
334
|
replayed?: boolean;
|
|
290
335
|
};
|
|
291
|
-
/** How `sandbox.keepAlive()`
|
|
336
|
+
/** How `sandbox.keepAlive()` moves on the time limit of a sandbox that has one. */
|
|
292
337
|
export type KeepAliveOptions = {
|
|
293
338
|
/** How often it checks, in seconds. Default 60. */
|
|
294
339
|
everySeconds?: number;
|
|
295
|
-
/** How much
|
|
340
|
+
/** How much of its time limit it keeps ahead of now, in seconds (60 to 3600). Default 600. */
|
|
296
341
|
marginSeconds?: number;
|
|
297
342
|
/** Called with an error an extension met; the loop carries on. */
|
|
298
343
|
onError?: (error: unknown) => void;
|
|
@@ -119,7 +119,9 @@ export class FileSystem {
|
|
|
119
119
|
const result = await this.#call(syscall, path, () => this.#ctx.run(argv, options));
|
|
120
120
|
if (result.exitCode === 0)
|
|
121
121
|
return result.stdout;
|
|
122
|
-
|
|
122
|
+
// dash, the sh of Debian and Ubuntu (a Runtime sandbox's), says "cannot
|
|
123
|
+
// create ...: Directory nonexistent" for a missing parent.
|
|
124
|
+
if (/No such file or directory|Directory nonexistent/.test(result.stderr))
|
|
123
125
|
throw fsError("ENOENT", syscall, path);
|
|
124
126
|
if (syscall === "unlink" || syscall === "rm")
|
|
125
127
|
throw fsError("EACCES", syscall, path);
|
package/dist/vercel/sandbox.d.ts
CHANGED
|
@@ -9,7 +9,9 @@ import { Snapshot } from "./snapshot.js";
|
|
|
9
9
|
* 2026). Runtime gives 2 vCPU and 4096 MiB. */
|
|
10
10
|
export declare const DEFAULT_VCPUS = 2;
|
|
11
11
|
export declare const MEMORY_MIB_PER_VCPU = 2048;
|
|
12
|
-
/** Vercel's default timeout, 5 minutes.
|
|
12
|
+
/** Vercel's default timeout, 5 minutes. Not sent: a sandbox created with no
|
|
13
|
+
* timeout has no time limit on Runtime, running while it works and pausing
|
|
14
|
+
* when idle (0300). Kept for code that imports it. */
|
|
13
15
|
export declare const DEFAULT_TIMEOUT_MS = 300000;
|
|
14
16
|
/** Vercel's working directory; `/workspace` on Runtime. */
|
|
15
17
|
export declare const HOME = "/vercel/sandbox";
|
|
@@ -40,9 +42,10 @@ export interface CreateSandboxParams extends Credentials {
|
|
|
40
42
|
/** Shared at public HTTPS addresses (Runtime previews); `domain(port)`
|
|
41
43
|
* answers from them. */
|
|
42
44
|
ports?: number[];
|
|
43
|
-
/** Milliseconds
|
|
44
|
-
*
|
|
45
|
-
*
|
|
45
|
+
/** Milliseconds, at least 60 s: when it stops. Leave it out for no time
|
|
46
|
+
* limit: it runs while it works and pauses when idle. Over an hour, the
|
|
47
|
+
* hour-long limit is extended while this object lives, up to the timeout
|
|
48
|
+
* (`sandbox.timeout`). */
|
|
46
49
|
timeout?: number;
|
|
47
50
|
resources?: {
|
|
48
51
|
vcpus: number;
|
|
@@ -135,13 +138,15 @@ export declare class Sandbox {
|
|
|
135
138
|
get cwd(): string;
|
|
136
139
|
get status(): "pending" | "running" | "stopping" | "stopped";
|
|
137
140
|
/** Milliseconds from the start of the session: the timeout given at
|
|
138
|
-
* create, with every extension, or the
|
|
141
|
+
* create, with every extension, or the time limit of a sandbox found by
|
|
142
|
+
* name. 0 for one with no time limit, which runs while it works. */
|
|
139
143
|
get timeout(): number;
|
|
140
144
|
get expiresAt(): Date;
|
|
141
145
|
get tags(): Record<string, string>;
|
|
142
146
|
get sourceSnapshotId(): string | undefined;
|
|
143
147
|
/** Creates a sandbox and waits until it runs. Vercel's defaults: 2 vCPUs,
|
|
144
|
-
* 2048 MiB per vCPU,
|
|
148
|
+
* 2048 MiB per vCPU, persistent. With no `timeout` it has no time limit: it
|
|
149
|
+
* runs while it works and pauses when idle. A `timeout` is kept as given. Funding is left to
|
|
145
150
|
* Runtime: the free trial while the account has trial time, then prepaid
|
|
146
151
|
* credit, exactly as withruntime's own create. */
|
|
147
152
|
static create(params?: CreateSandboxParams): Promise<Sandbox & AsyncDisposable>;
|
|
@@ -269,9 +274,10 @@ export declare class Sandbox {
|
|
|
269
274
|
signal?: AbortSignal;
|
|
270
275
|
}): Promise<void>;
|
|
271
276
|
[Symbol.asyncDispose](): Promise<void>;
|
|
272
|
-
/** Moves the end `duration` milliseconds later. The
|
|
273
|
-
* as far as it may (an hour ahead of now); past that it is extended
|
|
274
|
-
* this object lives.
|
|
277
|
+
/** Moves the end `duration` milliseconds later. The time limit moves at
|
|
278
|
+
* once as far as it may (an hour ahead of now); past that it is extended
|
|
279
|
+
* while this object lives. A sandbox with no time limit has no end to
|
|
280
|
+
* move, and nothing changes. */
|
|
275
281
|
extendTimeout(duration: number, opts?: {
|
|
276
282
|
signal?: AbortSignal;
|
|
277
283
|
}): Promise<void>;
|
|
@@ -281,7 +287,8 @@ export declare class Sandbox {
|
|
|
281
287
|
}): Promise<NetworkPolicy>;
|
|
282
288
|
/** Changes what Runtime can change on a sandbox: `timeout`, `ports`,
|
|
283
289
|
* `networkPolicy` and `snapshotExpiration`. Anything else is refused before
|
|
284
|
-
* anything changes.
|
|
290
|
+
* anything changes. A `timeout` on a sandbox with no time limit changes
|
|
291
|
+
* nothing: it runs while it works. */
|
|
285
292
|
update(params: {
|
|
286
293
|
timeout?: number;
|
|
287
294
|
ports?: number[];
|