withruntime 0.6.1 → 0.7.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/E2B.md +32 -32
- package/README.md +3 -3
- package/dist/ai/index.d.ts +21 -5
- package/dist/ai/index.js +14 -21
- package/dist/ai-harness/index.js +15 -2
- package/dist/billing-cli.d.ts +21 -0
- package/dist/billing-cli.js +158 -0
- package/dist/cli-extras.d.ts +15 -0
- package/dist/cli-extras.js +60 -4
- package/dist/cli-name.d.ts +6 -0
- package/dist/cli-name.js +8 -0
- package/dist/cli.d.ts +17 -1
- package/dist/cli.js +543 -132
- package/dist/client.d.ts +35 -6
- package/dist/client.js +23 -0
- package/dist/credentials.js +1 -2
- package/dist/daytona/filesystem.d.ts +2 -0
- package/dist/daytona/filesystem.js +9 -7
- package/dist/e2b/commands.js +11 -2
- package/dist/e2b/errors.d.ts +5 -0
- package/dist/e2b/errors.js +3 -0
- package/dist/e2b/filesystem.js +3 -2
- package/dist/e2b/sandbox.d.ts +9 -6
- package/dist/e2b/sandbox.js +31 -7
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +7 -0
- package/dist/index.d.ts +8 -6
- package/dist/index.js +1 -0
- package/dist/keys.js +1 -1
- package/dist/login.d.ts +2 -6
- package/dist/login.js +3 -8
- package/dist/network-cli.d.ts +1 -1
- package/dist/network-cli.js +64 -2
- package/dist/products/billing.d.ts +35 -0
- package/dist/products/billing.js +13 -0
- package/dist/products/index.d.ts +4 -1
- package/dist/products/index.js +4 -1
- package/dist/products/network-products.d.ts +45 -0
- package/dist/products/network-products.js +27 -0
- package/dist/products/previews.d.ts +1 -2
- package/dist/products/secrets.d.ts +14 -0
- package/dist/sandbox.d.ts +17 -3
- package/dist/sandbox.js +233 -62
- package/dist/transport.d.ts +17 -1
- package/dist/transport.js +124 -7
- package/dist/types.d.ts +0 -2
- package/dist/wallet.d.ts +30 -0
- package/dist/wallet.js +48 -0
- package/package.json +14 -3
package/dist/transport.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RuntimeError } from "./errors.js";
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.7.1";
|
|
3
3
|
export declare const DEFAULT_BASE_URL = "https://api.withruntime.com";
|
|
4
4
|
export type RequestOptions = {
|
|
5
5
|
/** Sent as Idempotency-Key. Omit it: the SDK makes one per call and keeps
|
|
@@ -30,6 +30,9 @@ export type Call = RequestOptions & {
|
|
|
30
30
|
* full trial, a full quota, a full region. Then the refusal is thrown as
|
|
31
31
|
* it came. Only a create sets it; 0 or absent fails at once. */
|
|
32
32
|
waitForCapacityMs?: number;
|
|
33
|
+
/** Called each time a call waits for room, with the refusal it is waiting
|
|
34
|
+
* out and how long it will sleep before sending the call again. */
|
|
35
|
+
onCapacityWait?: (refusal: RuntimeError, waitMs: number) => void;
|
|
33
36
|
};
|
|
34
37
|
export declare function apiOrigin(value: string): string;
|
|
35
38
|
export declare function encodeQuery(query: Query | undefined): string;
|
|
@@ -66,10 +69,23 @@ export declare class Transport {
|
|
|
66
69
|
send(call: Call): Promise<Response>;
|
|
67
70
|
json<T>(call: Call): Promise<T>;
|
|
68
71
|
bytes(call: Call): Promise<Uint8Array>;
|
|
72
|
+
/** A file's bytes, read whole and checked: the API says a body's length
|
|
73
|
+
* (x-content-length) and, when small, its SHA-256 (x-content-sha256) before
|
|
74
|
+
* sending it, and a body that falls short or differs is read again, twice,
|
|
75
|
+
* then refused. Nothing between the API and here reliably turns a stream
|
|
76
|
+
* cut part way into an error: on 25 September 2026 a 50 MB file came back
|
|
77
|
+
* 29 MB long, exit 0. */
|
|
78
|
+
fileBytes(call: Call): Promise<Uint8Array>;
|
|
79
|
+
/** A file's bytes as they arrive, for files too big to hold. The stream
|
|
80
|
+
* errors with `download_incomplete` if it ends short of the length the API
|
|
81
|
+
* promised; it is never retried part way. */
|
|
82
|
+
fileStream(call: Call): Promise<ReadableStream<Uint8Array>>;
|
|
69
83
|
/** Newline-delimited JSON events, as they arrive. */
|
|
70
84
|
events<T>(call: Call): AsyncGenerator<T>;
|
|
71
85
|
/** A WebSocket to an API path, authenticated with the same key. */
|
|
72
86
|
websocket(path: string, query?: Query): Promise<WebSocket>;
|
|
73
87
|
}
|
|
88
|
+
/** A download that ended short of (or past) the length the API promised. */
|
|
89
|
+
export declare function incompleteDownload(got: number, expected: number): RuntimeError;
|
|
74
90
|
/** No key given, none in RUNTIME_API_KEY, and none saved by `runtime login`. */
|
|
75
91
|
export declare function missingKey(): RuntimeError;
|
package/dist/transport.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConnectionError, 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.7.1";
|
|
4
4
|
export const DEFAULT_BASE_URL = "https://api.withruntime.com";
|
|
5
5
|
export function apiOrigin(value) {
|
|
6
6
|
const url = new URL(value);
|
|
@@ -198,8 +198,22 @@ export class Transport {
|
|
|
198
198
|
await sleep(backoff(attempt), signal);
|
|
199
199
|
continue;
|
|
200
200
|
}
|
|
201
|
-
|
|
201
|
+
/* An answer that took longer than the API holds its headers (90 s)
|
|
202
|
+
comes as a 200 sent early, and then its JSON: the answer, or
|
|
203
|
+
{"error": ...} when the work failed after the status went
|
|
204
|
+
(runtime-late-answer, api.md). Either way it is read here, so a
|
|
205
|
+
failure is thrown as the error it is, never returned as a success. */
|
|
206
|
+
let late;
|
|
207
|
+
if (response.ok && response.headers.get("runtime-late-answer") === "true") {
|
|
208
|
+
const text = await response.text();
|
|
209
|
+
const failed = lateFailure(text);
|
|
210
|
+
if (failed === undefined)
|
|
211
|
+
return new Response(text, { headers: response.headers });
|
|
212
|
+
late = { status: failed, text };
|
|
213
|
+
}
|
|
214
|
+
else if (response.ok)
|
|
202
215
|
return response;
|
|
216
|
+
const status = late?.status ?? response.status;
|
|
203
217
|
// Runtime never answers 407: it is a proxy refusing the tunnel, which
|
|
204
218
|
// Bun's fetch hands back as a response rather than an error.
|
|
205
219
|
if (response.status === 407 && this.#routed) {
|
|
@@ -213,15 +227,15 @@ export class Transport {
|
|
|
213
227
|
...(key ? { idempotencyKey: key } : {}),
|
|
214
228
|
});
|
|
215
229
|
}
|
|
216
|
-
const text = await response.text();
|
|
230
|
+
const text = late?.text ?? (await response.text());
|
|
217
231
|
let parsed = null;
|
|
218
232
|
try {
|
|
219
233
|
parsed = JSON.parse(text);
|
|
220
234
|
}
|
|
221
235
|
catch {
|
|
222
|
-
parsed = { error: { message: text.slice(0, 500) || `HTTP ${
|
|
236
|
+
parsed = { error: { message: text.slice(0, 500) || `HTTP ${status}` } };
|
|
223
237
|
}
|
|
224
|
-
const error = errorFor(
|
|
238
|
+
const error = errorFor(status, parsed, key);
|
|
225
239
|
// A full house: wait for a slot or for room, then send the same call
|
|
226
240
|
// again. A request that can never fit (a fork of more copies than the
|
|
227
241
|
// trial runs) is not waited for.
|
|
@@ -232,10 +246,12 @@ export class Transport {
|
|
|
232
246
|
const left = roomUntil - performance.now();
|
|
233
247
|
if (left <= 0)
|
|
234
248
|
throw error;
|
|
235
|
-
|
|
249
|
+
const pause = Math.min(left, error.retryAfterMs ?? roomBackoff(roomAttempt++));
|
|
250
|
+
call.onCapacityWait?.(error, pause);
|
|
251
|
+
await sleep(pause, signal);
|
|
236
252
|
continue;
|
|
237
253
|
}
|
|
238
|
-
const retryable = [429, 502, 503, 504].includes(
|
|
254
|
+
const retryable = [429, 502, 503, 504].includes(status) && !DELIBERATE.has(error.code);
|
|
239
255
|
if (!retry || !retryable || attempt >= this.#maxRetries)
|
|
240
256
|
throw error;
|
|
241
257
|
const header = Number(response.headers.get("retry-after"));
|
|
@@ -263,6 +279,54 @@ export class Transport {
|
|
|
263
279
|
this.#slots.give();
|
|
264
280
|
}
|
|
265
281
|
}
|
|
282
|
+
/** A file's bytes, read whole and checked: the API says a body's length
|
|
283
|
+
* (x-content-length) and, when small, its SHA-256 (x-content-sha256) before
|
|
284
|
+
* sending it, and a body that falls short or differs is read again, twice,
|
|
285
|
+
* then refused. Nothing between the API and here reliably turns a stream
|
|
286
|
+
* cut part way into an error: on 25 September 2026 a 50 MB file came back
|
|
287
|
+
* 29 MB long, exit 0. */
|
|
288
|
+
async fileBytes(call) {
|
|
289
|
+
for (let attempt = 0;; attempt++) {
|
|
290
|
+
await this.#slots.take();
|
|
291
|
+
let failure;
|
|
292
|
+
try {
|
|
293
|
+
const response = await this.send(call);
|
|
294
|
+
const body = new Uint8Array(await response.arrayBuffer());
|
|
295
|
+
const problem = await checkBody(response.headers, body);
|
|
296
|
+
if (!problem)
|
|
297
|
+
return body;
|
|
298
|
+
failure = problem;
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
this.#slots.give();
|
|
302
|
+
}
|
|
303
|
+
if (attempt >= 2 || call.signal?.aborted)
|
|
304
|
+
throw failure;
|
|
305
|
+
await sleep(backoff(attempt), call.signal);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/** A file's bytes as they arrive, for files too big to hold. The stream
|
|
309
|
+
* errors with `download_incomplete` if it ends short of the length the API
|
|
310
|
+
* promised; it is never retried part way. */
|
|
311
|
+
async fileStream(call) {
|
|
312
|
+
const response = await this.send(call);
|
|
313
|
+
const expected = promisedLength(response.headers);
|
|
314
|
+
const body = response.body ?? new ReadableStream({ start: (c) => c.close() });
|
|
315
|
+
let got = 0;
|
|
316
|
+
return body.pipeThrough(new TransformStream({
|
|
317
|
+
transform(chunk, controller) {
|
|
318
|
+
got += chunk.byteLength;
|
|
319
|
+
if (expected !== undefined && got > expected)
|
|
320
|
+
controller.error(incompleteDownload(got, expected));
|
|
321
|
+
else
|
|
322
|
+
controller.enqueue(chunk);
|
|
323
|
+
},
|
|
324
|
+
flush(controller) {
|
|
325
|
+
if (expected !== undefined && got !== expected)
|
|
326
|
+
controller.error(incompleteDownload(got, expected));
|
|
327
|
+
},
|
|
328
|
+
}));
|
|
329
|
+
}
|
|
266
330
|
/** Newline-delimited JSON events, as they arrive. */
|
|
267
331
|
async *events(call) {
|
|
268
332
|
const response = await this.send({ ...call, accept: "application/x-ndjson" });
|
|
@@ -305,6 +369,59 @@ export class Transport {
|
|
|
305
369
|
});
|
|
306
370
|
}
|
|
307
371
|
}
|
|
372
|
+
/** The status a late answer's body failed with, or undefined when it is an
|
|
373
|
+
* answer. The body is the error the API would have sent as its status. */
|
|
374
|
+
function lateFailure(text) {
|
|
375
|
+
let body;
|
|
376
|
+
try {
|
|
377
|
+
body = JSON.parse(text);
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
return 502;
|
|
381
|
+
}
|
|
382
|
+
// An API error is a body of `error` alone, with a code; an answer may carry
|
|
383
|
+
// an `error` of its own (a code cell's exception), next to other fields.
|
|
384
|
+
if (!body || typeof body !== "object" || Object.keys(body).length !== 1)
|
|
385
|
+
return undefined;
|
|
386
|
+
const error = body.error;
|
|
387
|
+
if (!error || typeof error !== "object" || typeof error.code !== "string")
|
|
388
|
+
return undefined;
|
|
389
|
+
const status = error.status;
|
|
390
|
+
return typeof status === "number" && status >= 400 ? status : 500;
|
|
391
|
+
}
|
|
392
|
+
function promisedLength(headers) {
|
|
393
|
+
const value = headers.get("x-content-length");
|
|
394
|
+
return value !== null && /^\d+$/.test(value) ? Number(value) : undefined;
|
|
395
|
+
}
|
|
396
|
+
/** A download that ended short of (or past) the length the API promised. */
|
|
397
|
+
export function incompleteDownload(got, expected) {
|
|
398
|
+
return new RuntimeError({
|
|
399
|
+
message: `The download ended at ${got} of ${expected} bytes.`,
|
|
400
|
+
code: "download_incomplete",
|
|
401
|
+
status: 0,
|
|
402
|
+
hint: "Read the file again; nothing was written for this read. If it keeps happening, report it with `npx withruntime feedback`.",
|
|
403
|
+
details: { received: got, expected },
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
/** Why a whole body cannot be trusted, or undefined when it can. */
|
|
407
|
+
async function checkBody(headers, body) {
|
|
408
|
+
const expected = promisedLength(headers);
|
|
409
|
+
if (expected !== undefined && body.byteLength !== expected)
|
|
410
|
+
return incompleteDownload(body.byteLength, expected);
|
|
411
|
+
const sha256 = headers.get("x-content-sha256");
|
|
412
|
+
if (!sha256)
|
|
413
|
+
return undefined;
|
|
414
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", body));
|
|
415
|
+
const hex = Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
416
|
+
if (hex === sha256.toLowerCase())
|
|
417
|
+
return undefined;
|
|
418
|
+
return new RuntimeError({
|
|
419
|
+
message: "The file's bytes did not match the SHA-256 the API sent with them.",
|
|
420
|
+
code: "download_incomplete",
|
|
421
|
+
status: 0,
|
|
422
|
+
hint: "Read the file again. If it keeps happening, report it with `npx withruntime feedback`.",
|
|
423
|
+
});
|
|
424
|
+
}
|
|
308
425
|
/** No key given, none in RUNTIME_API_KEY, and none saved by `runtime login`. */
|
|
309
426
|
export function missingKey() {
|
|
310
427
|
return new RuntimeError({
|
package/dist/types.d.ts
CHANGED
|
@@ -100,8 +100,6 @@ export type CreateSandbox = {
|
|
|
100
100
|
path: string;
|
|
101
101
|
mode?: "rw" | "snapshot";
|
|
102
102
|
}[];
|
|
103
|
-
/** Anything a newer server accepts (image, snapshotId, volumes, network...). */
|
|
104
|
-
[field: string]: unknown;
|
|
105
103
|
};
|
|
106
104
|
export type CommandResult = {
|
|
107
105
|
exitCode: number | null;
|
package/dist/wallet.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Topup } from "./products/billing.js";
|
|
2
|
+
export type WalletOptions = {
|
|
3
|
+
/** Default: RUNTIME_AUTH_URL, then https://withruntime.com. */
|
|
4
|
+
authUrl?: string;
|
|
5
|
+
fetch?: typeof fetch;
|
|
6
|
+
};
|
|
7
|
+
export type OpenedWalletAccount = Topup & {
|
|
8
|
+
orgId: string;
|
|
9
|
+
/** The only way to this account's key, shown once. Keep it secret. */
|
|
10
|
+
claimCode: string;
|
|
11
|
+
};
|
|
12
|
+
export type WalletClaim = {
|
|
13
|
+
status: "paid";
|
|
14
|
+
orgId: string;
|
|
15
|
+
apiKey: string;
|
|
16
|
+
} | {
|
|
17
|
+
status: Exclude<Topup["status"], "paid">;
|
|
18
|
+
orgId: string;
|
|
19
|
+
topup: Topup | null;
|
|
20
|
+
};
|
|
21
|
+
/** Opens an account and a stablecoin top-up of `usd` dollars ($10 to $10,000).
|
|
22
|
+
* `acceptTerms` must be true: opening one means agreeing to
|
|
23
|
+
* https://withruntime.com/legal/terms, so ask the person you work for first. */
|
|
24
|
+
export declare function openWalletAccount(input: {
|
|
25
|
+
usd: number;
|
|
26
|
+
acceptTerms: boolean;
|
|
27
|
+
}, options?: WalletOptions): Promise<OpenedWalletAccount>;
|
|
28
|
+
/** Exchanges a paid account's claim code for its API key, once. Until the
|
|
29
|
+
* payment arrives it answers the top-up's status instead; call it again. */
|
|
30
|
+
export declare function claimWalletAccount(claimCode: string, options?: WalletOptions): Promise<WalletClaim>;
|
package/dist/wallet.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { RuntimeError } from "./errors.js";
|
|
2
|
+
import { apiOrigin } from "./transport.js";
|
|
3
|
+
function origin(options) {
|
|
4
|
+
const env = typeof process === "undefined" ? {} : process.env;
|
|
5
|
+
return apiOrigin(options.authUrl ?? env.RUNTIME_AUTH_URL ?? "https://withruntime.com");
|
|
6
|
+
}
|
|
7
|
+
async function post(options, path, body) {
|
|
8
|
+
const fetcher = options.fetch ?? fetch;
|
|
9
|
+
let response;
|
|
10
|
+
try {
|
|
11
|
+
response = await fetcher(`${origin(options)}${path}`, {
|
|
12
|
+
method: "POST",
|
|
13
|
+
redirect: "error",
|
|
14
|
+
headers: { "content-type": "application/json" },
|
|
15
|
+
body: JSON.stringify(body),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
catch (cause) {
|
|
19
|
+
throw new RuntimeError({
|
|
20
|
+
message: `No answer from ${origin(options)}.`,
|
|
21
|
+
code: "network",
|
|
22
|
+
status: 0,
|
|
23
|
+
hint: "Retry in a minute.",
|
|
24
|
+
cause,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const answer = (await response.json().catch(() => ({})));
|
|
28
|
+
if (!response.ok)
|
|
29
|
+
throw new RuntimeError({
|
|
30
|
+
message: answer.error ?? `The request failed (HTTP ${response.status}).`,
|
|
31
|
+
code: answer.code ?? (response.status === 429 ? "rate_limited" : "request_failed"),
|
|
32
|
+
status: response.status,
|
|
33
|
+
});
|
|
34
|
+
return [response.status, answer];
|
|
35
|
+
}
|
|
36
|
+
/** Opens an account and a stablecoin top-up of `usd` dollars ($10 to $10,000).
|
|
37
|
+
* `acceptTerms` must be true: opening one means agreeing to
|
|
38
|
+
* https://withruntime.com/legal/terms, so ask the person you work for first. */
|
|
39
|
+
export async function openWalletAccount(input, options = {}) {
|
|
40
|
+
const [, opened] = await post(options, "/api/wallet/topups", input);
|
|
41
|
+
return opened;
|
|
42
|
+
}
|
|
43
|
+
/** Exchanges a paid account's claim code for its API key, once. Until the
|
|
44
|
+
* payment arrives it answers the top-up's status instead; call it again. */
|
|
45
|
+
export async function claimWalletAccount(claimCode, options = {}) {
|
|
46
|
+
const [, claim] = await post(options, "/api/wallet/claim", { claimCode });
|
|
47
|
+
return claim;
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "withruntime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"mcpName": "com.withruntime/runtime",
|
|
6
6
|
"type": "module",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"LICENSE"
|
|
70
70
|
],
|
|
71
71
|
"engines": {
|
|
72
|
-
"node": ">=22"
|
|
72
|
+
"node": ">=22.12"
|
|
73
73
|
},
|
|
74
74
|
"scripts": {
|
|
75
75
|
"build": "tsc -p tsconfig.build.json",
|
|
@@ -110,14 +110,25 @@
|
|
|
110
110
|
"registry": "https://registry.npmjs.org/"
|
|
111
111
|
},
|
|
112
112
|
"description": "Runtime Cloud: one client, CLI and MCP bridge for every Runtime product. Sandboxes first.",
|
|
113
|
+
"homepage": "https://withruntime.com",
|
|
114
|
+
"repository": {
|
|
115
|
+
"type": "git",
|
|
116
|
+
"url": "git+https://github.com/withruntime/runtime.git",
|
|
117
|
+
"directory": "packages/cloud-sdk"
|
|
118
|
+
},
|
|
119
|
+
"bugs": "https://github.com/withruntime/runtime/issues",
|
|
113
120
|
"sideEffects": false,
|
|
114
121
|
"keywords": [
|
|
115
122
|
"sandbox",
|
|
116
123
|
"cloud",
|
|
117
124
|
"agents",
|
|
125
|
+
"ai-agents",
|
|
118
126
|
"mcp",
|
|
119
127
|
"microvm",
|
|
120
|
-
"
|
|
128
|
+
"firecracker",
|
|
129
|
+
"code-execution",
|
|
130
|
+
"code-interpreter",
|
|
131
|
+
"llm"
|
|
121
132
|
],
|
|
122
133
|
"dependencies": {
|
|
123
134
|
"undici": "^7.29.1"
|