withruntime 0.9.0 → 0.11.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/BLAXEL.md +4 -4
- package/DAYTONA.md +52 -40
- package/E2B.md +65 -62
- package/README.md +15 -7
- package/README.src.md +15 -7
- package/VERCEL.md +34 -22
- package/compatibility-lock.json +16 -16
- package/dist/blaxel/errors.js +1 -1
- package/dist/blaxel/lookup.d.ts +3 -2
- package/dist/blaxel/lookup.js +3 -2
- package/dist/blaxel/pagination.d.ts +2 -2
- package/dist/blaxel/pagination.js +45 -17
- package/dist/blaxel/preview.d.ts +2 -1
- package/dist/blaxel/preview.js +15 -13
- package/dist/blaxel/process.js +56 -10
- package/dist/blaxel/sandbox.d.ts +2 -0
- package/dist/blaxel/sandbox.js +2 -0
- package/dist/blaxel/snapshot.js +3 -5
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +262 -56
- package/dist/client.d.ts +41 -3
- package/dist/client.js +67 -11
- package/dist/cloudflare/identity.d.ts +7 -0
- package/dist/cloudflare/identity.js +13 -0
- package/dist/cloudflare/index.d.ts +9 -0
- package/dist/cloudflare/index.js +47 -9
- package/dist/cloudflare/worker.d.ts +4 -0
- package/dist/cloudflare/worker.js +166 -16
- package/dist/codesandbox/guest-service.js +57 -20
- package/dist/codesandbox/index.js +42 -8
- package/dist/compat/core.d.ts +9 -8
- package/dist/compat/core.js +60 -21
- package/dist/compat/files.js +22 -4
- package/dist/compat/lease.d.ts +36 -0
- package/dist/compat/lease.js +87 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +107 -38
- package/dist/daytona/computer-use.d.ts +162 -0
- package/dist/daytona/computer-use.js +324 -0
- package/dist/daytona/context.d.ts +10 -0
- package/dist/daytona/context.js +24 -0
- package/dist/daytona/daytona.d.ts +20 -0
- package/dist/daytona/daytona.js +134 -20
- package/dist/daytona/filesystem.d.ts +17 -4
- package/dist/daytona/filesystem.js +129 -38
- package/dist/daytona/index.d.ts +3 -27
- package/dist/daytona/index.js +2 -7
- package/dist/daytona/process.d.ts +63 -6
- package/dist/daytona/process.js +224 -23
- package/dist/daytona/sandbox.d.ts +20 -6
- package/dist/daytona/sandbox.js +55 -32
- package/dist/e2b/client.d.ts +5 -0
- package/dist/e2b/client.js +6 -2
- package/dist/e2b/commands.d.ts +43 -12
- package/dist/e2b/commands.js +217 -64
- package/dist/e2b/errors.d.ts +7 -0
- package/dist/e2b/errors.js +20 -0
- package/dist/e2b/filesystem.d.ts +2 -1
- package/dist/e2b/filesystem.js +43 -6
- package/dist/e2b/index.d.ts +3 -2
- package/dist/e2b/index.js +2 -1
- package/dist/e2b/pty.d.ts +24 -0
- package/dist/e2b/pty.js +83 -0
- package/dist/e2b/sandbox.d.ts +17 -6
- package/dist/e2b/sandbox.js +239 -43
- package/dist/e2b/users.d.ts +30 -0
- package/dist/e2b/users.js +185 -0
- package/dist/file-source.d.ts +16 -0
- package/dist/file-source.js +162 -0
- package/dist/freestyle/index.d.ts +9 -1
- package/dist/freestyle/index.js +38 -5
- package/dist/index.d.ts +3 -1
- package/dist/login.js +16 -10
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +6 -1
- package/dist/modal/index.js +2 -1
- package/dist/network-cli.js +139 -43
- package/dist/openai-agents/index.js +9 -45
- package/dist/page.d.ts +1 -1
- package/dist/page.js +2 -2
- package/dist/products/browser.d.ts +60 -0
- package/dist/products/browser.js +37 -0
- package/dist/products/desktop.d.ts +2 -2
- package/dist/products/desktop.js +2 -2
- package/dist/products/images.d.ts +3 -1
- package/dist/products/images.js +14 -2
- package/dist/products/index.d.ts +14 -0
- package/dist/products/index.js +12 -0
- package/dist/products/network-products.d.ts +7 -0
- package/dist/products/network-products.js +3 -0
- package/dist/products/observability.d.ts +44 -0
- package/dist/products/observability.js +6 -0
- package/dist/products/previews.d.ts +8 -2
- package/dist/products/previews.js +11 -3
- package/dist/products/private-network.d.ts +22 -0
- package/dist/products/private-network.js +10 -0
- package/dist/products/volumes.d.ts +42 -0
- package/dist/products/volumes.js +36 -0
- package/dist/products/watch.js +61 -8
- package/dist/proxy.js +3 -1
- package/dist/runloop/index.d.ts +13 -27
- package/dist/runloop/index.js +149 -52
- package/dist/sandbox.d.ts +59 -8
- package/dist/sandbox.js +479 -68
- package/dist/snapshots.d.ts +9 -4
- package/dist/sprites/index.js +69 -33
- package/dist/tar.d.ts +3 -1
- package/dist/tar.js +140 -14
- package/dist/transport.d.ts +4 -7
- package/dist/transport.js +205 -122
- package/dist/types.d.ts +5 -2
- package/dist/vercel/command.d.ts +1 -1
- package/dist/vercel/command.js +3 -2
- package/dist/vercel/errors.d.ts +1 -1
- package/dist/vercel/errors.js +7 -1
- package/dist/vercel/index.d.ts +1 -7
- package/dist/vercel/index.js +1 -3
- package/dist/vercel/sandbox-user.d.ts +70 -0
- package/dist/vercel/sandbox-user.js +206 -0
- package/dist/vercel/sandbox.d.ts +55 -20
- package/dist/vercel/sandbox.js +353 -99
- package/dist/vercel/snapshot.d.ts +3 -2
- package/dist/vercel/snapshot.js +9 -5
- package/package.json +2 -1
package/dist/transport.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConnectionError, DELIBERATE, errorFor, RuntimeError, WAITS_FOR_ROOM } from "./errors.js";
|
|
2
2
|
import { describeRoute, envFetch, openWebSocket } from "./proxy.js";
|
|
3
|
-
export const VERSION = "0.
|
|
3
|
+
export const VERSION = "0.11.0";
|
|
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
|
|
@@ -80,18 +80,57 @@ const sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
|
80
80
|
else
|
|
81
81
|
signal?.addEventListener("abort", abort, { once: true });
|
|
82
82
|
});
|
|
83
|
+
/** A credential lookup can finish independently, but must not keep its
|
|
84
|
+
* caller waiting after cancellation or hold that caller's queue slot. */
|
|
85
|
+
function untilAborted(pending, signal) {
|
|
86
|
+
if (!signal)
|
|
87
|
+
return pending;
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
const abort = () => reject(abortReason(signal));
|
|
90
|
+
if (signal.aborted)
|
|
91
|
+
abort();
|
|
92
|
+
else
|
|
93
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
94
|
+
pending.then((value) => {
|
|
95
|
+
signal.removeEventListener("abort", abort);
|
|
96
|
+
resolve(value);
|
|
97
|
+
}, (cause) => {
|
|
98
|
+
signal.removeEventListener("abort", abort);
|
|
99
|
+
reject(cause instanceof Error ? cause : new Error("Credential lookup failed.", { cause }));
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function abortReason(signal) {
|
|
104
|
+
return signal?.reason instanceof Error
|
|
105
|
+
? signal.reason
|
|
106
|
+
: new Error("Aborted", { cause: signal?.reason });
|
|
107
|
+
}
|
|
83
108
|
/** At most `maximum` holders at once; the rest wait their turn, first come
|
|
84
109
|
* first served. */
|
|
85
110
|
function slots(maximum) {
|
|
86
111
|
let active = 0;
|
|
87
112
|
const waiting = [];
|
|
88
113
|
return {
|
|
89
|
-
async take() {
|
|
114
|
+
async take(signal) {
|
|
115
|
+
signal?.throwIfAborted();
|
|
90
116
|
if (active < maximum) {
|
|
91
117
|
active++;
|
|
92
118
|
return;
|
|
93
119
|
}
|
|
94
|
-
await new Promise((resolve) =>
|
|
120
|
+
await new Promise((resolve, reject) => {
|
|
121
|
+
const ready = () => {
|
|
122
|
+
signal?.removeEventListener("abort", abort);
|
|
123
|
+
resolve();
|
|
124
|
+
};
|
|
125
|
+
const abort = () => {
|
|
126
|
+
const at = waiting.indexOf(ready);
|
|
127
|
+
if (at >= 0)
|
|
128
|
+
waiting.splice(at, 1);
|
|
129
|
+
reject(abortReason(signal));
|
|
130
|
+
};
|
|
131
|
+
waiting.push(ready);
|
|
132
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
133
|
+
});
|
|
95
134
|
},
|
|
96
135
|
give() {
|
|
97
136
|
const next = waiting.shift();
|
|
@@ -170,22 +209,71 @@ export class Transport {
|
|
|
170
209
|
* the same key every time, and waits out a full house for a call that sets
|
|
171
210
|
* waitForCapacityMs. */
|
|
172
211
|
async send(call) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
212
|
+
return this.#send(this.#prepare(call), async (response) => response);
|
|
213
|
+
}
|
|
214
|
+
/** Start the deadline before waiting for a connection; keep one key and
|
|
215
|
+
* signal through every retry and the complete response body. */
|
|
216
|
+
#prepare(call) {
|
|
217
|
+
const timeoutMs = call.timeoutMs ?? this.#timeoutMs;
|
|
218
|
+
const roomMs = Math.max(0, call.waitForCapacityMs ?? 0);
|
|
219
|
+
const deadline = timeoutMs === 0 ? undefined : AbortSignal.timeout(timeoutMs + roomMs);
|
|
220
|
+
const signal = deadline
|
|
221
|
+
? call.signal
|
|
222
|
+
? AbortSignal.any([call.signal, deadline])
|
|
223
|
+
: deadline
|
|
224
|
+
: call.signal;
|
|
225
|
+
return {
|
|
226
|
+
...call,
|
|
227
|
+
timeoutMs: 0,
|
|
228
|
+
...(signal ? { signal } : {}),
|
|
229
|
+
...(call.method !== "GET"
|
|
230
|
+
? { idempotencyKey: call.idempotencyKey ?? crypto.randomUUID() }
|
|
231
|
+
: {}),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
#connectionError(call, cause) {
|
|
235
|
+
const cancelled = call.signal?.aborted === true;
|
|
236
|
+
const route = this.#routed && !cancelled
|
|
237
|
+
? describeRoute(`${this.baseUrl}${call.path}`, cause)
|
|
238
|
+
: { via: "" };
|
|
239
|
+
return new ConnectionError({
|
|
240
|
+
message: cancelled
|
|
241
|
+
? "The call was cancelled or ran past its deadline."
|
|
242
|
+
: call.method !== "GET"
|
|
243
|
+
? `No answer from Runtime at ${this.baseUrl}${route.via}. The change may have happened; retrying with the same idempotencyKey is safe.`
|
|
244
|
+
: `No answer from Runtime at ${this.baseUrl}${route.via}.`,
|
|
245
|
+
hint: cancelled
|
|
246
|
+
? "Raise timeoutMs for a long call, or retry with the same idempotencyKey."
|
|
247
|
+
: (route.hint ?? "Check the network, and RUNTIME_API_URL if you set it."),
|
|
248
|
+
code: cancelled ? "timeout" : "connection_error",
|
|
249
|
+
status: 0,
|
|
250
|
+
...(call.idempotencyKey ? { idempotencyKey: call.idempotencyKey } : {}),
|
|
251
|
+
cause,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
async #send(call, consume) {
|
|
255
|
+
let apiKey;
|
|
256
|
+
try {
|
|
257
|
+
call.signal?.throwIfAborted();
|
|
258
|
+
apiKey = await untilAborted(this.#key(), call.signal);
|
|
259
|
+
}
|
|
260
|
+
catch (cause) {
|
|
261
|
+
if (call.signal?.aborted)
|
|
262
|
+
throw this.#connectionError(call, cause);
|
|
263
|
+
throw cause;
|
|
264
|
+
}
|
|
265
|
+
const key = call.method !== "GET" ? call.idempotencyKey : undefined;
|
|
178
266
|
const roomMs = Math.max(0, call.waitForCapacityMs ?? 0);
|
|
179
267
|
const roomUntil = performance.now() + roomMs;
|
|
180
268
|
let roomAttempt = 0;
|
|
181
|
-
const
|
|
182
|
-
const deadline = timeoutMs === 0 ? new AbortController().signal : AbortSignal.timeout(timeoutMs + roomMs);
|
|
183
|
-
const signal = call.signal ? AbortSignal.any([call.signal, deadline]) : deadline;
|
|
269
|
+
const signal = call.signal;
|
|
184
270
|
const url = `${this.baseUrl}${call.path}${encodeQuery(call.query)}`;
|
|
185
271
|
const body = call.bytes ?? (call.body === undefined ? undefined : JSON.stringify(call.body));
|
|
186
272
|
const retry = call.retry ?? true;
|
|
187
|
-
|
|
273
|
+
let attempt = 0;
|
|
274
|
+
for (;;) {
|
|
188
275
|
let response;
|
|
276
|
+
let inCallback = false;
|
|
189
277
|
// Called as a plain function: Cloudflare Workers' fetch throws "Illegal
|
|
190
278
|
// invocation" when called as a method of another object (this.#fetch()).
|
|
191
279
|
const send = this.#fetch;
|
|
@@ -193,6 +281,7 @@ export class Transport {
|
|
|
193
281
|
// "manual", not "error": Workers accept only "follow" and "manual".
|
|
194
282
|
// The API never redirects, so a redirect is refused here exactly as
|
|
195
283
|
// "error" refused it: thrown, retried, then a connection_error.
|
|
284
|
+
signal?.throwIfAborted();
|
|
196
285
|
response = await send(url, {
|
|
197
286
|
method: call.method,
|
|
198
287
|
headers: this.#headers(call, key, apiKey),
|
|
@@ -205,138 +294,132 @@ export class Transport {
|
|
|
205
294
|
await response.body?.cancel().catch(() => { });
|
|
206
295
|
throw new TypeError(`fetch failed: Runtime answered a redirect (${response.status}).`);
|
|
207
296
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
297
|
+
/* An answer that took longer than the API holds its headers (90 s)
|
|
298
|
+
comes as a 200 sent early, and then its JSON: the answer, or
|
|
299
|
+
{"error": ...} when the work failed after the status went
|
|
300
|
+
(runtime-late-answer, api.md). Either way it is read here, so a
|
|
301
|
+
failure is thrown as the error it is, never returned as a success. */
|
|
302
|
+
let late;
|
|
303
|
+
if (response.ok && response.headers.get("runtime-late-answer") === "true") {
|
|
304
|
+
const text = await response.text();
|
|
305
|
+
const failed = lateFailure(text);
|
|
306
|
+
if (failed === undefined)
|
|
307
|
+
return await consume(new Response(text, { headers: response.headers }));
|
|
308
|
+
late = { status: failed, text };
|
|
309
|
+
}
|
|
310
|
+
else if (response.ok) {
|
|
311
|
+
inCallback = true;
|
|
312
|
+
call.onResponse?.(response);
|
|
313
|
+
inCallback = false;
|
|
314
|
+
return await consume(response);
|
|
315
|
+
}
|
|
316
|
+
const status = late?.status ?? response.status;
|
|
317
|
+
// Runtime never answers 407: it is a proxy refusing the tunnel, which
|
|
318
|
+
// Bun's fetch hands back as a response rather than an error.
|
|
319
|
+
if (response.status === 407 && this.#routed) {
|
|
320
|
+
await response.body?.cancel();
|
|
321
|
+
const route = describeRoute(url, 407);
|
|
215
322
|
throw new ConnectionError({
|
|
216
|
-
message:
|
|
217
|
-
|
|
218
|
-
: write
|
|
219
|
-
? `No answer from Runtime at ${this.baseUrl}${route.via}. The change may have happened; retrying with the same idempotencyKey is safe.`
|
|
220
|
-
: `No answer from Runtime at ${this.baseUrl}${route.via}.`,
|
|
221
|
-
hint: signal.aborted
|
|
222
|
-
? "Raise timeoutMs for a long call, or retry with the same idempotencyKey."
|
|
223
|
-
: (route.hint ?? "Check the network, and RUNTIME_API_URL if you set it."),
|
|
224
|
-
code: signal.aborted ? "timeout" : "connection_error",
|
|
323
|
+
message: `No answer from Runtime at ${this.baseUrl}${route.via}.`,
|
|
324
|
+
code: "connection_error",
|
|
225
325
|
status: 0,
|
|
326
|
+
...(route.hint ? { hint: route.hint } : {}),
|
|
226
327
|
...(key ? { idempotencyKey: key } : {}),
|
|
227
|
-
cause,
|
|
228
328
|
});
|
|
229
329
|
}
|
|
230
|
-
await
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
330
|
+
const text = late?.text ?? (await response.text());
|
|
331
|
+
let parsed = null;
|
|
332
|
+
try {
|
|
333
|
+
parsed = JSON.parse(text);
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
parsed = { error: { message: text.slice(0, 500) || `HTTP ${status}` } };
|
|
337
|
+
}
|
|
338
|
+
const error = errorFor(status, parsed, key);
|
|
339
|
+
// A full house: wait for a slot or for room, then send the same call
|
|
340
|
+
// again. A request that can never fit (a fork of more copies than the
|
|
341
|
+
// trial runs) is not waited for.
|
|
342
|
+
if (retry &&
|
|
343
|
+
roomMs > 0 &&
|
|
344
|
+
WAITS_FOR_ROOM.has(error.code) &&
|
|
345
|
+
error.details?.field !== "count") {
|
|
346
|
+
const left = roomUntil - performance.now();
|
|
347
|
+
if (left <= 0)
|
|
348
|
+
throw error;
|
|
349
|
+
const pause = Math.min(left, error.retryAfterMs ?? roomBackoff(roomAttempt++));
|
|
350
|
+
inCallback = true;
|
|
351
|
+
call.onCapacityWait?.(error, pause);
|
|
352
|
+
inCallback = false;
|
|
353
|
+
await sleep(pause, signal);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
const retryable = [429, 502, 503, 504].includes(status) && !DELIBERATE.has(error.code);
|
|
357
|
+
if (!retry || !retryable || attempt >= this.#maxRetries)
|
|
358
|
+
throw error;
|
|
359
|
+
const header = Number(response.headers.get("retry-after"));
|
|
360
|
+
const wait = error.retryAfterMs ??
|
|
361
|
+
(Number.isFinite(header) && header > 0 ? header * 1000 : backoff(attempt));
|
|
362
|
+
await sleep(Math.min(30_000, wait) * (0.9 + Math.random() * 0.2), signal);
|
|
249
363
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
364
|
+
catch (cause) {
|
|
365
|
+
// Deliberate API refusals and unusable proxy settings cannot be
|
|
366
|
+
// fixed by repeating the request. Body failures are transport
|
|
367
|
+
// failures too: a write replays under its original key.
|
|
368
|
+
if (inCallback) {
|
|
369
|
+
await response?.body?.cancel().catch(() => undefined);
|
|
370
|
+
throw cause;
|
|
371
|
+
}
|
|
372
|
+
if (cause instanceof RuntimeError)
|
|
373
|
+
throw cause;
|
|
374
|
+
if (signal?.aborted || !retry || attempt >= this.#maxRetries)
|
|
375
|
+
throw this.#connectionError(call, cause);
|
|
376
|
+
await sleep(backoff(attempt), signal).catch((cause) => {
|
|
377
|
+
throw this.#connectionError(call, cause);
|
|
262
378
|
});
|
|
263
379
|
}
|
|
264
|
-
|
|
265
|
-
let parsed = null;
|
|
266
|
-
try {
|
|
267
|
-
parsed = JSON.parse(text);
|
|
268
|
-
}
|
|
269
|
-
catch {
|
|
270
|
-
parsed = { error: { message: text.slice(0, 500) || `HTTP ${status}` } };
|
|
271
|
-
}
|
|
272
|
-
const error = errorFor(status, parsed, key);
|
|
273
|
-
// A full house: wait for a slot or for room, then send the same call
|
|
274
|
-
// again. A request that can never fit (a fork of more copies than the
|
|
275
|
-
// trial runs) is not waited for.
|
|
276
|
-
if (retry &&
|
|
277
|
-
roomMs > 0 &&
|
|
278
|
-
WAITS_FOR_ROOM.has(error.code) &&
|
|
279
|
-
error.details?.field !== "count") {
|
|
280
|
-
const left = roomUntil - performance.now();
|
|
281
|
-
if (left <= 0)
|
|
282
|
-
throw error;
|
|
283
|
-
const pause = Math.min(left, error.retryAfterMs ?? roomBackoff(roomAttempt++));
|
|
284
|
-
call.onCapacityWait?.(error, pause);
|
|
285
|
-
await sleep(pause, signal);
|
|
286
|
-
continue;
|
|
287
|
-
}
|
|
288
|
-
const retryable = [429, 502, 503, 504].includes(status) && !DELIBERATE.has(error.code);
|
|
289
|
-
if (!retry || !retryable || attempt >= this.#maxRetries)
|
|
290
|
-
throw error;
|
|
291
|
-
const header = Number(response.headers.get("retry-after"));
|
|
292
|
-
const wait = error.retryAfterMs ??
|
|
293
|
-
(Number.isFinite(header) && header > 0 ? header * 1000 : backoff(attempt));
|
|
294
|
-
await sleep(Math.min(30_000, wait) * (0.9 + Math.random() * 0.2), signal);
|
|
380
|
+
attempt++;
|
|
295
381
|
}
|
|
296
382
|
}
|
|
297
|
-
async
|
|
298
|
-
await this.#slots.take();
|
|
383
|
+
async #queued(call, consume) {
|
|
299
384
|
try {
|
|
300
|
-
|
|
301
|
-
return (text ? JSON.parse(text) : null);
|
|
385
|
+
await this.#slots.take(call.signal);
|
|
302
386
|
}
|
|
303
|
-
|
|
304
|
-
this.#
|
|
387
|
+
catch (cause) {
|
|
388
|
+
throw this.#connectionError(call, cause);
|
|
305
389
|
}
|
|
306
|
-
}
|
|
307
|
-
async bytes(call) {
|
|
308
|
-
await this.#slots.take();
|
|
309
390
|
try {
|
|
310
|
-
return
|
|
391
|
+
return await this.#send(call, consume);
|
|
311
392
|
}
|
|
312
393
|
finally {
|
|
313
394
|
this.#slots.give();
|
|
314
395
|
}
|
|
315
396
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
397
|
+
async json(call) {
|
|
398
|
+
return this.#queued(this.#prepare(call), async (response) => {
|
|
399
|
+
const text = await response.text();
|
|
400
|
+
return (text ? JSON.parse(text) : null);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
async bytes(call) {
|
|
404
|
+
return this.#queued(this.#prepare(call), async (response) => new Uint8Array(await response.arrayBuffer()));
|
|
405
|
+
}
|
|
406
|
+
/** A file's bytes, read whole and checked against its promised length and
|
|
407
|
+
* SHA-256. A short or different body is read again twice, within the same
|
|
408
|
+
* call deadline. */
|
|
322
409
|
async fileBytes(call) {
|
|
410
|
+
call = this.#prepare(call);
|
|
323
411
|
for (let attempt = 0;; attempt++) {
|
|
324
|
-
await this.#
|
|
325
|
-
let failure;
|
|
326
|
-
try {
|
|
327
|
-
const response = await this.send(call);
|
|
412
|
+
const { body, problem } = await this.#queued(call, async (response) => {
|
|
328
413
|
const body = new Uint8Array(await response.arrayBuffer());
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
throw failure;
|
|
339
|
-
await sleep(backoff(attempt), call.signal);
|
|
414
|
+
return { body, problem: await checkBody(response.headers, body) };
|
|
415
|
+
});
|
|
416
|
+
if (!problem)
|
|
417
|
+
return body;
|
|
418
|
+
if (attempt >= 2)
|
|
419
|
+
throw problem;
|
|
420
|
+
await sleep(backoff(attempt), call.signal).catch((cause) => {
|
|
421
|
+
throw this.#connectionError(call, cause);
|
|
422
|
+
});
|
|
340
423
|
}
|
|
341
424
|
}
|
|
342
425
|
/** A file's bytes as they arrive, for files too big to hold. The stream
|
package/dist/types.d.ts
CHANGED
|
@@ -42,8 +42,9 @@ export type SandboxInfo = {
|
|
|
42
42
|
/** The names of its own environment variables (`env`), on a create, an
|
|
43
43
|
* update and a read of one sandbox. Values are never shown. */
|
|
44
44
|
envNames?: string[];
|
|
45
|
-
/** On a create from an image with a start command: whether it started
|
|
46
|
-
*
|
|
45
|
+
/** On a create from an image with a start command: whether it started
|
|
46
|
+
* (and was still running half a second later), became ready (readyMs),
|
|
47
|
+
* timed out, or exited first (exitCode, and stderr's last lines). The create's answer
|
|
47
48
|
* alone carries it, and the Sandbox that create returned keeps it through
|
|
48
49
|
* later reads; `sandboxes.get` and `list` have none, because the API keeps
|
|
49
50
|
* no record of it. */
|
|
@@ -52,6 +53,8 @@ export type SandboxInfo = {
|
|
|
52
53
|
processId?: string;
|
|
53
54
|
exitCode?: number | null;
|
|
54
55
|
readyMs?: number;
|
|
56
|
+
/** With exited: the last lines the start command wrote to stderr. */
|
|
57
|
+
stderr?: string;
|
|
55
58
|
};
|
|
56
59
|
/** Present and true when getOrCreate answered a sandbox that already held the name. */
|
|
57
60
|
reused?: boolean;
|
package/dist/vercel/command.d.ts
CHANGED
package/dist/vercel/command.js
CHANGED
|
@@ -136,13 +136,14 @@ export class Command {
|
|
|
136
136
|
return this.output("stderr", opts);
|
|
137
137
|
}
|
|
138
138
|
/** Sends a signal, SIGTERM by default. */
|
|
139
|
-
async kill(signal = "SIGTERM",
|
|
139
|
+
async kill(signal = "SIGTERM", opts = {}) {
|
|
140
|
+
opts.abortSignal?.throwIfAborted();
|
|
140
141
|
if (!this.init.process)
|
|
141
142
|
return;
|
|
142
143
|
const name = typeof signal === "number" ? SIGNAL_NUMBERS[signal] : signal;
|
|
143
144
|
if (!name)
|
|
144
145
|
throw new RangeError(`Signal ${signal} has no name Runtime knows; use SIGTERM, SIGKILL, SIGINT, SIGHUP, SIGQUIT, SIGUSR1 or SIGUSR2.`);
|
|
145
|
-
await guard(() => this.init.process.kill(name), this.init.sandboxName);
|
|
146
|
+
await guard(() => this.init.process.kill(name, opts.abortSignal ? { signal: opts.abortSignal } : {}), this.init.sandboxName);
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
/** A command that has ended; `exitCode` is set. */
|
package/dist/vercel/errors.d.ts
CHANGED
|
@@ -45,7 +45,7 @@ export declare function translate(error: unknown, sandboxName?: string): unknown
|
|
|
45
45
|
export declare function guard<T>(work: () => Promise<T>, sandboxName?: string): Promise<T>;
|
|
46
46
|
/** A node:fs-style error, as Vercel's `sandbox.fs` throws. */
|
|
47
47
|
export declare function fsError(code: "ENOENT" | "EEXIST" | "EISDIR" | "ENOTDIR" | "EACCES" | "ENOTEMPTY" | "EINVAL", syscall: string, path: string): Error & {
|
|
48
|
-
code: "ENOENT" | "
|
|
48
|
+
code: "ENOENT" | "ENOTDIR" | "EEXIST" | "EISDIR" | "EACCES" | "ENOTEMPTY" | "EINVAL";
|
|
49
49
|
errno: number;
|
|
50
50
|
syscall: string;
|
|
51
51
|
path: string;
|
package/dist/vercel/errors.js
CHANGED
|
@@ -73,7 +73,13 @@ export function translate(error, sandboxName) {
|
|
|
73
73
|
return new NotSupportedError(error.code.replace(/_unavailable$/, ""), error.hint ?? "", message);
|
|
74
74
|
if (error.status < 200 || error.status > 599)
|
|
75
75
|
return error;
|
|
76
|
-
const json = {
|
|
76
|
+
const json = {
|
|
77
|
+
error: {
|
|
78
|
+
code: error.code,
|
|
79
|
+
message: error.message,
|
|
80
|
+
...(error.details ? { details: error.details } : {}),
|
|
81
|
+
},
|
|
82
|
+
};
|
|
77
83
|
const out = new APIError(new Response(JSON.stringify(json), {
|
|
78
84
|
status: error.status,
|
|
79
85
|
headers: { "content-type": "application/json" },
|
package/dist/vercel/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { Sandbox, DEFAULT_TIMEOUT_MS, DEFAULT_VCPUS, MEMORY_MIB_PER_VCPU, networkRules, toRuntimePath, type CreateSandboxParams, type RunCommandParams, type NetworkPolicy, type SandboxRoute, } from "./sandbox.js";
|
|
2
2
|
export { Command, CommandFinished, type CommandOutput, type LogOutputLine, type Signal, } from "./command.js";
|
|
3
3
|
export { FileSystem, Stats, Dirent } from "./filesystem.js";
|
|
4
|
+
export { SandboxUser, SandboxUserAlreadyExistsError } from "./sandbox-user.js";
|
|
4
5
|
export { Snapshot } from "./snapshot.js";
|
|
5
6
|
export { APIError, StreamError, AuthenticationError, NotSupportedError } from "./errors.js";
|
|
6
7
|
export type { Credentials, WithRuntime } from "./client.js";
|
|
@@ -16,13 +17,6 @@ export declare const Session: {
|
|
|
16
17
|
new (...args: unknown[]): never;
|
|
17
18
|
readonly [key: string]: (...args: unknown[]) => never;
|
|
18
19
|
};
|
|
19
|
-
export declare const SandboxUser: {
|
|
20
|
-
(...args: unknown[]): never;
|
|
21
|
-
new (...args: unknown[]): never;
|
|
22
|
-
readonly [key: string]: (...args: unknown[]) => never;
|
|
23
|
-
};
|
|
24
|
-
export declare class SandboxUserAlreadyExistsError extends Error {
|
|
25
|
-
}
|
|
26
20
|
export declare const defineSandboxProxy: {
|
|
27
21
|
(...args: unknown[]): never;
|
|
28
22
|
new (...args: unknown[]): never;
|
package/dist/vercel/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { NotSupportedError } from "./errors.js";
|
|
|
9
9
|
export { Sandbox, DEFAULT_TIMEOUT_MS, DEFAULT_VCPUS, MEMORY_MIB_PER_VCPU, networkRules, toRuntimePath, } from "./sandbox.js";
|
|
10
10
|
export { Command, CommandFinished, } from "./command.js";
|
|
11
11
|
export { FileSystem, Stats, Dirent } from "./filesystem.js";
|
|
12
|
+
export { SandboxUser, SandboxUserAlreadyExistsError } from "./sandbox-user.js";
|
|
12
13
|
export { Snapshot } from "./snapshot.js";
|
|
13
14
|
export { APIError, StreamError, AuthenticationError, NotSupportedError } from "./errors.js";
|
|
14
15
|
/** Vercel's default region; Runtime runs in one US region. */
|
|
@@ -32,7 +33,4 @@ function unsupportedExport(name, alternative) {
|
|
|
32
33
|
}
|
|
33
34
|
export const Drive = unsupportedExport("Drive", "Use a Runtime volume (runtime.volumes) and mount it with withruntime: { create: { volumes: [{ volumeId, path }] } }.");
|
|
34
35
|
export const Session = unsupportedExport("Session", "A Runtime sandbox is its own session: call the methods on the sandbox.");
|
|
35
|
-
export const SandboxUser = unsupportedExport("SandboxUser", "Commands run as the sandbox owner with passwordless sudo; create users with `sudo useradd` in runCommand.");
|
|
36
|
-
export class SandboxUserAlreadyExistsError extends Error {
|
|
37
|
-
}
|
|
38
36
|
export const defineSandboxProxy = unsupportedExport("sandbox proxy (defineSandboxProxy)", "Share a port with sandbox.withruntime.previews.create(port) instead.");
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Command, CommandFinished } from "./command.js";
|
|
2
|
+
import type { RunCommandParams, Sandbox } from "./sandbox.js";
|
|
3
|
+
/** A Linux user or group name Vercel accepts, which is also safe to pass to
|
|
4
|
+
* useradd, groupadd and sudo. */
|
|
5
|
+
export declare function validateName(name: string, kind: string): void;
|
|
6
|
+
/** Thrown when `sandbox.createUser` is called for an existing username. */
|
|
7
|
+
export declare class SandboxUserAlreadyExistsError extends Error {
|
|
8
|
+
readonly username: string;
|
|
9
|
+
constructor(username: string);
|
|
10
|
+
}
|
|
11
|
+
type Signal = {
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
};
|
|
14
|
+
/** A user in a sandbox, made with `sandbox.createUser` or found with
|
|
15
|
+
* `sandbox.asUser`. Commands and file calls run as this user. */
|
|
16
|
+
export declare class SandboxUser {
|
|
17
|
+
#private;
|
|
18
|
+
readonly username: string;
|
|
19
|
+
/** `/root` for root, else `/home/<username>`. */
|
|
20
|
+
readonly homeDir: string;
|
|
21
|
+
/** Use sandbox.createUser or sandbox.asUser. */
|
|
22
|
+
constructor({ sandbox, username }: {
|
|
23
|
+
sandbox: Sandbox;
|
|
24
|
+
username: string;
|
|
25
|
+
});
|
|
26
|
+
/** Runs a command as this user. `sudo: true` runs it as root instead. */
|
|
27
|
+
runCommand(command: string, args?: string[], opts?: {
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
}): Promise<CommandFinished>;
|
|
31
|
+
runCommand(params: RunCommandParams & {
|
|
32
|
+
detached: true;
|
|
33
|
+
}): Promise<Command>;
|
|
34
|
+
runCommand(params: RunCommandParams): Promise<CommandFinished>;
|
|
35
|
+
/** Writes files into this user's home (or absolute paths), then gives them
|
|
36
|
+
* and the directories made for them to this user. */
|
|
37
|
+
writeFiles(files: {
|
|
38
|
+
path: string;
|
|
39
|
+
content: string | Uint8Array;
|
|
40
|
+
mode?: number;
|
|
41
|
+
}[], opts?: Signal): Promise<void>;
|
|
42
|
+
/** The file as this user may read it, as a stream; null when missing. */
|
|
43
|
+
readFile(file: {
|
|
44
|
+
path: string;
|
|
45
|
+
cwd?: string;
|
|
46
|
+
}, opts?: Signal): Promise<NodeJS.ReadableStream | null>;
|
|
47
|
+
/** The file as this user may read it; null when missing. */
|
|
48
|
+
readFileToBuffer(file: {
|
|
49
|
+
path: string;
|
|
50
|
+
cwd?: string;
|
|
51
|
+
}, opts?: Signal): Promise<Buffer | null>;
|
|
52
|
+
/** Copies a file this user may read to the local disk; null when missing. */
|
|
53
|
+
downloadFile(src: {
|
|
54
|
+
path: string;
|
|
55
|
+
cwd?: string;
|
|
56
|
+
}, dst: {
|
|
57
|
+
path: string;
|
|
58
|
+
cwd?: string;
|
|
59
|
+
}, opts?: {
|
|
60
|
+
mkdirRecursive?: boolean;
|
|
61
|
+
signal?: AbortSignal;
|
|
62
|
+
}): Promise<string | null>;
|
|
63
|
+
/** Makes a directory owned by this user. */
|
|
64
|
+
mkDir(path: string, opts?: Signal): Promise<void>;
|
|
65
|
+
addToGroup(groupname: string, opts?: Signal): Promise<void>;
|
|
66
|
+
removeFromGroup(groupname: string, opts?: Signal): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
/** Runs `cmd args` under sudo; a non-zero exit throws `what(): <stderr>`. */
|
|
69
|
+
export declare function rootOrThrow(sandbox: Sandbox, cmd: string, args: string[], signal: AbortSignal | undefined, what: () => string): Promise<CommandFinished>;
|
|
70
|
+
export {};
|