withruntime 0.8.4 → 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.
Files changed (132) hide show
  1. package/BLAXEL.md +4 -4
  2. package/COMPATIBILITY.md +145 -0
  3. package/DAYTONA.md +13 -7
  4. package/E2B.md +34 -20
  5. package/README.md +22 -11
  6. package/README.src.md +22 -11
  7. package/VERCEL.md +13 -3
  8. package/compatibility-lock.json +357 -0
  9. package/dist/ai-harness/index.js +2 -2
  10. package/dist/api-defaults.d.ts +17 -0
  11. package/dist/api-defaults.js +22 -0
  12. package/dist/blaxel/errors.js +1 -1
  13. package/dist/blaxel/lookup.d.ts +3 -2
  14. package/dist/blaxel/lookup.js +3 -2
  15. package/dist/blaxel/pagination.d.ts +2 -2
  16. package/dist/blaxel/pagination.js +45 -17
  17. package/dist/blaxel/preview.d.ts +3 -2
  18. package/dist/blaxel/preview.js +16 -14
  19. package/dist/blaxel/process.d.ts +1 -1
  20. package/dist/blaxel/process.js +66 -11
  21. package/dist/blaxel/sandbox.d.ts +2 -0
  22. package/dist/blaxel/sandbox.js +2 -0
  23. package/dist/blaxel/snapshot.js +3 -5
  24. package/dist/cli-words.d.ts +27 -0
  25. package/dist/cli-words.js +135 -0
  26. package/dist/cli.d.ts +2 -0
  27. package/dist/cli.js +351 -85
  28. package/dist/client.d.ts +33 -4
  29. package/dist/client.js +47 -12
  30. package/dist/cloudflare/identity.d.ts +7 -0
  31. package/dist/cloudflare/identity.js +13 -0
  32. package/dist/cloudflare/index.d.ts +303 -0
  33. package/dist/cloudflare/index.js +592 -0
  34. package/dist/cloudflare/worker.d.ts +419 -0
  35. package/dist/cloudflare/worker.js +447 -0
  36. package/dist/codesandbox/guest-service.d.ts +31 -0
  37. package/dist/codesandbox/guest-service.js +490 -0
  38. package/dist/codesandbox/index.d.ts +386 -0
  39. package/dist/codesandbox/index.js +1347 -0
  40. package/dist/compat/core.d.ts +40 -0
  41. package/dist/compat/core.js +238 -0
  42. package/dist/compat/files.d.ts +26 -0
  43. package/dist/compat/files.js +133 -0
  44. package/dist/compat/shell.d.ts +4 -0
  45. package/dist/compat/shell.js +165 -0
  46. package/dist/credentials.d.ts +6 -1
  47. package/dist/credentials.js +107 -38
  48. package/dist/daytona/context.js +2 -1
  49. package/dist/daytona/daytona.js +11 -4
  50. package/dist/daytona/filesystem.d.ts +17 -4
  51. package/dist/daytona/filesystem.js +121 -38
  52. package/dist/daytona/index.d.ts +1 -1
  53. package/dist/daytona/process.d.ts +1 -1
  54. package/dist/daytona/process.js +27 -4
  55. package/dist/daytona/sandbox.d.ts +1 -1
  56. package/dist/daytona/sandbox.js +16 -7
  57. package/dist/e2b/client.js +1 -1
  58. package/dist/e2b/code-interpreter.d.ts +4 -5
  59. package/dist/e2b/code-interpreter.js +54 -56
  60. package/dist/e2b/commands.d.ts +35 -12
  61. package/dist/e2b/commands.js +169 -73
  62. package/dist/e2b/errors.js +1 -1
  63. package/dist/e2b/filesystem.d.ts +8 -6
  64. package/dist/e2b/filesystem.js +127 -28
  65. package/dist/e2b/index.d.ts +1 -0
  66. package/dist/e2b/index.js +1 -0
  67. package/dist/e2b/pty.d.ts +24 -0
  68. package/dist/e2b/pty.js +80 -0
  69. package/dist/e2b/sandbox.d.ts +6 -4
  70. package/dist/e2b/sandbox.js +59 -25
  71. package/dist/errors.d.ts +8 -0
  72. package/dist/errors.js +15 -2
  73. package/dist/file-source.d.ts +16 -0
  74. package/dist/file-source.js +162 -0
  75. package/dist/freestyle/index.d.ts +309 -0
  76. package/dist/freestyle/index.js +343 -0
  77. package/dist/index.d.ts +3 -1
  78. package/dist/login.js +16 -10
  79. package/dist/mcp.js +2 -2
  80. package/dist/modal/index.d.ts +205 -0
  81. package/dist/modal/index.js +486 -0
  82. package/dist/network-cli.js +112 -44
  83. package/dist/openai-agents/index.js +18 -71
  84. package/dist/page.d.ts +1 -1
  85. package/dist/page.js +2 -2
  86. package/dist/products/audit.d.ts +3 -0
  87. package/dist/products/index.d.ts +14 -0
  88. package/dist/products/index.js +12 -0
  89. package/dist/products/interpreter.d.ts +11 -4
  90. package/dist/products/interpreter.js +33 -9
  91. package/dist/products/jobs.js +3 -2
  92. package/dist/products/limits.d.ts +10 -0
  93. package/dist/products/observability.d.ts +44 -0
  94. package/dist/products/observability.js +6 -0
  95. package/dist/products/previews.d.ts +18 -4
  96. package/dist/products/previews.js +14 -5
  97. package/dist/products/tailscale.d.ts +57 -0
  98. package/dist/products/tailscale.js +17 -0
  99. package/dist/products/volumes.d.ts +42 -0
  100. package/dist/products/volumes.js +36 -0
  101. package/dist/products/watch.d.ts +25 -4
  102. package/dist/products/watch.js +132 -18
  103. package/dist/proxy.js +3 -1
  104. package/dist/runloop/index.d.ts +326 -0
  105. package/dist/runloop/index.js +535 -0
  106. package/dist/runloop/pagination.d.ts +46 -0
  107. package/dist/runloop/pagination.js +82 -0
  108. package/dist/runloop/snapshots.d.ts +52 -0
  109. package/dist/runloop/snapshots.js +244 -0
  110. package/dist/sandbox.d.ts +76 -16
  111. package/dist/sandbox.js +696 -135
  112. package/dist/snapshots.d.ts +20 -5
  113. package/dist/snapshots.js +11 -1
  114. package/dist/sprites/index.d.ts +251 -0
  115. package/dist/sprites/index.js +588 -0
  116. package/dist/tar.d.ts +10 -1
  117. package/dist/tar.js +311 -35
  118. package/dist/tools/index.js +2 -1
  119. package/dist/transport.d.ts +23 -8
  120. package/dist/transport.js +247 -127
  121. package/dist/types.d.ts +52 -3
  122. package/dist/vercel/command.d.ts +1 -1
  123. package/dist/vercel/command.js +15 -3
  124. package/dist/vercel/errors.d.ts +2 -2
  125. package/dist/vercel/errors.js +19 -2
  126. package/dist/vercel/filesystem.d.ts +21 -18
  127. package/dist/vercel/filesystem.js +95 -48
  128. package/dist/vercel/sandbox.d.ts +8 -8
  129. package/dist/vercel/sandbox.js +159 -64
  130. package/dist/vercel/snapshot.d.ts +3 -2
  131. package/dist/vercel/snapshot.js +9 -5
  132. package/package.json +46 -3
