withruntime 0.7.1 → 0.8.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 +210 -0
- package/README.md +18 -0
- package/dist/ai-harness/index.js +5 -0
- package/dist/blaxel/client.d.ts +48 -0
- package/dist/blaxel/client.js +58 -0
- package/dist/blaxel/context.d.ts +59 -0
- package/dist/blaxel/context.js +162 -0
- package/dist/blaxel/errors.d.ts +51 -0
- package/dist/blaxel/errors.js +148 -0
- package/dist/blaxel/filesystem.d.ts +147 -0
- package/dist/blaxel/filesystem.js +585 -0
- package/dist/blaxel/index.d.ts +280 -0
- package/dist/blaxel/index.js +332 -0
- package/dist/blaxel/interpreter.d.ts +74 -0
- package/dist/blaxel/interpreter.js +256 -0
- package/dist/blaxel/lookup.d.ts +37 -0
- package/dist/blaxel/lookup.js +82 -0
- package/dist/blaxel/pagination.d.ts +39 -0
- package/dist/blaxel/pagination.js +60 -0
- package/dist/blaxel/preview.d.ts +110 -0
- package/dist/blaxel/preview.js +228 -0
- package/dist/blaxel/process.d.ts +126 -0
- package/dist/blaxel/process.js +526 -0
- package/dist/blaxel/sandbox.d.ts +173 -0
- package/dist/blaxel/sandbox.js +768 -0
- package/dist/blaxel/snapshot.d.ts +119 -0
- package/dist/blaxel/snapshot.js +197 -0
- package/dist/blaxel/types.d.ts +145 -0
- package/dist/blaxel/types.js +60 -0
- package/dist/blaxel/unsupported.d.ts +22 -0
- package/dist/blaxel/unsupported.js +34 -0
- package/dist/cli.js +49 -15
- package/dist/login.d.ts +9 -36
- package/dist/login.js +0 -22
- package/dist/products/switching.d.ts +2 -2
- package/dist/sandbox.d.ts +3 -2
- package/dist/sandbox.js +3 -2
- package/dist/tar.js +72 -14
- package/dist/transport.d.ts +1 -1
- package/dist/transport.js +1 -1
- package/dist/types.d.ts +14 -1
- package/package.json +10 -2
package/dist/cli.js
CHANGED
|
@@ -46,10 +46,11 @@ Account
|
|
|
46
46
|
A new API key for CI, approved by an owner in the browser
|
|
47
47
|
referrals Your referral link: you both get up to $500
|
|
48
48
|
compare --from <provider> [--days 30] What your usage would cost at e2b, daytona, vercel,
|
|
49
|
-
modal, cloudflare, fly, lambda-microvms,
|
|
50
|
-
prime and others, and what you save
|
|
51
|
-
switch [--from <provider>] Moving from
|
|
52
|
-
|
|
49
|
+
modal, cloudflare, fly, blaxel, lambda-microvms,
|
|
50
|
+
freestyle, prime and others, and what you save
|
|
51
|
+
switch [--from <provider>] Moving from e2b, daytona, vercel, modal, cloudflare, fly
|
|
52
|
+
or blaxel: record it before your first top-up, and
|
|
53
|
+
that top-up is matched, up to $100
|
|
53
54
|
ls Everything you run, every product
|
|
54
55
|
feedback "<text>" [--kind bug|missing_feature|competitor_gap|migration_blocker|docs|pricing|praise|other]
|
|
55
56
|
[--detail <text>] [--competitor <name>] [--request-id <req_...>]
|
|
@@ -140,7 +141,7 @@ const SANDBOX_HELP = `runtime sandbox <command>
|
|
|
140
141
|
[--option SERVER.NAME=value]... [--port 8765] [--replace]
|
|
141
142
|
Start them; prints each URL and the header to send
|
|
142
143
|
mcp <id> [status] | mcp <id> stop Their state and fresh URLs; stop them
|
|
143
|
-
preview <id> <port> [--public] [--ttl <s>] Share a port at an HTTPS address
|
|
144
|
+
preview <id> <port> [--public] [--ttl <s>] Share a port at an HTTPS address (--public: paid only)
|
|
144
145
|
preview rotate <id> <port> Refuse every token given out for a private port so
|
|
145
146
|
far; prints the new token
|
|
146
147
|
previews <id> Every shared port
|
|
@@ -494,7 +495,10 @@ function remote(spec) {
|
|
|
494
495
|
return match ? { id: match[1], path: match[2] } : undefined;
|
|
495
496
|
}
|
|
496
497
|
export async function run(argv, env = process.env, out = defaultOut(argv)) {
|
|
497
|
-
|
|
498
|
+
// Only what comes before -- is the CLI's: a --json after it is the
|
|
499
|
+
// command's, whether or not the CLI was given its own.
|
|
500
|
+
const dash = argv.indexOf("--");
|
|
501
|
+
const [first, ...remaining] = argv.filter((arg, index) => arg !== "--json" || (dash !== -1 && index > dash));
|
|
498
502
|
// Sandbox is one product of several, so its commands live under it
|
|
499
503
|
// (AGENTS.md, 23 September 2026): `runtime sandbox run`, `sandbox ssh` and
|
|
500
504
|
// `sandbox port-forward`. The old top-level forms were removed, not aliased.
|
|
@@ -980,6 +984,14 @@ function usageSummary(u) {
|
|
|
980
984
|
"free trial",
|
|
981
985
|
`${hours(u.trial.availableMs)} of ${hours(u.trial.totalMs)} hours left${u.trial.reservedMs > 0 ? `, ${hours(u.trial.reservedMs)} held by running sandboxes` : ""}`,
|
|
982
986
|
]);
|
|
987
|
+
if (u.outbound) {
|
|
988
|
+
const gib = (bytes) => `${(bytes / 1_073_741_824).toLocaleString("en-US", { maximumFractionDigits: 1 })} GiB`;
|
|
989
|
+
const left = Math.max(0, u.outbound.allowanceBytes - u.outbound.freeBytes);
|
|
990
|
+
rows.push([
|
|
991
|
+
"outbound traffic this month",
|
|
992
|
+
`${gib(u.outbound.sentBytes)} sent, ${gib(left)} of ${gib(u.outbound.allowanceBytes)} free left, ${dollars(micros(u.outbound.chargedMicros))} charged`,
|
|
993
|
+
]);
|
|
994
|
+
}
|
|
983
995
|
const kinds = new Map();
|
|
984
996
|
for (const resource of u.resources ?? []) {
|
|
985
997
|
const kind = typeof resource.kind === "string" ? resource.kind : "other";
|
|
@@ -1044,13 +1056,14 @@ async function outputSoFar(runtime, sandboxId, processId) {
|
|
|
1044
1056
|
chunks,
|
|
1045
1057
|
};
|
|
1046
1058
|
}
|
|
1047
|
-
/** Dollars from integer microdollars: cents from a dollar up,
|
|
1048
|
-
*
|
|
1059
|
+
/** Dollars from integer microdollars: cents from a dollar up, four places
|
|
1060
|
+
* below one, and every place below a hundredth of a cent, so a short trial
|
|
1061
|
+
* test never reads as $0.0000. */
|
|
1049
1062
|
function dollars(micros) {
|
|
1050
1063
|
const value = BigInt(micros);
|
|
1051
1064
|
const sign = value < 0n ? "-" : "";
|
|
1052
1065
|
const size = value < 0n ? -value : value;
|
|
1053
|
-
const places = size
|
|
1066
|
+
const places = size >= 1000000n ? 2 : size >= 100n || size === 0n ? 4 : 6;
|
|
1054
1067
|
const unit = 10n ** BigInt(6 - places);
|
|
1055
1068
|
const rounded = (size + unit / 2n) / unit;
|
|
1056
1069
|
const whole = rounded / 10n ** BigInt(places);
|
|
@@ -1065,6 +1078,7 @@ const SWITCH_FROM = {
|
|
|
1065
1078
|
cloudflare: "cloudflare",
|
|
1066
1079
|
fly: "fly",
|
|
1067
1080
|
"fly-machines": "fly",
|
|
1081
|
+
blaxel: "blaxel",
|
|
1068
1082
|
};
|
|
1069
1083
|
const RIVAL_NAMES = {
|
|
1070
1084
|
e2b: "E2B",
|
|
@@ -1073,6 +1087,7 @@ const RIVAL_NAMES = {
|
|
|
1073
1087
|
modal: "Modal",
|
|
1074
1088
|
cloudflare: "Cloudflare Sandbox",
|
|
1075
1089
|
fly: "Fly",
|
|
1090
|
+
blaxel: "Blaxel",
|
|
1076
1091
|
};
|
|
1077
1092
|
/** A date as the CLI writes one: 23 September 2026. */
|
|
1078
1093
|
const longDate = (iso) => new Date(iso).toLocaleDateString("en-GB", {
|
|
@@ -1112,11 +1127,28 @@ function switchingLine(summary, provider) {
|
|
|
1112
1127
|
: `${recorded}; its first top-up was not matched. Ask support if you think that is wrong.${once}`;
|
|
1113
1128
|
}
|
|
1114
1129
|
}
|
|
1130
|
+
/** Running time in the largest unit that keeps it above zero: a short
|
|
1131
|
+
* trial test is seconds, not "0 hours". */
|
|
1132
|
+
function runningTime(seconds) {
|
|
1133
|
+
const [amount, unit] = seconds < 60
|
|
1134
|
+
? [seconds, "second"]
|
|
1135
|
+
: seconds < 3600
|
|
1136
|
+
? [seconds / 60, "minute"]
|
|
1137
|
+
: [seconds / 3600, "hour"];
|
|
1138
|
+
const shown = amount.toLocaleString("en-US", {
|
|
1139
|
+
maximumFractionDigits: unit === "second" ? 1 : 2,
|
|
1140
|
+
});
|
|
1141
|
+
return `${shown} ${unit}${shown === "1" ? "" : "s"}`;
|
|
1142
|
+
}
|
|
1115
1143
|
/** The sandboxes the free trial paid for, said beside what was priced. */
|
|
1116
|
-
function trialNote(trial, all) {
|
|
1144
|
+
function trialNote(trial, all, trialSeconds) {
|
|
1117
1145
|
if (!trial)
|
|
1118
1146
|
return "";
|
|
1119
|
-
const which = trial === all
|
|
1147
|
+
const which = trial === all
|
|
1148
|
+
? all === 1
|
|
1149
|
+
? "It"
|
|
1150
|
+
: "All of them"
|
|
1151
|
+
: `${trial} of them, ${runningTime(trialSeconds)} of that time,`;
|
|
1120
1152
|
return ` ${which} ran on the free trial, which charged nothing; Runtime's side prices ${trial === 1 ? "it" : "them"} at the standard rates, what the same work costs on paid credit.`;
|
|
1121
1153
|
}
|
|
1122
1154
|
async function switching(command, argv, env, out) {
|
|
@@ -1147,7 +1179,7 @@ async function switching(command, argv, env, out) {
|
|
|
1147
1179
|
return 0;
|
|
1148
1180
|
}
|
|
1149
1181
|
if (!from)
|
|
1150
|
-
throw usage(`Name the rival: ${me} compare --from e2b (or daytona, vercel, modal, cloudflare, fly, fly-machines, lambda-microvms, freestyle, prime...).`);
|
|
1182
|
+
throw usage(`Name the rival: ${me} compare --from e2b (or daytona, vercel, modal, cloudflare, fly, fly-machines, blaxel, lambda-microvms, freestyle, prime...).`);
|
|
1151
1183
|
/* The server holds the list of rivals it has rates for, and names them all
|
|
1152
1184
|
when it refuses one, so a rival added there needs no new CLI. */
|
|
1153
1185
|
const days = integer(args, "days");
|
|
@@ -1178,13 +1210,13 @@ async function switching(command, argv, env, out) {
|
|
|
1178
1210
|
const lines = c.basis === "usage"
|
|
1179
1211
|
? [
|
|
1180
1212
|
`Your last ${c.window.days} days: ${figures}${pace}`,
|
|
1181
|
-
`Priced: ${c.usage.sandboxes.toLocaleString("en-US")} sandbox${c.usage.sandboxes === 1 ? "" : "es"}, ${(c.usage.runSeconds
|
|
1213
|
+
`Priced: ${c.usage.sandboxes.toLocaleString("en-US")} sandbox${c.usage.sandboxes === 1 ? "" : "es"}, ${runningTime(c.usage.runSeconds)} running.${trialNote(c.usage.trialSandboxes, c.usage.sandboxes, c.usage.trialRunSeconds)}${c.usage.unpricedSandboxes ? ` ${c.usage.unpricedSandboxes} left out: ${c.rival.name} publishes no price for their size.` : ""}`,
|
|
1182
1214
|
]
|
|
1183
1215
|
: [
|
|
1184
1216
|
`No settled sandboxes in your last ${c.window.days} days yet, so this prices an example: ${c.usage.sandboxes.toLocaleString("en-US")} runs of ${(c.usage.runSeconds / c.usage.sandboxes).toLocaleString("en-US")} seconds using ${(c.usage.activeCpuSeconds / c.usage.sandboxes).toLocaleString("en-US")} CPU-seconds each.`,
|
|
1185
1217
|
`The example: ${figures}.`,
|
|
1186
1218
|
];
|
|
1187
|
-
lines.push(`${c.rival.name}'s published rates, checked ${longDate(`${c.rival.checked}T12:00:00Z`)}: ${c.rival.rates.join("; ")}. Compute only: storage, network, plan fees and free allowances are left out.`);
|
|
1219
|
+
lines.push(`${c.rival.name}'s published rates, checked ${longDate(`${c.rival.checked}T12:00:00Z`)}: ${c.rival.rates.join("; ")}. Compute only, with the disk where a rival bills it while running: storage at rest, network, plan fees and free allowances are left out.`);
|
|
1188
1220
|
const line = switchingLine(c.switching, Object.hasOwn(SWITCH_FROM, from) ? SWITCH_FROM[from] : undefined);
|
|
1189
1221
|
if (line)
|
|
1190
1222
|
lines.push("", line);
|
|
@@ -2642,8 +2674,10 @@ ownInterrupt = true) {
|
|
|
2642
2674
|
}
|
|
2643
2675
|
if (out.json)
|
|
2644
2676
|
out.write(JSON.stringify(result));
|
|
2677
|
+
// Here the output streamed, whose timeout is 24 hours unless one was given
|
|
2678
|
+
// (Sandbox.exec); `--json` answers the result and prints no such line.
|
|
2645
2679
|
else if (result.timedOut)
|
|
2646
|
-
out.error(`runtime: timed out after ${timeout
|
|
2680
|
+
out.error(`runtime: timed out after ${timeout ? `${timeout} s` : "24 h"}; the command's process group was killed.`);
|
|
2647
2681
|
// Never drop output silently: say so, and do not exit 0.
|
|
2648
2682
|
const lost = extras.lostOutput(result, sbx.id);
|
|
2649
2683
|
if (lost && !out.json)
|
package/dist/login.d.ts
CHANGED
|
@@ -15,34 +15,11 @@ type LoginOptions = {
|
|
|
15
15
|
* at the same machine to press Connect agent. */
|
|
16
16
|
export declare const AGENT_WAIT_MS = 50000;
|
|
17
17
|
export declare function authenticationCommand(args: string[], env: NodeJS.ProcessEnv, options?: LoginOptions): Promise<{
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
reason?: undefined;
|
|
21
|
-
agentName?: undefined;
|
|
22
|
-
orgId?: undefined;
|
|
23
|
-
disconnected?: undefined;
|
|
24
|
-
pending?: undefined;
|
|
25
|
-
url?: undefined;
|
|
26
|
-
userCode?: undefined;
|
|
27
|
-
expiresAt?: undefined;
|
|
28
|
-
} | {
|
|
29
|
-
connected: boolean;
|
|
30
|
-
source?: undefined;
|
|
31
|
-
reason?: undefined;
|
|
18
|
+
disconnected: boolean;
|
|
19
|
+
connected?: undefined;
|
|
32
20
|
agentName?: undefined;
|
|
33
21
|
orgId?: undefined;
|
|
34
|
-
disconnected?: undefined;
|
|
35
|
-
pending?: undefined;
|
|
36
|
-
url?: undefined;
|
|
37
|
-
userCode?: undefined;
|
|
38
|
-
expiresAt?: undefined;
|
|
39
|
-
} | {
|
|
40
|
-
connected: boolean;
|
|
41
|
-
reason: string;
|
|
42
22
|
source?: undefined;
|
|
43
|
-
agentName?: undefined;
|
|
44
|
-
orgId?: undefined;
|
|
45
|
-
disconnected?: undefined;
|
|
46
23
|
pending?: undefined;
|
|
47
24
|
url?: undefined;
|
|
48
25
|
userCode?: undefined;
|
|
@@ -51,18 +28,16 @@ export declare function authenticationCommand(args: string[], env: NodeJS.Proces
|
|
|
51
28
|
connected: boolean;
|
|
52
29
|
agentName: string;
|
|
53
30
|
orgId: string;
|
|
54
|
-
source
|
|
55
|
-
reason?: undefined;
|
|
31
|
+
source: string;
|
|
56
32
|
disconnected?: undefined;
|
|
57
33
|
pending?: undefined;
|
|
58
34
|
url?: undefined;
|
|
59
35
|
userCode?: undefined;
|
|
60
36
|
expiresAt?: undefined;
|
|
61
37
|
} | {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
reason?: undefined;
|
|
38
|
+
connected: boolean;
|
|
39
|
+
source: string;
|
|
40
|
+
disconnected?: undefined;
|
|
66
41
|
agentName?: undefined;
|
|
67
42
|
orgId?: undefined;
|
|
68
43
|
pending?: undefined;
|
|
@@ -73,9 +48,8 @@ export declare function authenticationCommand(args: string[], env: NodeJS.Proces
|
|
|
73
48
|
connected: boolean;
|
|
74
49
|
agentName: string;
|
|
75
50
|
orgId: string;
|
|
76
|
-
source: string;
|
|
77
|
-
reason?: undefined;
|
|
78
51
|
disconnected?: undefined;
|
|
52
|
+
source?: undefined;
|
|
79
53
|
pending?: undefined;
|
|
80
54
|
url?: undefined;
|
|
81
55
|
userCode?: undefined;
|
|
@@ -86,11 +60,10 @@ export declare function authenticationCommand(args: string[], env: NodeJS.Proces
|
|
|
86
60
|
url: string;
|
|
87
61
|
userCode: string;
|
|
88
62
|
expiresAt: string;
|
|
89
|
-
|
|
90
|
-
reason?: undefined;
|
|
63
|
+
disconnected?: undefined;
|
|
91
64
|
agentName?: undefined;
|
|
92
65
|
orgId?: undefined;
|
|
93
|
-
|
|
66
|
+
source?: undefined;
|
|
94
67
|
}>;
|
|
95
68
|
export type PendingLogin = {
|
|
96
69
|
/** Open this in a browser, where the account owner approves the agent. */
|
package/dist/login.js
CHANGED
|
@@ -109,28 +109,6 @@ export async function authenticationCommand(args, env, options = {}) {
|
|
|
109
109
|
const notify = options.notify ?? ((message) => process.stderr.write(`${message}\n`));
|
|
110
110
|
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
111
111
|
const [command, ...rest] = args;
|
|
112
|
-
if (command === "auth" && rest.length === 1 && rest[0] === "status") {
|
|
113
|
-
if (env.RUNTIME_API_KEY) {
|
|
114
|
-
await new Runtime({
|
|
115
|
-
apiKey: env.RUNTIME_API_KEY,
|
|
116
|
-
baseUrl: origins.api,
|
|
117
|
-
fetch: fetcher,
|
|
118
|
-
}).usage();
|
|
119
|
-
return { connected: true, source: "environment" };
|
|
120
|
-
}
|
|
121
|
-
const saved = await store.read();
|
|
122
|
-
if (!saved)
|
|
123
|
-
return { connected: false };
|
|
124
|
-
try {
|
|
125
|
-
await request("confirm", undefined, saved.key);
|
|
126
|
-
}
|
|
127
|
-
catch (error) {
|
|
128
|
-
if (error instanceof ConnectionError && error.status === 401)
|
|
129
|
-
return { connected: false, reason: "revoked" };
|
|
130
|
-
throw error;
|
|
131
|
-
}
|
|
132
|
-
return { connected: true, agentName: saved.agentName, orgId: saved.orgId };
|
|
133
|
-
}
|
|
134
112
|
if (command === "logout" && rest.length === 0) {
|
|
135
113
|
if (env.RUNTIME_API_KEY)
|
|
136
114
|
throw new Error("This shell uses RUNTIME_API_KEY. Remove that override before disconnecting the saved connection.");
|
|
@@ -3,9 +3,9 @@ import type { RequestOptions, Transport } from "../transport.js";
|
|
|
3
3
|
* Machines, priced only at the size Fly publishes a rate for here. The server
|
|
4
4
|
* prices every rival its rates table carries and names them when it refuses
|
|
5
5
|
* one, so any string is sent as it is. */
|
|
6
|
-
export type CompareProvider = "e2b" | "daytona" | "vercel" | "modal" | "cloudflare" | "fly" | "fly-machines" | (string & {});
|
|
6
|
+
export type CompareProvider = "e2b" | "daytona" | "vercel" | "modal" | "cloudflare" | "fly" | "fly-machines" | "blaxel" | (string & {});
|
|
7
7
|
/** The rivals a switch can be recorded from. */
|
|
8
|
-
export type SwitchProvider = "e2b" | "daytona" | "vercel" | "modal" | "cloudflare" | "fly";
|
|
8
|
+
export type SwitchProvider = "e2b" | "daytona" | "vercel" | "modal" | "cloudflare" | "fly" | "blaxel";
|
|
9
9
|
export type SwitchingSummary = {
|
|
10
10
|
/** False while the programme is paused. */
|
|
11
11
|
enabled: boolean;
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -218,8 +218,9 @@ export declare class Files {
|
|
|
218
218
|
*/
|
|
219
219
|
readStream(path: string, options?: RequestOptions): Promise<ReadableStream<Uint8Array>>;
|
|
220
220
|
readText(path: string, options?: RequestOptions): Promise<string>;
|
|
221
|
-
/** Writes a file
|
|
222
|
-
* ones
|
|
221
|
+
/** Writes a file, atomically, making parent directories. Under /workspace
|
|
222
|
+
* any size, large ones in parallel 1 MiB chunks checked against their
|
|
223
|
+
* SHA-256; elsewhere, with the sandbox user's rights, up to 1 MiB. `mode`
|
|
223
224
|
* sets its permissions (0o755 for a program); 0o644 when left out. */
|
|
224
225
|
write(path: string, data: string | Uint8Array, writeOptions?: RequestOptions & {
|
|
225
226
|
mode?: number;
|
package/dist/sandbox.js
CHANGED
|
@@ -718,8 +718,9 @@ export class Files {
|
|
|
718
718
|
async readText(path, options = {}) {
|
|
719
719
|
return new TextDecoder().decode(await this.read(path, options));
|
|
720
720
|
}
|
|
721
|
-
/** Writes a file
|
|
722
|
-
* ones
|
|
721
|
+
/** Writes a file, atomically, making parent directories. Under /workspace
|
|
722
|
+
* any size, large ones in parallel 1 MiB chunks checked against their
|
|
723
|
+
* SHA-256; elsewhere, with the sandbox user's rights, up to 1 MiB. `mode`
|
|
723
724
|
* sets its permissions (0o755 for a program); 0o644 when left out. */
|
|
724
725
|
async write(path, data, writeOptions = {}) {
|
|
725
726
|
const { mode, ...options } = writeOptions;
|
package/dist/tar.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdir, readdir, readFile,
|
|
2
|
-
import { join, relative, resolve, sep } from "node:path";
|
|
1
|
+
import { chmod, lstat, mkdir, readdir, readFile, realpath, symlink, unlink, writeFile, } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { gunzipSync, gzipSync } from "node:zlib";
|
|
4
4
|
/* A small ustar writer and reader for directory uploads and downloads, so the
|
|
5
5
|
SDK needs no dependency. Regular files, directories and symbolic links;
|
|
@@ -68,17 +68,62 @@ export async function packDirectory(root) {
|
|
|
68
68
|
parts.push(new Uint8Array(1024));
|
|
69
69
|
return gzipSync(Buffer.concat(parts));
|
|
70
70
|
}
|
|
71
|
+
/* The archive comes from a sandbox, whose contents the customer's untrusted code
|
|
72
|
+
controls, so nothing in it may write outside `target`. A lexical check on each
|
|
73
|
+
name is not enough: a link `x -> ../outside` followed by `x/file` passes it and
|
|
74
|
+
writes through the link. So no write ever passes through a link, links are made
|
|
75
|
+
only after every file and directory is in place, and each link is then walked
|
|
76
|
+
through the finished tree and removed, failing the unpack, if it leads out. */
|
|
77
|
+
async function linkAt(path) {
|
|
78
|
+
try {
|
|
79
|
+
return (await lstat(path)).isSymbolicLink();
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Throws unless `destination` is inside `root` and no part of the way to it,
|
|
86
|
+
itself included, is a link. */
|
|
87
|
+
async function assertPlain(root, destination, name) {
|
|
88
|
+
if (destination !== root && !destination.startsWith(root + sep))
|
|
89
|
+
throw new Error(`Refusing an archive entry outside the target: ${name}`);
|
|
90
|
+
let path = root;
|
|
91
|
+
for (const part of relative(root, destination).split(sep).filter(Boolean)) {
|
|
92
|
+
path = join(path, part);
|
|
93
|
+
if (await linkAt(path))
|
|
94
|
+
throw new Error(`Refusing an archive entry that passes through a link: ${name}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Where following `link` from `directory` leads, or undefined when the way
|
|
98
|
+
leaves `root` or turns at a link before its last step. The last step may
|
|
99
|
+
be a link; that link is checked on its own. */
|
|
100
|
+
async function linkStaysInside(root, directory, link) {
|
|
101
|
+
if (isAbsolute(link))
|
|
102
|
+
return false;
|
|
103
|
+
const parts = link.split("/").filter((part) => part && part !== ".");
|
|
104
|
+
let path = directory;
|
|
105
|
+
for (const [index, part] of parts.entries()) {
|
|
106
|
+
path = part === ".." ? dirname(path) : join(path, part);
|
|
107
|
+
if (path !== root && !path.startsWith(root + sep))
|
|
108
|
+
return false;
|
|
109
|
+
if (index < parts.length - 1 && part !== ".." && (await linkAt(path)))
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
71
114
|
/** Unpacks into `target`, refusing any entry that would land outside it. */
|
|
72
115
|
export async function unpackArchive(archive, target) {
|
|
73
116
|
const data = gunzipSync(archive);
|
|
74
|
-
|
|
75
|
-
|
|
117
|
+
await mkdir(resolve(target), { recursive: true });
|
|
118
|
+
const root = await realpath(resolve(target));
|
|
76
119
|
const text = (start, length) => {
|
|
77
120
|
const slice = data.subarray(start, start + length);
|
|
78
121
|
const end = slice.indexOf(0);
|
|
79
122
|
return new TextDecoder().decode(end < 0 ? slice : slice.subarray(0, end));
|
|
80
123
|
};
|
|
124
|
+
const links = [];
|
|
81
125
|
let longName;
|
|
126
|
+
let longLink;
|
|
82
127
|
for (let offset = 0; offset + 512 <= data.length;) {
|
|
83
128
|
if (data.subarray(offset, offset + 512).every((byte) => byte === 0))
|
|
84
129
|
break;
|
|
@@ -86,31 +131,44 @@ export async function unpackArchive(archive, target) {
|
|
|
86
131
|
const type = String.fromCharCode(data[offset + 156] ?? 48);
|
|
87
132
|
const prefix = text(offset + 345, 155);
|
|
88
133
|
let name = longName ?? (prefix ? `${prefix}/${text(offset, 100)}` : text(offset, 100));
|
|
134
|
+
const link = longLink ?? text(offset + 157, 100);
|
|
89
135
|
longName = undefined;
|
|
136
|
+
longLink = undefined;
|
|
90
137
|
const mode = parseInt(text(offset + 100, 8).trim() || "644", 8);
|
|
91
|
-
const link = text(offset + 157, 100);
|
|
92
138
|
const body = data.subarray(offset + 512, offset + 512 + size);
|
|
93
139
|
offset += 512 + Math.ceil(size / 512) * 512;
|
|
94
|
-
if (type === "L") {
|
|
95
|
-
|
|
140
|
+
if (type === "L" || type === "K") {
|
|
141
|
+
const value = new TextDecoder().decode(body).replace(/\0.*$/s, "");
|
|
142
|
+
if (type === "L")
|
|
143
|
+
longName = value;
|
|
144
|
+
else
|
|
145
|
+
longLink = value;
|
|
96
146
|
continue;
|
|
97
147
|
}
|
|
98
148
|
name = name.replace(/^\.\//, "");
|
|
99
149
|
if (!name || name === "." || name === "./")
|
|
100
150
|
continue;
|
|
101
151
|
const destination = resolve(root, name);
|
|
102
|
-
|
|
103
|
-
throw new Error(`Refusing an archive entry outside the target: ${name}`);
|
|
152
|
+
await assertPlain(root, destination, name);
|
|
104
153
|
if (type === "5")
|
|
105
154
|
await mkdir(destination, { recursive: true });
|
|
106
|
-
else if (type === "2")
|
|
107
|
-
|
|
108
|
-
await symlink(link, destination).catch(() => undefined);
|
|
109
|
-
}
|
|
155
|
+
else if (type === "2")
|
|
156
|
+
links.push({ destination, link, name });
|
|
110
157
|
else if (type === "0" || type === "\0" || type === "7") {
|
|
111
|
-
await mkdir(
|
|
158
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
112
159
|
await writeFile(destination, body);
|
|
113
160
|
await chmod(destination, mode & 0o777);
|
|
114
161
|
}
|
|
115
162
|
}
|
|
163
|
+
for (const { destination, link, name } of links) {
|
|
164
|
+
await assertPlain(root, destination, name);
|
|
165
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
166
|
+
await symlink(link, destination).catch(() => undefined);
|
|
167
|
+
}
|
|
168
|
+
for (const { destination, link, name } of links) {
|
|
169
|
+
if (await linkStaysInside(root, dirname(destination), link))
|
|
170
|
+
continue;
|
|
171
|
+
await unlink(destination).catch(() => undefined);
|
|
172
|
+
throw new Error(`Refusing an archive link that leads outside the target: ${name} -> ${link}`);
|
|
173
|
+
}
|
|
116
174
|
}
|
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.8.0";
|
|
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
|
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.8.0";
|
|
4
4
|
export const DEFAULT_BASE_URL = "https://api.withruntime.com";
|
|
5
5
|
export function apiOrigin(value) {
|
|
6
6
|
const url = new URL(value);
|
package/dist/types.d.ts
CHANGED
|
@@ -118,7 +118,8 @@ export type ExecOptions = {
|
|
|
118
118
|
env?: Record<string, string>;
|
|
119
119
|
/** Given to standard input, then closed. */
|
|
120
120
|
stdin?: string | Uint8Array;
|
|
121
|
-
/** Default 60 000
|
|
121
|
+
/** Default 60 000, or 24 hours when the output streams (onStdout, onStderr or
|
|
122
|
+
* execStream); up to 24 hours. A timeout is a result (timedOut), not an error. */
|
|
122
123
|
timeoutMs?: number;
|
|
123
124
|
onStdout?: (text: string) => void;
|
|
124
125
|
onStderr?: (text: string) => void;
|
|
@@ -201,6 +202,18 @@ export type Usage = {
|
|
|
201
202
|
reservedMs: number;
|
|
202
203
|
availableMs: number;
|
|
203
204
|
} | null;
|
|
205
|
+
/** Outbound traffic this calendar month, UTC: the first `allowanceBytes`
|
|
206
|
+
* (100 GiB) an account sends are free, the rest $0.02 per decimal GB. */
|
|
207
|
+
outbound?: {
|
|
208
|
+
month: string;
|
|
209
|
+
sentBytes: number;
|
|
210
|
+
freeBytes: number;
|
|
211
|
+
billableBytes: number;
|
|
212
|
+
allowanceBytes: number;
|
|
213
|
+
chargedMicros: string;
|
|
214
|
+
/** What the balance or a spending limit could not cover; never charged. */
|
|
215
|
+
writtenOffMicros: string;
|
|
216
|
+
} | null;
|
|
204
217
|
resources: Array<Record<string, unknown>>;
|
|
205
218
|
[key: string]: unknown;
|
|
206
219
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "withruntime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"mcpName": "com.withruntime/runtime",
|
|
6
6
|
"type": "module",
|
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
"import": "./dist/vercel/index.js",
|
|
31
31
|
"default": "./dist/vercel/index.js"
|
|
32
32
|
},
|
|
33
|
+
"./blaxel": {
|
|
34
|
+
"types": "./dist/blaxel/index.d.ts",
|
|
35
|
+
"import": "./dist/blaxel/index.js",
|
|
36
|
+
"default": "./dist/blaxel/index.js"
|
|
37
|
+
},
|
|
33
38
|
"./tools": {
|
|
34
39
|
"types": "./dist/tools/index.d.ts",
|
|
35
40
|
"import": "./dist/tools/index.js",
|
|
@@ -66,6 +71,7 @@
|
|
|
66
71
|
"E2B.md",
|
|
67
72
|
"DAYTONA.md",
|
|
68
73
|
"VERCEL.md",
|
|
74
|
+
"BLAXEL.md",
|
|
69
75
|
"LICENSE"
|
|
70
76
|
],
|
|
71
77
|
"engines": {
|
|
@@ -76,7 +82,7 @@
|
|
|
76
82
|
"prepack": "bun run build",
|
|
77
83
|
"check-types": "tsc --noEmit",
|
|
78
84
|
"lint": "eslint",
|
|
79
|
-
"test": "bun
|
|
85
|
+
"test": "bun scripts/test.ts",
|
|
80
86
|
"test:java": "bun ../../sdks/java/scripts/check.ts",
|
|
81
87
|
"test:ruby": "bun ../../sdks/ruby/scripts/check.ts",
|
|
82
88
|
"test:python": "bun scripts/test-python.ts",
|
|
@@ -93,6 +99,8 @@
|
|
|
93
99
|
"@openai/agents": "0.18.0",
|
|
94
100
|
"@openai/agents-core": "0.18.0",
|
|
95
101
|
"@openrouter/sdk": "1.3.27",
|
|
102
|
+
"@runtime/cloud": "workspace:*",
|
|
103
|
+
"@runtime/cloud-guide": "workspace:*",
|
|
96
104
|
"@runtime/eslint-config": "workspace:*",
|
|
97
105
|
"@runtime/typescript-config": "workspace:*",
|
|
98
106
|
"@types/bun": "^1.4.0",
|