withruntime 0.10.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.
Files changed (65) hide show
  1. package/DAYTONA.md +40 -34
  2. package/E2B.md +56 -57
  3. package/VERCEL.md +21 -19
  4. package/compatibility-lock.json +8 -84
  5. package/dist/cli.js +73 -2
  6. package/dist/client.d.ts +12 -0
  7. package/dist/client.js +27 -0
  8. package/dist/compat/lease.d.ts +36 -0
  9. package/dist/compat/lease.js +87 -0
  10. package/dist/daytona/computer-use.d.ts +162 -0
  11. package/dist/daytona/computer-use.js +324 -0
  12. package/dist/daytona/context.d.ts +10 -0
  13. package/dist/daytona/context.js +24 -0
  14. package/dist/daytona/daytona.d.ts +20 -0
  15. package/dist/daytona/daytona.js +123 -16
  16. package/dist/daytona/filesystem.js +8 -0
  17. package/dist/daytona/index.d.ts +2 -26
  18. package/dist/daytona/index.js +2 -7
  19. package/dist/daytona/process.d.ts +63 -6
  20. package/dist/daytona/process.js +224 -23
  21. package/dist/daytona/sandbox.d.ts +19 -5
  22. package/dist/daytona/sandbox.js +39 -25
  23. package/dist/e2b/client.d.ts +5 -0
  24. package/dist/e2b/client.js +5 -1
  25. package/dist/e2b/commands.d.ts +15 -6
  26. package/dist/e2b/commands.js +83 -18
  27. package/dist/e2b/errors.d.ts +7 -0
  28. package/dist/e2b/errors.js +20 -0
  29. package/dist/e2b/filesystem.d.ts +2 -1
  30. package/dist/e2b/filesystem.js +43 -6
  31. package/dist/e2b/index.d.ts +2 -2
  32. package/dist/e2b/index.js +1 -1
  33. package/dist/e2b/pty.js +6 -3
  34. package/dist/e2b/sandbox.d.ts +15 -5
  35. package/dist/e2b/sandbox.js +190 -28
  36. package/dist/e2b/users.d.ts +30 -0
  37. package/dist/e2b/users.js +185 -0
  38. package/dist/index.d.ts +1 -0
  39. package/dist/mcp.d.ts +2 -1
  40. package/dist/mcp.js +6 -1
  41. package/dist/network-cli.js +28 -0
  42. package/dist/products/browser.d.ts +60 -0
  43. package/dist/products/browser.js +37 -0
  44. package/dist/products/desktop.d.ts +2 -2
  45. package/dist/products/desktop.js +2 -2
  46. package/dist/products/images.d.ts +3 -1
  47. package/dist/products/images.js +14 -2
  48. package/dist/products/index.d.ts +2 -0
  49. package/dist/products/index.js +2 -0
  50. package/dist/products/network-products.d.ts +7 -0
  51. package/dist/products/network-products.js +3 -0
  52. package/dist/products/private-network.d.ts +22 -0
  53. package/dist/products/private-network.js +10 -0
  54. package/dist/sandbox.d.ts +22 -0
  55. package/dist/sandbox.js +47 -0
  56. package/dist/transport.d.ts +1 -1
  57. package/dist/transport.js +1 -1
  58. package/dist/types.d.ts +5 -2
  59. package/dist/vercel/index.d.ts +1 -7
  60. package/dist/vercel/index.js +1 -3
  61. package/dist/vercel/sandbox-user.d.ts +70 -0
  62. package/dist/vercel/sandbox-user.js +206 -0
  63. package/dist/vercel/sandbox.d.ts +47 -12
  64. package/dist/vercel/sandbox.js +201 -42
  65. package/package.json +1 -1