@@ -47,18 +47,25 @@ export class Command {
47
47
  logs(opts = {}) {
48
48
  const abort = new AbortController();
49
49
  const forward = () => abort.abort();
50
- opts.signal?.addEventListener("abort", forward, { once: true });
50
+ if (opts.signal?.aborted)
51
+ abort.abort();
52
+ else
53
+ opts.signal?.addEventListener("abort", forward, { once: true });
51
54
  const lines = this.init.lines;
52
55
  const process = this.init.process;
53
56
  const sandboxName = this.init.sandboxName;
54
57
  async function* read() {
55
58
  try {
59
+ if (abort.signal.aborted)
60
+ return;
56
61
  if (lines) {
57
62
  yield* lines;
58
63
  return;
59
64
  }
60
65
  try {
61
66
  for await (const event of process.output({ signal: abort.signal })) {
67
+ if (event.type === "truncated")
68
+ throw new StreamError("output_truncated", "Some command output is no longer available.", process.id);
62
69
  if (event.type === "stdout" || event.type === "stderr")
63
70
  yield { stream: event.type, data: event.data };
64
71
  if (event.type === "exit")
@@ -68,6 +75,8 @@ export class Command {
68
75
  catch (error) {
69
76
  if (abort.signal.aborted)
70
77
  return;
78
+ if (error instanceof StreamError)
79
+ throw error;
71
80
  const translated = translate(error, sandboxName);
72
81
  throw translated instanceof Error && translated !== error
73
82
  ? translated
@@ -94,6 +103,8 @@ export class Command {
94
103
  let exit;
95
104
  await guard(async () => {
96
105
  for await (const event of this.init.process.output(params.signal ? { signal: params.signal } : {})) {
106
+ if (event.type === "truncated")
107
+ throw new StreamError("output_truncated", "Some command output is no longer available.", this.init.process.id);
97
108
  if (event.type === "stdout" || event.type === "stderr")
98
109
  lines.push({ stream: event.type, data: event.data });
99
110
  else if (event.type === "exit")
@@ -125,13 +136,14 @@ export class Command {
125
136
  return this.output("stderr", opts);
126
137
  }
127
138
  /** Sends a signal, SIGTERM by default. */
128
- async kill(signal = "SIGTERM", _opts = {}) {
139
+ async kill(signal = "SIGTERM", opts = {}) {
140
+ opts.abortSignal?.throwIfAborted();
129
141
  if (!this.init.process)
130
142
  return;
131
143
  const name = typeof signal === "number" ? SIGNAL_NUMBERS[signal] : signal;
132
144
  if (!name)
133
145
  throw new RangeError(`Signal ${signal} has no name Runtime knows; use SIGTERM, SIGKILL, SIGINT, SIGHUP, SIGQUIT, SIGUSR1 or SIGUSR2.`);
134
- 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);
135
147
  }
136
148
  }
137
149
  /** A command that has ended; `exitCode` is set. */
@@ -44,8 +44,8 @@ export declare function translate(error: unknown, sandboxName?: string): unknown
44
44
  /** Runs `work`, turning any Runtime error into Vercel's. */
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
- export declare function fsError(code: "ENOENT" | "EEXIST" | "EISDIR" | "ENOTDIR", syscall: string, path: string): Error & {
48
- code: "ENOENT" | "EEXIST" | "EISDIR" | "ENOTDIR";
47
+ export declare function fsError(code: "ENOENT" | "EEXIST" | "EISDIR" | "ENOTDIR" | "EACCES" | "ENOTEMPTY" | "EINVAL", syscall: string, path: string): Error & {
48
+ code: "ENOENT" | "ENOTDIR" | "EEXIST" | "EISDIR" | "EACCES" | "ENOTEMPTY" | "EINVAL";
49
49
  errno: number;
50
50
  syscall: string;
51
51
  path: string;
@@ -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 = { error: { code: error.code, message: error.message } };
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" },
@@ -102,8 +108,19 @@ export function fsError(code, syscall, path) {
102
108
  EEXIST: "file already exists",
103
109
  EISDIR: "illegal operation on a directory",
104
110
  ENOTDIR: "not a directory",
111
+ EACCES: "permission denied",
112
+ ENOTEMPTY: "directory not empty",
113
+ EINVAL: "invalid argument",
114
+ }[code];
115
+ const errno = {
116
+ ENOENT: -2,
117
+ EEXIST: -17,
118
+ EISDIR: -21,
119
+ ENOTDIR: -20,
120
+ EACCES: -13,
121
+ ENOTEMPTY: -39,
122
+ EINVAL: -22,
105
123
  }[code];
106
- const errno = { ENOENT: -2, EEXIST: -17, EISDIR: -21, ENOTDIR: -20 }[code];
107
124
  return Object.assign(new Error(`${code}: ${text}, ${syscall} '${path}'`), {
108
125
  code,
109
126
  errno,
@@ -5,7 +5,10 @@ export interface FsContext {
5
5
  readonly name: string;
6
6
  files(): Promise<Files>;
7
7
  /** Runs argv without a shell; resolves with the exit code and output. */
8
- run(argv: string[]): Promise<{
8
+ run(argv: string[], options?: {
9
+ stdin?: Uint8Array;
10
+ signal?: AbortSignal;
11
+ }): Promise<{
9
12
  exitCode: number | null;
10
13
  stdout: string;
11
14
  stderr: string;
@@ -28,8 +31,8 @@ export declare class Stats {
28
31
  readonly ctimeMs: number;
29
32
  readonly birthtime: Date;
30
33
  readonly birthtimeMs: number;
31
- readonly uid = 0;
32
- readonly gid = 0;
34
+ readonly uid: number;
35
+ readonly gid: number;
33
36
  readonly dev = 0;
34
37
  readonly ino = 0;
35
38
  readonly nlink = 1;
@@ -86,7 +89,7 @@ export declare class FileSystem {
86
89
  mkdir(path: string, options?: {
87
90
  recursive?: boolean;
88
91
  signal?: AbortSignal;
89
- } | number): Promise<string | undefined>;
92
+ } | number): Promise<void>;
90
93
  readdir(path: string, options?: {
91
94
  signal?: AbortSignal;
92
95
  withFileTypes?: false;
@@ -95,14 +98,14 @@ export declare class FileSystem {
95
98
  signal?: AbortSignal;
96
99
  withFileTypes: true;
97
100
  }): Promise<Dirent[]>;
98
- lstat(path: string, _options?: {
101
+ lstat(path: string, options?: {
99
102
  signal?: AbortSignal;
100
103
  }): Promise<Stats>;
101
104
  /** Follows symbolic links. */
102
105
  stat(path: string, options?: {
103
106
  signal?: AbortSignal;
104
107
  }): Promise<Stats>;
105
- unlink(path: string, _options?: {
108
+ unlink(path: string, options?: {
106
109
  signal?: AbortSignal;
107
110
  }): Promise<void>;
108
111
  rm(path: string, options?: {
@@ -110,40 +113,40 @@ export declare class FileSystem {
110
113
  force?: boolean;
111
114
  signal?: AbortSignal;
112
115
  }): Promise<void>;
113
- rmdir(path: string, _options?: {
116
+ rmdir(path: string, options?: {
114
117
  signal?: AbortSignal;
115
118
  }): Promise<void>;
116
- rename(oldPath: string, newPath: string, _options?: {
119
+ rename(oldPath: string, newPath: string, options?: {
117
120
  signal?: AbortSignal;
118
121
  }): Promise<void>;
119
- copyFile(src: string, dest: string, _options?: {
122
+ copyFile(src: string, dest: string, options?: {
120
123
  signal?: AbortSignal;
121
124
  }): Promise<void>;
122
- access(path: string, _options?: {
125
+ access(path: string, options?: {
123
126
  signal?: AbortSignal;
124
127
  }): Promise<void>;
125
- exists(path: string, _options?: {
128
+ exists(path: string, options?: {
126
129
  signal?: AbortSignal;
127
130
  }): Promise<boolean>;
128
- chmod(path: string, mode: number | string, _options?: {
131
+ chmod(path: string, mode: number | string, options?: {
129
132
  signal?: AbortSignal;
130
133
  }): Promise<void>;
131
- chown(path: string, uid: number, gid: number, _options?: {
134
+ chown(path: string, uid: number, gid: number, options?: {
132
135
  signal?: AbortSignal;
133
136
  }): Promise<void>;
134
- symlink(target: string, path: string, _options?: {
137
+ symlink(target: string, path: string, options?: {
135
138
  signal?: AbortSignal;
136
139
  }): Promise<void>;
137
- readlink(path: string, _options?: {
140
+ readlink(path: string, options?: {
138
141
  signal?: AbortSignal;
139
142
  }): Promise<string>;
140
- realpath(path: string, _options?: {
143
+ realpath(path: string, options?: {
141
144
  signal?: AbortSignal;
142
145
  }): Promise<string>;
143
- truncate(path: string, len?: number, _options?: {
146
+ truncate(path: string, len?: number, options?: {
144
147
  signal?: AbortSignal;
145
148
  }): Promise<void>;
146
- mkdtemp(prefix: string, _options?: {
149
+ mkdtemp(prefix: string, options?: {
147
150
  signal?: AbortSignal;
148
151
  }): Promise<string>;
149
152
  }
@@ -13,8 +13,8 @@ export class Stats {
13
13
  ctimeMs;
14
14
  birthtime;
15
15
  birthtimeMs;
16
- uid = 0;
17
- gid = 0;
16
+ uid;
17
+ gid;
18
18
  dev = 0;
19
19
  ino = 0;
20
20
  nlink = 1;
@@ -24,6 +24,8 @@ export class Stats {
24
24
  #type;
25
25
  constructor(entry) {
26
26
  this.#type = entry.type;
27
+ this.uid = entry.uid ?? 0;
28
+ this.gid = entry.gid ?? 0;
27
29
  this.size = entry.size;
28
30
  const typeBits = entry.type === "directory" ? 0o040000 : entry.type === "symlink" ? 0o120000 : 0o100000;
29
31
  this.mode = typeBits | ((parseInt(entry.mode, 8) || 0) & 0o7777);
@@ -61,7 +63,8 @@ export class Dirent {
61
63
  #type;
62
64
  constructor(entry, parent) {
63
65
  this.name = entry.name;
64
- this.parentPath = this.path = parent;
66
+ this.parentPath = parent;
67
+ this.path = entry.path;
65
68
  this.#type = entry.type;
66
69
  }
67
70
  isFile() {
@@ -111,18 +114,27 @@ export class FileSystem {
111
114
  }
112
115
  }
113
116
  /** Runs a coreutils command; a failure is a Node-style error. */
114
- async #run(syscall, path, argv) {
115
- const result = await this.#call(syscall, path, () => this.#ctx.run(argv));
117
+ async #run(syscall, path, argv, options) {
118
+ options?.signal?.throwIfAborted();
119
+ const result = await this.#call(syscall, path, () => this.#ctx.run(argv, options));
116
120
  if (result.exitCode === 0)
117
121
  return result.stdout;
118
122
  if (/No such file or directory/.test(result.stderr))
119
123
  throw fsError("ENOENT", syscall, path);
124
+ if (syscall === "unlink" || syscall === "rm")
125
+ throw fsError("EACCES", syscall, path);
126
+ if (syscall === "rmdir" && /not empty/i.test(result.stderr))
127
+ throw fsError("ENOTEMPTY", syscall, path);
128
+ if (syscall === "readlink")
129
+ throw fsError("EINVAL", syscall, path);
120
130
  if (/File exists/.test(result.stderr))
121
131
  throw fsError("EEXIST", syscall, path);
122
132
  if (/Is a directory/.test(result.stderr))
123
133
  throw fsError("EISDIR", syscall, path);
124
134
  if (/Not a directory/.test(result.stderr))
125
135
  throw fsError("ENOTDIR", syscall, path);
136
+ if (/Permission denied/.test(result.stderr))
137
+ throw fsError("EACCES", syscall, path);
126
138
  throw Object.assign(new Error(`${syscall} '${path}' failed: ${result.stderr.trim()}`), {
127
139
  code: "EIO",
128
140
  syscall,
@@ -131,6 +143,8 @@ export class FileSystem {
131
143
  }
132
144
  async readFile(path, options) {
133
145
  const target = this.#ctx.resolve(path);
146
+ if (options && typeof options === "object")
147
+ options.signal?.throwIfAborted();
134
148
  const files = await this.#ctx.files();
135
149
  const bytes = await this.#call("open", target, () => files.read(target, typeof options === "object" && options?.signal ? { signal: options.signal } : {}));
136
150
  const encoding = encodingOf(options);
@@ -139,37 +153,64 @@ export class FileSystem {
139
153
  }
140
154
  async writeFile(path, data, options) {
141
155
  const target = this.#ctx.resolve(path);
156
+ if (options && typeof options === "object")
157
+ options.signal?.throwIfAborted();
142
158
  const files = await this.#ctx.files();
143
- await this.#call("open", target, () => files.write(target, bytesOf(data, encodingOf(options))));
159
+ await this.#call("open", target, () => files.write(target, bytesOf(data, encodingOf(options)), typeof options === "object" && options.signal ? { signal: options.signal } : {}));
144
160
  }
145
161
  async appendFile(path, data, options) {
146
162
  const target = this.#ctx.resolve(path);
163
+ // Never read-modify-write: another caller may be appending too. The
164
+ // shell opens with O_APPEND and cat streams bytes without Python. Exec
165
+ // stdin is limited to 1 MiB; larger input uses the chunked files API.
166
+ const bytes = bytesOf(data, encodingOf(options));
167
+ const signal = typeof options === "object" ? options.signal : undefined;
168
+ signal?.throwIfAborted();
169
+ if (bytes.byteLength <= 1_048_576) {
170
+ await this.#run("open", target, ["sh", "-c", 'cat >> "$1"', "runtime-append", target], {
171
+ stdin: bytes,
172
+ ...(signal ? { signal } : {}),
173
+ });
174
+ return;
175
+ }
176
+ if (options && typeof options === "object")
177
+ options.signal?.throwIfAborted();
147
178
  const files = await this.#ctx.files();
148
- const before = await files.read(target).catch((error) => {
149
- if (missing(error))
150
- return new Uint8Array();
151
- throw translate(error, this.#ctx.name);
152
- });
153
- await this.#call("open", target, () => files.write(target, Buffer.concat([before, bytesOf(data, encodingOf(options))])));
179
+ const staging = `/workspace/.runtime/append/${crypto.randomUUID()}`;
180
+ try {
181
+ await this.#call("open", target, () => files.write(staging, bytes, { mode: 0o600, ...(signal ? { signal } : {}) }));
182
+ await this.#run("open", target, ["sh", "-c", 'cat "$1" >> "$2"', "runtime-append", staging, target], signal ? { signal } : undefined);
183
+ }
184
+ finally {
185
+ // A cleanup failure must not invite retrying a successful append.
186
+ await files.remove(staging).catch(() => undefined);
187
+ }
154
188
  }
155
189
  async mkdir(path, options) {
156
190
  const target = this.#ctx.resolve(path);
157
191
  const recursive = typeof options === "object" && options.recursive === true;
158
- await this.#run("mkdir", target, recursive ? ["mkdir", "-p", target] : ["mkdir", target]);
159
- return recursive ? target : undefined;
192
+ await this.#run("mkdir", target, recursive ? ["mkdir", "-p", "--", target] : ["mkdir", "--", target], typeof options === "object" ? options : {});
160
193
  }
161
194
  async readdir(path, options = {}) {
162
195
  const target = this.#ctx.resolve(path);
196
+ if (options && typeof options === "object")
197
+ options.signal?.throwIfAborted();
163
198
  const files = await this.#ctx.files();
164
- const entries = await this.#call("scandir", target, () => files.list(target, { depth: 1, hidden: true }));
199
+ const entries = await this.#call("scandir", target, () => files.list(target, {
200
+ depth: 1,
201
+ hidden: options.withFileTypes === true,
202
+ ...(options.signal ? { signal: options.signal } : {}),
203
+ }));
165
204
  return options.withFileTypes
166
205
  ? entries.map((entry) => new Dirent(entry, target))
167
- : entries.map((entry) => entry.name);
206
+ : entries.filter((entry) => !entry.name.startsWith(".")).map((entry) => entry.name);
168
207
  }
169
- async lstat(path, _options = {}) {
208
+ async lstat(path, options = {}) {
170
209
  const target = this.#ctx.resolve(path);
210
+ if (options && typeof options === "object")
211
+ options.signal?.throwIfAborted();
171
212
  const files = await this.#ctx.files();
172
- const found = await this.#call("lstat", target, () => files.stat(target));
213
+ const found = await this.#call("lstat", target, () => files.stat(target, options));
173
214
  if (!found.exists)
174
215
  throw fsError("ENOENT", "lstat", target);
175
216
  return new Stats(found);
@@ -180,69 +221,75 @@ export class FileSystem {
180
221
  const first = await this.lstat(target, options);
181
222
  if (!first.isSymbolicLink())
182
223
  return first;
183
- return this.lstat(await this.realpath(target), options);
224
+ return this.lstat(await this.realpath(target, options), options);
184
225
  }
185
- async unlink(path, _options = {}) {
226
+ async unlink(path, options = {}) {
186
227
  const target = this.#ctx.resolve(path);
187
- await this.#run("unlink", target, ["rm", "--", target]);
228
+ await this.#run("unlink", target, ["rm", "--", target], options);
188
229
  }
189
230
  async rm(path, options = {}) {
190
231
  const target = this.#ctx.resolve(path);
191
232
  const flags = `${options.recursive ? "r" : ""}${options.force ? "f" : ""}`;
192
- await this.#run("rm", target, flags ? ["rm", `-${flags}`, "--", target] : ["rm", "--", target]);
233
+ await this.#run("rm", target, flags ? ["rm", `-${flags}`, "--", target] : ["rm", "--", target], options);
193
234
  }
194
- async rmdir(path, _options = {}) {
235
+ async rmdir(path, options = {}) {
195
236
  const target = this.#ctx.resolve(path);
196
- await this.#run("rmdir", target, ["rmdir", "--", target]);
237
+ await this.#run("rmdir", target, ["rmdir", "--", target], options);
197
238
  }
198
- async rename(oldPath, newPath, _options = {}) {
239
+ async rename(oldPath, newPath, options = {}) {
199
240
  const from = this.#ctx.resolve(oldPath);
200
241
  const to = this.#ctx.resolve(newPath);
242
+ if (options && typeof options === "object")
243
+ options.signal?.throwIfAborted();
201
244
  const files = await this.#ctx.files();
202
- await this.#call("rename", from, () => files.rename(from, to, { overwrite: true }));
245
+ await this.#call("rename", from, () => files.rename(from, to, { overwrite: true, ...options }));
203
246
  }
204
- async copyFile(src, dest, _options = {}) {
247
+ async copyFile(src, dest, options = {}) {
205
248
  const from = this.#ctx.resolve(src);
206
249
  const to = this.#ctx.resolve(dest);
207
- await this.#run("copyfile", from, ["cp", "--", from, to]);
250
+ await this.#run("copyfile", from, ["cp", "--", from, to], options);
208
251
  }
209
- async access(path, _options = {}) {
252
+ async access(path, options = {}) {
210
253
  const target = this.#ctx.resolve(path);
211
- if (!(await this.exists(target)))
254
+ if (!(await this.exists(target, options)))
212
255
  throw fsError("ENOENT", "access", target);
213
256
  }
214
- async exists(path, _options = {}) {
257
+ async exists(path, options = {}) {
215
258
  const target = this.#ctx.resolve(path);
216
- const files = await this.#ctx.files();
217
- return this.#call("access", target, () => files.exists(target));
218
- }
219
- async chmod(path, mode, _options = {}) {
259
+ if (options && typeof options === "object")
260
+ options.signal?.throwIfAborted();
261
+ // The native stat is lstat: a dangling link exists there. Vercel's public
262
+ // exists/access follow the target, matching test -e in its published SDK.
263
+ const result = await this.#call("access", target, () => this.#ctx.run(["test", "-e", target], options));
264
+ return result.exitCode === 0;
265
+ }
266
+ async chmod(path, mode, options = {}) {
220
267
  const target = this.#ctx.resolve(path);
221
268
  const octal = typeof mode === "number" ? mode.toString(8) : mode;
222
- await this.#run("chmod", target, ["chmod", octal, "--", target]);
269
+ await this.#run("chmod", target, ["chmod", octal, "--", target], options);
223
270
  }
224
- async chown(path, uid, gid, _options = {}) {
271
+ async chown(path, uid, gid, options = {}) {
225
272
  const target = this.#ctx.resolve(path);
226
- await this.#run("chown", target, ["sudo", "chown", `${uid}:${gid}`, "--", target]);
273
+ await this.#run("chown", target, ["sudo", "chown", `${uid}:${gid}`, "--", target], options);
227
274
  }
228
- async symlink(target, path, _options = {}) {
275
+ async symlink(target, path, options = {}) {
229
276
  const link = this.#ctx.resolve(path);
230
- await this.#run("symlink", link, ["ln", "-s", "--", target, link]);
277
+ await this.#run("symlink", link, ["ln", "-s", "--", target, link], options);
231
278
  }
232
- async readlink(path, _options = {}) {
279
+ async readlink(path, options = {}) {
233
280
  const target = this.#ctx.resolve(path);
234
- return (await this.#run("readlink", target, ["readlink", "--", target])).replace(/\n$/, "");
281
+ return (await this.#run("readlink", target, ["readlink", "--", target], options)).replace(/\n$/, "");
235
282
  }
236
- async realpath(path, _options = {}) {
283
+ async realpath(path, options = {}) {
237
284
  const target = this.#ctx.resolve(path);
238
- return (await this.#run("realpath", target, ["realpath", "-e", "--", target])).replace(/\n$/, "");
285
+ return (await this.#run("realpath", target, ["realpath", "-e", "--", target], options)).replace(/\n$/, "");
239
286
  }
240
- async truncate(path, len = 0, _options = {}) {
287
+ async truncate(path, len = 0, options = {}) {
241
288
  const target = this.#ctx.resolve(path);
242
- await this.#run("open", target, ["truncate", "-s", String(len), "--", target]);
289
+ await this.#run("open", target, ["truncate", "-s", String(len), "--", target], options);
243
290
  }
244
- async mkdtemp(prefix, _options = {}) {
291
+ async mkdtemp(prefix, options = {}) {
245
292
  const base = this.#ctx.resolve(prefix);
246
- return (await this.#run("mkdtemp", base, ["mktemp", "-d", `${base}XXXXXX`])).replace(/\n$/, "");
293
+ return (await this.#run("mkdtemp", base, ["mktemp", "-d", `${base}XXXXXX`], options)).replace(/\n$/, "");
247
294
  }
248
295
  }
@@ -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
- /** Accepted: a paused Runtime sandbox keeps exactly its latest state. */
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, _opts?: {
201
+ getCommand(cmdId: string, opts?: {
202
202
  signal?: AbortSignal;
203
203
  }): Promise<Command>;
204
- mkDir(path: string, _opts?: {
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
- }[], _opts?: {
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, _opts?: {
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>, _opts?: {
276
+ } & Record<string, unknown>, opts?: {
277
277
  signal?: AbortSignal;
278
278
  }): Promise<void>;
279
- /** Keeps the sandbox's whole machine as a Runtime snapshot (files, memory
280
- * and running processes), then stops the sandbox, as Vercel does. Start
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;