withruntime 0.9.0 → 0.10.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 +13 -7
- package/E2B.md +12 -8
- package/README.md +15 -7
- package/README.src.md +15 -7
- package/VERCEL.md +13 -3
- package/compatibility-lock.json +84 -8
- 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 +189 -54
- package/dist/client.d.ts +29 -3
- package/dist/client.js +40 -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/credentials.d.ts +6 -1
- package/dist/credentials.js +107 -38
- package/dist/daytona/daytona.js +11 -4
- package/dist/daytona/filesystem.d.ts +17 -4
- package/dist/daytona/filesystem.js +121 -38
- package/dist/daytona/index.d.ts +1 -1
- package/dist/daytona/sandbox.d.ts +1 -1
- package/dist/daytona/sandbox.js +16 -7
- package/dist/e2b/client.js +1 -1
- package/dist/e2b/commands.d.ts +30 -8
- package/dist/e2b/commands.js +142 -54
- package/dist/e2b/index.d.ts +1 -0
- package/dist/e2b/index.js +1 -0
- package/dist/e2b/pty.d.ts +24 -0
- package/dist/e2b/pty.js +80 -0
- package/dist/e2b/sandbox.d.ts +2 -1
- package/dist/e2b/sandbox.js +49 -15
- 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 +2 -1
- package/dist/login.js +16 -10
- package/dist/modal/index.js +2 -1
- package/dist/network-cli.js +111 -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/index.d.ts +12 -0
- package/dist/products/index.js +10 -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/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 +37 -8
- package/dist/sandbox.js +432 -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/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/sandbox.d.ts +8 -8
- package/dist/vercel/sandbox.js +154 -59
- 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.10.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/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/sandbox.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ export interface CreateSandboxParams extends Credentials {
|
|
|
60
60
|
/** Milliseconds a stopped persistent sandbox is kept, as Runtime's
|
|
61
61
|
* retention (whole days, 1 to 365; 0 is 365). */
|
|
62
62
|
snapshotExpiration?: number;
|
|
63
|
-
/**
|
|
63
|
+
/** Refused: Runtime does not enforce snapshot count or eviction policies. */
|
|
64
64
|
keepLastSnapshots?: {
|
|
65
65
|
count: number;
|
|
66
66
|
expiration?: number;
|
|
@@ -198,10 +198,10 @@ export declare class Sandbox {
|
|
|
198
198
|
}): Promise<Command>;
|
|
199
199
|
runCommand(params: RunCommandParams): Promise<CommandFinished>;
|
|
200
200
|
/** A command started earlier, by its cmdId. */
|
|
201
|
-
getCommand(cmdId: string,
|
|
201
|
+
getCommand(cmdId: string, opts?: {
|
|
202
202
|
signal?: AbortSignal;
|
|
203
203
|
}): Promise<Command>;
|
|
204
|
-
mkDir(path: string,
|
|
204
|
+
mkDir(path: string, opts?: {
|
|
205
205
|
signal?: AbortSignal;
|
|
206
206
|
}): Promise<void>;
|
|
207
207
|
/** The file's bytes as a Node stream, or null when there is no file. */
|
|
@@ -235,7 +235,7 @@ export declare class Sandbox {
|
|
|
235
235
|
path: string;
|
|
236
236
|
content: string | Uint8Array;
|
|
237
237
|
mode?: number;
|
|
238
|
-
}[],
|
|
238
|
+
}[], opts?: {
|
|
239
239
|
signal?: AbortSignal;
|
|
240
240
|
}): Promise<void>;
|
|
241
241
|
/** The public address of a port listed in `ports`, such as
|
|
@@ -262,7 +262,7 @@ export declare class Sandbox {
|
|
|
262
262
|
signal?: AbortSignal;
|
|
263
263
|
}): Promise<void>;
|
|
264
264
|
/** Replaces the network rules; returns the policy given. */
|
|
265
|
-
updateNetworkPolicy(networkPolicy: NetworkPolicy,
|
|
265
|
+
updateNetworkPolicy(networkPolicy: NetworkPolicy, opts?: {
|
|
266
266
|
signal?: AbortSignal;
|
|
267
267
|
}): Promise<NetworkPolicy>;
|
|
268
268
|
/** Changes what Runtime can change on a sandbox: `timeout`, `ports`,
|
|
@@ -273,11 +273,11 @@ export declare class Sandbox {
|
|
|
273
273
|
ports?: number[];
|
|
274
274
|
networkPolicy?: NetworkPolicy;
|
|
275
275
|
snapshotExpiration?: number;
|
|
276
|
-
} & Record<string, unknown>,
|
|
276
|
+
} & Record<string, unknown>, opts?: {
|
|
277
277
|
signal?: AbortSignal;
|
|
278
278
|
}): Promise<void>;
|
|
279
|
-
/** Keeps the sandbox's
|
|
280
|
-
*
|
|
279
|
+
/** Keeps the sandbox's filesystem as a disk-only snapshot, then stops
|
|
280
|
+
* the source. Restoring it starts fresh processes, as Vercel does. Start
|
|
281
281
|
* from it with `Sandbox.create({ source: { type: "snapshot", snapshotId } })`. */
|
|
282
282
|
snapshot(opts?: {
|
|
283
283
|
expiration?: number;
|