@@ -0,0 +1,206 @@
1
+ import { dirname, resolve } from "node:path";
2
+ /* Vercel's multi-user helpers over Runtime's sandbox owner, who has
3
+ passwordless sudo: a user is a real Linux user made with `sudo useradd`,
4
+ and every call as that user is `sudo -u <user>`, exactly as Vercel's own
5
+ SDK does it (checked against @vercel/sandbox 3.5.1). */
6
+ const VALID_NAME = /^[a-z_][a-z0-9_-]*$/;
7
+ const MAX_NAME_LENGTH = 32;
8
+ /** A Linux user or group name Vercel accepts, which is also safe to pass to
9
+ * useradd, groupadd and sudo. */
10
+ export function validateName(name, kind) {
11
+ if (!name)
12
+ throw new Error(`Invalid ${kind}: must not be empty`);
13
+ if (name.length > MAX_NAME_LENGTH)
14
+ throw new Error(`Invalid ${kind} "${name}": must be at most ${MAX_NAME_LENGTH} characters`);
15
+ if (!VALID_NAME.test(name))
16
+ throw new Error(`Invalid ${kind} "${name}": must match ${VALID_NAME} (lowercase letters, digits, hyphens, underscores)`);
17
+ }
18
+ /** Thrown when `sandbox.createUser` is called for an existing username. */
19
+ export class SandboxUserAlreadyExistsError extends Error {
20
+ username;
21
+ constructor(username) {
22
+ super(`Failed to create user "${username}": user already exists`);
23
+ this.name = "SandboxUserAlreadyExistsError";
24
+ this.username = username;
25
+ }
26
+ }
27
+ /** A user in a sandbox, made with `sandbox.createUser` or found with
28
+ * `sandbox.asUser`. Commands and file calls run as this user. */
29
+ export class SandboxUser {
30
+ username;
31
+ /** `/root` for root, else `/home/<username>`. */
32
+ homeDir;
33
+ #sandbox;
34
+ #primaryGroup;
35
+ /** Use sandbox.createUser or sandbox.asUser. */
36
+ constructor({ sandbox, username }) {
37
+ this.#sandbox = sandbox;
38
+ this.username = username;
39
+ this.homeDir = username === "root" ? "/root" : `/home/${username}`;
40
+ }
41
+ /** `sudo -u <user>`, in `cwd` (the user's home by default), with `env`
42
+ * given through `env` so it survives sudo. */
43
+ #wrap(params) {
44
+ const env = Object.entries(params.env ?? {}).map(([key, value]) => `${key}=${value}`);
45
+ return {
46
+ cmd: "sudo",
47
+ args: [
48
+ "-u",
49
+ this.username,
50
+ "--",
51
+ "bash",
52
+ "-c",
53
+ 'cd "$1" || exit 1; shift; exec "$@"',
54
+ "bash",
55
+ params.cwd ?? this.homeDir,
56
+ ...(env.length ? ["env", ...env] : []),
57
+ params.cmd,
58
+ ...(params.args ?? []),
59
+ ],
60
+ };
61
+ }
62
+ #resolve(path) {
63
+ return path.startsWith("/") ? path : `${this.homeDir}/${path}`;
64
+ }
65
+ runCommand(commandOrParams, args, opts = {}) {
66
+ if (typeof commandOrParams === "string")
67
+ return this.#sandbox.runCommand({
68
+ ...this.#wrap({ cmd: commandOrParams, ...(args ? { args } : {}) }),
69
+ ...(opts.signal ? { signal: opts.signal } : {}),
70
+ ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
71
+ });
72
+ const params = commandOrParams;
73
+ if (params.sudo)
74
+ return this.#sandbox.runCommand({ ...params });
75
+ const wrapped = this.#wrap(params);
76
+ return this.#sandbox.runCommand({
77
+ cmd: wrapped.cmd,
78
+ args: wrapped.args,
79
+ ...(params.detached ? { detached: true } : {}),
80
+ ...(params.stdout ? { stdout: params.stdout } : {}),
81
+ ...(params.stderr ? { stderr: params.stderr } : {}),
82
+ ...(params.signal ? { signal: params.signal } : {}),
83
+ ...(params.timeoutMs !== undefined ? { timeoutMs: params.timeoutMs } : {}),
84
+ });
85
+ }
86
+ /** Writes files into this user's home (or absolute paths), then gives them
87
+ * and the directories made for them to this user. */
88
+ async writeFiles(files, opts = {}) {
89
+ const absolute = files.map((file) => ({ ...file, path: this.#resolve(file.path) }));
90
+ await this.#sandbox.writeFiles(absolute, opts);
91
+ const paths = absolute.map((file) => file.path);
92
+ if (!paths.length)
93
+ return;
94
+ await this.#chown(paths, `${this.username}:${await this.#group(opts.signal)}`, opts.signal);
95
+ const dirs = this.#ancestorsUnderHome(paths);
96
+ if (dirs.length) {
97
+ const { group } = await this.#sandbox.getDefaultUser(opts);
98
+ await this.#chown(dirs, `${this.username}:${group}`, opts.signal);
99
+ await this.#chmod(dirs, "770", opts.signal);
100
+ }
101
+ }
102
+ /** The file as this user may read it, as a stream; null when missing. */
103
+ async readFile(file, opts = {}) {
104
+ const buffer = await this.#cat(file, opts);
105
+ if (buffer === null)
106
+ return null;
107
+ const { Readable } = await import("node:stream");
108
+ return Readable.from([buffer]);
109
+ }
110
+ /** The file as this user may read it; null when missing. */
111
+ readFileToBuffer(file, opts = {}) {
112
+ return this.#cat(file, opts);
113
+ }
114
+ /** Copies a file this user may read to the local disk; null when missing. */
115
+ async downloadFile(src, dst, opts = {}) {
116
+ const buffer = await this.#cat(src, opts);
117
+ if (buffer === null)
118
+ return null;
119
+ const { mkdir, writeFile } = await import("node:fs/promises");
120
+ const target = resolve(dst.cwd ?? "", dst.path);
121
+ if (opts.mkdirRecursive)
122
+ await mkdir(dirname(target), { recursive: true });
123
+ await writeFile(target, buffer, opts.signal ? { signal: opts.signal } : {});
124
+ return target;
125
+ }
126
+ /** Reads as this user with `base64`, so the user's own permissions apply
127
+ * and binary files arrive whole. */
128
+ async #cat(file, opts) {
129
+ const path = file.path.startsWith("/") ? file.path : `${file.cwd ?? this.homeDir}/${file.path}`;
130
+ const result = await this.runCommand({
131
+ cmd: "base64",
132
+ args: [path],
133
+ ...(opts.signal ? { signal: opts.signal } : {}),
134
+ });
135
+ if (result.exitCode !== 0) {
136
+ const stderr = await result.stderr();
137
+ if (/No such file or directory/i.test(stderr))
138
+ return null;
139
+ throw new Error(`Failed to read ${path}: ${stderr}`);
140
+ }
141
+ return Buffer.from(await result.stdout(), "base64");
142
+ }
143
+ /** Makes a directory owned by this user. */
144
+ async mkDir(path, opts = {}) {
145
+ const absolute = this.#resolve(path);
146
+ await this.#sandbox.mkDir(absolute, opts);
147
+ const dirs = [absolute, ...this.#ancestorsUnderHome([absolute])];
148
+ const { group } = await this.#sandbox.getDefaultUser(opts);
149
+ await this.#chown(dirs, `${this.username}:${group}`, opts.signal);
150
+ await this.#chmod(dirs, "770", opts.signal);
151
+ }
152
+ async addToGroup(groupname, opts = {}) {
153
+ validateName(groupname, "group name");
154
+ await this.#sandbox.addUserToGroup(this.username, groupname, opts);
155
+ }
156
+ async removeFromGroup(groupname, opts = {}) {
157
+ validateName(groupname, "group name");
158
+ await this.#sandbox.removeUserFromGroup(this.username, groupname, opts);
159
+ }
160
+ /** The user's primary group, read from the sandbox once. */
161
+ #group(signal) {
162
+ this.#primaryGroup ??= (async () => {
163
+ const result = await this.#sandbox.runCommand({
164
+ cmd: "id",
165
+ args: ["-gn", this.username],
166
+ ...(signal ? { signal } : {}),
167
+ });
168
+ if (result.exitCode !== 0)
169
+ throw new Error(`Failed to resolve the primary group of "${this.username}": ${await result.stderr()}`);
170
+ const group = (await result.stdout()).trim();
171
+ if (!group)
172
+ throw new Error(`Failed to resolve the primary group of "${this.username}"`);
173
+ return group;
174
+ })().catch((error) => {
175
+ this.#primaryGroup = undefined;
176
+ throw error;
177
+ });
178
+ return this.#primaryGroup;
179
+ }
180
+ async #chown(paths, owner, signal) {
181
+ await rootOrThrow(this.#sandbox, "chown", [owner, ...paths], signal, () => `Failed to set ownership on ${paths.join(", ")}`);
182
+ }
183
+ async #chmod(paths, mode, signal) {
184
+ await rootOrThrow(this.#sandbox, "chmod", [mode, ...paths], signal, () => `Failed to set permissions on ${paths.join(", ")}`);
185
+ }
186
+ /** The directories strictly between this user's home and each path. */
187
+ #ancestorsUnderHome(paths) {
188
+ const dirs = new Set();
189
+ const prefix = `${this.homeDir}/`;
190
+ for (const path of paths) {
191
+ let dir = path.slice(0, path.lastIndexOf("/"));
192
+ while (dir.length > this.homeDir.length && dir.startsWith(prefix)) {
193
+ dirs.add(dir);
194
+ dir = dir.slice(0, dir.lastIndexOf("/"));
195
+ }
196
+ }
197
+ return [...dirs];
198
+ }
199
+ }
200
+ /** Runs `cmd args` under sudo; a non-zero exit throws `what(): <stderr>`. */
201
+ export async function rootOrThrow(sandbox, cmd, args, signal, what) {
202
+ const result = await sandbox.runCommand({ cmd, args, sudo: true, ...(signal ? { signal } : {}) });
203
+ if (result.exitCode !== 0)
204
+ throw new Error(`${what()}: ${await result.stderr()}`);
205
+ return result;
206
+ }
@@ -3,6 +3,7 @@ import type { Runtime } from "../client.js";
3
3
  import { type Credentials, type RuntimeCreate, type RuntimeSandbox, type WithRuntime } from "./client.js";
4
4
  import { Command, CommandFinished } from "./command.js";
5
5
  import { FileSystem } from "./filesystem.js";
6
+ import { SandboxUser } from "./sandbox-user.js";
6
7
  import { Snapshot } from "./snapshot.js";
7
8
  /** Vercel's default machine: 2 vCPUs with 2048 MB each (checked 23 September
8
9
  * 2026). Runtime gives 2 vCPU and 4096 MiB. */
@@ -39,7 +40,9 @@ export interface CreateSandboxParams extends Credentials {
39
40
  /** Shared at public HTTPS addresses (Runtime previews); `domain(port)`
40
41
  * answers from them. */
41
42
  ports?: number[];
42
- /** Milliseconds. Default 300 000; 60 s to 1 hour on Runtime. */
43
+ /** Milliseconds. Default 300 000, at least 60 s. Over an hour, the
44
+ * sandbox's hour-long lease is extended while this object lives, up to the
45
+ * timeout (`sandbox.timeout`). */
43
46
  timeout?: number;
44
47
  resources?: {
45
48
  vcpus: number;
@@ -131,7 +134,8 @@ export declare class Sandbox {
131
134
  get updatedAt(): Date;
132
135
  get cwd(): string;
133
136
  get status(): "pending" | "running" | "stopping" | "stopped";
134
- /** Milliseconds. */
137
+ /** Milliseconds from the start of the session: the timeout given at
138
+ * create, with every extension, or the lease of a sandbox found by name. */
135
139
  get timeout(): number;
136
140
  get expiresAt(): Date;
137
141
  get tags(): Record<string, string>;
@@ -162,14 +166,23 @@ export declare class Sandbox {
162
166
  sourceSandbox: string;
163
167
  }): Promise<Sandbox & AsyncDisposable>;
164
168
  /** Running and stopped (paused) sandboxes, oldest first, with Vercel's
165
- * paginator: `for await (const s of await Sandbox.list())`. */
169
+ * paginator: `for await (const s of await Sandbox.list())`. A name prefix,
170
+ * creation-time bounds (`since`, `until`, epoch ms), `sortBy` and
171
+ * `sortOrder`, and a `cursor` from an earlier page are applied here, over
172
+ * every sandbox the key sees. */
166
173
  static list(params?: {
167
174
  tags?: Record<string, string>;
168
175
  limit?: number;
176
+ namePrefix?: string;
177
+ sortBy?: "createdAt" | "name" | "statusUpdatedAt";
178
+ sortOrder?: "asc" | "desc";
179
+ cursor?: string;
180
+ since?: number | Date;
181
+ until?: number | Date;
169
182
  signal?: AbortSignal;
170
183
  } & Credentials & {
171
184
  withruntime?: WithRuntime;
172
- } & Record<string, unknown>): Promise<{
185
+ }): Promise<{
173
186
  sandboxes: ListItem[];
174
187
  pagination: {
175
188
  count: number;
@@ -256,8 +269,9 @@ export declare class Sandbox {
256
269
  signal?: AbortSignal;
257
270
  }): Promise<void>;
258
271
  [Symbol.asyncDispose](): Promise<void>;
259
- /** Moves the end `duration` milliseconds later (at most an hour ahead of
260
- * now on Runtime). */
272
+ /** Moves the end `duration` milliseconds later. The lease moves at once
273
+ * as far as it may (an hour ahead of now); past that it is extended while
274
+ * this object lives. */
261
275
  extendTimeout(duration: number, opts?: {
262
276
  signal?: AbortSignal;
263
277
  }): Promise<void>;
@@ -283,16 +297,37 @@ export declare class Sandbox {
283
297
  expiration?: number;
284
298
  signal?: AbortSignal;
285
299
  }): Promise<Snapshot>;
300
+ /** Refused: Vercel hands back a token scoped to this one shell, and
301
+ * Runtime's terminal opens with the account key, which must not leave the
302
+ * server. */
286
303
  openInteractive(): Promise<never>;
287
- getDefaultUser(): Promise<{
304
+ /** The user commands run as, `runtime`, and its group. */
305
+ getDefaultUser(_opts?: {
306
+ signal?: AbortSignal;
307
+ }): Promise<{
288
308
  username: string;
289
309
  group: string;
290
310
  }>;
291
- createUser(): Promise<never>;
292
- asUser(): never;
293
- createGroup(): Promise<never>;
294
- addUserToGroup(): Promise<never>;
295
- removeUserFromGroup(): Promise<never>;
311
+ /** Makes a Linux user with its own home, as Vercel does: `useradd -m`,
312
+ * then the home given to the user and the sandbox's group, mode 770. */
313
+ createUser(username: string, opts?: {
314
+ signal?: AbortSignal;
315
+ }): Promise<SandboxUser>;
316
+ /** A handle for an existing user, such as `root`. */
317
+ asUser(username: "root" | (string & {})): SandboxUser;
318
+ /** Makes a group and its shared directory, `/shared/<group>`, setgid 2770. */
319
+ createGroup(groupname: string, opts?: {
320
+ signal?: AbortSignal;
321
+ }): Promise<{
322
+ groupname: string;
323
+ sharedDir: string;
324
+ }>;
325
+ addUserToGroup(username: string, groupname: string, opts?: {
326
+ signal?: AbortSignal;
327
+ }): Promise<void>;
328
+ removeUserFromGroup(username: string, groupname: string, opts?: {
329
+ signal?: AbortSignal;
330
+ }): Promise<void>;
296
331
  currentSession(): never;
297
332
  listSessions(): Promise<never>;
298
333
  listSnapshots(): Promise<never>;