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
@@ -0,0 +1,592 @@
1
+ import { createHash } from "node:crypto";
2
+ import { Runtime } from "../client.js";
3
+ import { NotFoundError } from "../errors.js";
4
+ import { client, closeShell, CompatibilityError, destroy, environment, execOptions, only, pathAt, saveEnvironment, shellCommand, shellRoot, } from "../compat/core.js";
5
+ export { CompatibilityError };
6
+ export class SandboxSecurityError extends Error {
7
+ code;
8
+ constructor(message, code) {
9
+ super(message);
10
+ this.code = code;
11
+ this.name = "SandboxSecurityError";
12
+ }
13
+ }
14
+ export function sanitizeSandboxId(id) {
15
+ if (!id || id.length > 63)
16
+ throw new SandboxSecurityError("Sandbox ID must be 1-63 characters long.", "INVALID_SANDBOX_ID_LENGTH");
17
+ if (id.startsWith("-") || id.endsWith("-"))
18
+ throw new SandboxSecurityError("Sandbox ID cannot start or end with hyphens (DNS requirement).", "INVALID_SANDBOX_ID_HYPHENS");
19
+ if (["www", "api", "admin", "root", "system", "cloudflare", "workers"].includes(id.toLowerCase()))
20
+ throw new SandboxSecurityError(`Reserved sandbox ID '${id}' is not allowed.`, "RESERVED_SANDBOX_ID");
21
+ return id;
22
+ }
23
+ const cleanEnv = (env = {}) => Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== undefined));
24
+ function sleepSeconds(value = 600) {
25
+ let seconds;
26
+ if (typeof value === "number")
27
+ seconds = value;
28
+ else {
29
+ const match = /^(\d+)(s|m|h)$/.exec(value);
30
+ if (!match)
31
+ throw new TypeError("sleepAfter must be seconds or a duration such as 5m");
32
+ seconds = Number(match[1]) * ({ s: 1, m: 60, h: 3600 }[match[2]] ?? 1);
33
+ }
34
+ if (!Number.isSafeInteger(seconds) || seconds < 0 || seconds > 86_400)
35
+ throw new CompatibilityError("Cloudflare", "sleepAfter outside whole seconds from 0 to 86400");
36
+ return seconds;
37
+ }
38
+ export function validateSandboxConfiguration(options) {
39
+ if (options.sleepAfter !== undefined)
40
+ sleepSeconds(options.sleepAfter);
41
+ if (options.keepAlive !== undefined && typeof options.keepAlive !== "boolean")
42
+ throw new TypeError("keepAlive must be a boolean");
43
+ }
44
+ /** Worker bindings are deliberately not inspected or invoked. Supply Runtime credentials
45
+ * through options.runtime in Workers; Node also supports RUNTIME_API_KEY/login. */
46
+ export function getSandbox(namespace, id, options = {}) {
47
+ sanitizeSandboxId(id);
48
+ only("Cloudflare getSandbox", options, ["sleepAfter", "keepAlive", "normalizeId", "runtime"]);
49
+ const runtime = namespace instanceof Runtime ? namespace : client(options.runtime);
50
+ return new Sandbox(runtime, options.normalizeId ? id.toLowerCase() : id, options);
51
+ }
52
+ export class Sandbox {
53
+ runtime;
54
+ options;
55
+ pending;
56
+ existing;
57
+ id;
58
+ constructor(runtime, id, options = {}) {
59
+ this.runtime = runtime;
60
+ this.options = options;
61
+ validateSandboxConfiguration(options);
62
+ this.id = id;
63
+ }
64
+ async configure(options) {
65
+ only("Cloudflare configure", options, ["sleepAfter", "keepAlive"]);
66
+ validateSandboxConfiguration(options);
67
+ const changed = (options.sleepAfter !== undefined && options.sleepAfter !== this.options.sleepAfter) ||
68
+ (options.keepAlive !== undefined && options.keepAlive !== this.options.keepAlive);
69
+ const next = { ...this.options, ...options };
70
+ if (changed && this.pending) {
71
+ const s = await this.pending;
72
+ await s.update({
73
+ idlePauseSeconds: next.keepAlive ? 0 : sleepSeconds(next.sleepAfter),
74
+ ...(options.keepAlive !== undefined ? { persistent: options.keepAlive } : {}),
75
+ });
76
+ }
77
+ Object.assign(this.options, options);
78
+ }
79
+ /** Only a successfully initialized native handle can supply a recovery id. */
80
+ existingNativeId() {
81
+ return this.existing?.id;
82
+ }
83
+ /** Reconcile an uncertain update from an authoritative read, without allocation. */
84
+ async reconcileConfiguration(nativeId = this.existingNativeId()) {
85
+ if (!nativeId)
86
+ throw new Error("The uncertain Runtime configuration has no existing sandbox identity.");
87
+ const sandbox = await this.runtime.sandboxes.get(nativeId);
88
+ const { idlePauseSeconds, persistent } = sandbox.info;
89
+ if (typeof idlePauseSeconds !== "number" ||
90
+ !Number.isSafeInteger(idlePauseSeconds) ||
91
+ idlePauseSeconds < 0 ||
92
+ idlePauseSeconds > 86_400 ||
93
+ typeof persistent !== "boolean")
94
+ throw new Error("Runtime did not return authoritative idle and persistence settings.");
95
+ const configuration = { sleepAfter: idlePauseSeconds, keepAlive: persistent };
96
+ Object.assign(this.options, configuration);
97
+ this.existing = sandbox;
98
+ this.pending = Promise.resolve(sandbox);
99
+ return configuration;
100
+ }
101
+ async native() {
102
+ if (!this.pending) {
103
+ this.pending = (async () => {
104
+ const s = await this.runtime.sandboxes.create({
105
+ name: /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(this.id)
106
+ ? this.id
107
+ : `cf-${createHash("sha256").update(this.id).digest("hex").slice(0, 40)}`,
108
+ getOrCreate: true,
109
+ labels: { "compat.provider": "cloudflare" },
110
+ idlePauseSeconds: this.options.keepAlive ? 0 : sleepSeconds(this.options.sleepAfter),
111
+ persistent: !!this.options.keepAlive,
112
+ });
113
+ if (!s.info.reused) {
114
+ try {
115
+ await saveEnvironment(s, {});
116
+ }
117
+ catch (e) {
118
+ await destroy(s);
119
+ throw e;
120
+ }
121
+ }
122
+ this.existing = s;
123
+ return s;
124
+ })();
125
+ this.pending.catch(() => {
126
+ this.pending = undefined;
127
+ });
128
+ }
129
+ return this.pending;
130
+ }
131
+ session(id = `sandbox-${this.id}`, options = {}) {
132
+ return new ExecutionSession(this, id, options);
133
+ }
134
+ exec(command, options) {
135
+ return this.session().exec(command, options);
136
+ }
137
+ execStream(command, options) {
138
+ return this.session().execStream(command, options);
139
+ }
140
+ startProcess(command, options = {}) {
141
+ return this.session(options.sessionId).startProcess(command, options);
142
+ }
143
+ async createSession(options = {}) {
144
+ only("Cloudflare session", options, [
145
+ "id",
146
+ "name",
147
+ "env",
148
+ "cwd",
149
+ "isolation",
150
+ "commandTimeoutMs",
151
+ ]);
152
+ if (options.isolation)
153
+ throw new CompatibilityError("Cloudflare", "session PID namespace isolation");
154
+ const id = options.id ?? crypto.randomUUID();
155
+ await (await this.native()).files.write(`/workspace/.runtime-compat/cloudflare/${encodeURIComponent(id)}.json`, JSON.stringify(options), { mode: 0o600 });
156
+ return this.session(id, options);
157
+ }
158
+ async getSession(id) {
159
+ if (id === `sandbox-${this.id}`)
160
+ return this.session();
161
+ const options = JSON.parse(await (await this.native()).files.readText(`/workspace/.runtime-compat/cloudflare/${encodeURIComponent(id)}.json`));
162
+ return this.session(id, options);
163
+ }
164
+ async deleteSession(id) {
165
+ if (id === `sandbox-${this.id}`)
166
+ throw new Error("The default session cannot be deleted; destroy the sandbox instead");
167
+ await closeShell(await this.native(), `cf:${id}`);
168
+ await (await this.native()).files.remove(`/workspace/.runtime-compat/cloudflare/${encodeURIComponent(id)}.json`);
169
+ return { success: true, sessionId: id, timestamp: new Date().toISOString() };
170
+ }
171
+ async destroy() {
172
+ await destroy(await this.native());
173
+ this.pending = undefined;
174
+ }
175
+ async setEnvVars(env) {
176
+ const s = await this.native();
177
+ await saveEnvironment(s, { ...(await environment(s)), ...cleanEnv(env) });
178
+ }
179
+ writeFile(path, content, options) {
180
+ return this.session().writeFile(path, content, options);
181
+ }
182
+ readFile(path, options) {
183
+ return options?.encoding === "none"
184
+ ? this.session().readFile(path, { encoding: "none" })
185
+ : this.session().readFile(path, options);
186
+ }
187
+ mkdir(path, options) {
188
+ return this.session().mkdir(path, options);
189
+ }
190
+ deleteFile(path) {
191
+ return this.session().deleteFile(path);
192
+ }
193
+ renameFile(from, to) {
194
+ return this.session().renameFile(from, to);
195
+ }
196
+ moveFile(from, to) {
197
+ return this.renameFile(from, to);
198
+ }
199
+ listFiles(path, options) {
200
+ return this.session().listFiles(path, options);
201
+ }
202
+ exists(path) {
203
+ return this.session().exists(path);
204
+ }
205
+ async listProcesses() {
206
+ const s = await this.native();
207
+ return Promise.all((await s.processes.list()).map((p) => s.processes.get(p.id).then((p) => new Process(this, p))));
208
+ }
209
+ async getProcess(id) {
210
+ try {
211
+ return new Process(this, await (await this.native()).processes.get(id));
212
+ }
213
+ catch (e) {
214
+ if (e instanceof NotFoundError)
215
+ return null;
216
+ throw e;
217
+ }
218
+ }
219
+ async killProcess(id, signal = "SIGTERM") {
220
+ const p = await this.getProcess(id);
221
+ if (!p)
222
+ throw new Error(`Process ${id} not found`);
223
+ await p.kill(signal);
224
+ }
225
+ async killAllProcesses() {
226
+ const processes = (await this.listProcesses()).filter((p) => p.status === "running");
227
+ await Promise.all(processes.map((p) => p.kill()));
228
+ return processes.length;
229
+ }
230
+ async getProcessLogs(id) {
231
+ const s = await this.native();
232
+ let cursor = 0;
233
+ let stdout = "", stderr = "";
234
+ for (;;) {
235
+ const r = await this.runtime.transport.json({
236
+ method: "GET",
237
+ path: `/v1/sandboxes/${encodeURIComponent(s.id)}/processes/${encodeURIComponent(id)}/output`,
238
+ query: { cursor, maxBytes: 196608 },
239
+ });
240
+ if (r.truncated)
241
+ throw new Error("Process logs were truncated by the host");
242
+ for (const c of r.chunks) {
243
+ if (c.stream === "stdout")
244
+ stdout += c.text;
245
+ else
246
+ stderr += c.text;
247
+ }
248
+ if (r.chunks.length === 0 || r.nextCursor <= cursor)
249
+ break;
250
+ cursor = r.nextCursor;
251
+ }
252
+ return { stdout, stderr, processId: id };
253
+ }
254
+ async exposePort(port, options) {
255
+ only("Cloudflare exposePort", options, ["name", "hostname", "token"]);
256
+ if (options.token)
257
+ throw new CompatibilityError("Cloudflare", "caller-chosen preview tokens");
258
+ const p = await (await this.native()).previews.create(port);
259
+ // Cloudflare's hostname routes through its Worker. Runtime returns its own signed URL.
260
+ return { url: p.urlWithToken ?? p.url, port, name: options.name };
261
+ }
262
+ async unexposePort(port) {
263
+ await (await this.native()).previews.delete(port);
264
+ }
265
+ }
266
+ export class ExecutionSession {
267
+ sandbox;
268
+ id;
269
+ options;
270
+ constructor(sandbox, id, options) {
271
+ this.sandbox = sandbox;
272
+ this.id = id;
273
+ this.options = options;
274
+ }
275
+ async context(options = {}) {
276
+ only("Cloudflare exec", options, [
277
+ "timeout",
278
+ "env",
279
+ "cwd",
280
+ "encoding",
281
+ "stream",
282
+ "onOutput",
283
+ "onComplete",
284
+ "onError",
285
+ "signal",
286
+ ]);
287
+ if (options.encoding && !["utf8", "utf-8"].includes(options.encoding))
288
+ throw new CompatibilityError("Cloudflare", `process encoding ${options.encoding}`);
289
+ const s = await this.sandbox.native();
290
+ return {
291
+ s,
292
+ options: await execOptions(s, {
293
+ cwd: options.cwd ?? this.options.cwd,
294
+ env: { ...cleanEnv(this.options.env), ...cleanEnv(options.env) },
295
+ timeoutMs: options.timeout ?? this.options.commandTimeoutMs,
296
+ signal: options.signal,
297
+ onStdout: (data) => {
298
+ if (options.stream)
299
+ options.onOutput?.("stdout", data);
300
+ },
301
+ onStderr: (data) => {
302
+ if (options.stream)
303
+ options.onOutput?.("stderr", data);
304
+ },
305
+ }),
306
+ };
307
+ }
308
+ async exec(command, options = {}) {
309
+ options.signal?.throwIfAborted();
310
+ const started = Date.now();
311
+ try {
312
+ const c = await this.context(options);
313
+ const r = await c.s.exec(await shellCommand(c.s, command, `cf:${this.id}`, {
314
+ env: { ...cleanEnv(this.options.env), ...cleanEnv(options.env) },
315
+ cwd: options.cwd,
316
+ }), c.options);
317
+ const result = {
318
+ success: r.exitCode === 0 && !r.timedOut,
319
+ exitCode: r.exitCode ?? -1,
320
+ stdout: r.stdout,
321
+ stderr: r.stderr,
322
+ command,
323
+ duration: Date.now() - started,
324
+ timestamp: new Date(started).toISOString(),
325
+ sessionId: this.id,
326
+ };
327
+ if (options.stream)
328
+ options.onComplete?.(result);
329
+ return result;
330
+ }
331
+ catch (e) {
332
+ options.onError?.(e instanceof Error ? e : new Error(String(e)));
333
+ throw e;
334
+ }
335
+ }
336
+ async execStream(command, options = {}) {
337
+ const encode = new TextEncoder();
338
+ const started = new Date().toISOString();
339
+ const run = this.exec.bind(this);
340
+ const sessionId = this.id;
341
+ const cancellation = new AbortController();
342
+ const signal = options.signal
343
+ ? AbortSignal.any([options.signal, cancellation.signal])
344
+ : cancellation.signal;
345
+ return new ReadableStream({
346
+ async start(controller) {
347
+ const send = (event) => {
348
+ if (!signal.aborted)
349
+ controller.enqueue(encode.encode(`data: ${JSON.stringify(event)}\n\n`));
350
+ };
351
+ try {
352
+ send({ type: "start", command, timestamp: started, sessionId: sessionId });
353
+ const result = await run(command, {
354
+ ...options,
355
+ signal,
356
+ stream: true,
357
+ onOutput: (type, data) => send({ type, data, timestamp: new Date().toISOString(), sessionId: sessionId }),
358
+ });
359
+ send({
360
+ type: "complete",
361
+ result,
362
+ exitCode: result.exitCode,
363
+ timestamp: new Date().toISOString(),
364
+ });
365
+ if (!cancellation.signal.aborted)
366
+ controller.close();
367
+ }
368
+ catch (error) {
369
+ if (!cancellation.signal.aborted)
370
+ controller.error(error);
371
+ }
372
+ },
373
+ cancel(reason) {
374
+ cancellation.abort(reason);
375
+ },
376
+ });
377
+ }
378
+ async startProcess(command, options = {}) {
379
+ only("Cloudflare startProcess", options, [
380
+ "timeout",
381
+ "env",
382
+ "cwd",
383
+ "encoding",
384
+ "sessionId",
385
+ "onOutput",
386
+ "onExit",
387
+ "onStart",
388
+ "onError",
389
+ ]);
390
+ const c = await this.context({
391
+ timeout: options.timeout,
392
+ env: options.env,
393
+ cwd: options.cwd,
394
+ encoding: options.encoding,
395
+ });
396
+ const { stdin: _stdin, ...spawnOptions } = c.options;
397
+ const p = await c.s.spawn(await shellCommand(c.s, command, `cf:${this.id}`, {
398
+ env: { ...cleanEnv(this.options.env), ...cleanEnv(options.env) },
399
+ cwd: options.cwd,
400
+ persist: false,
401
+ }), spawnOptions);
402
+ const process = new Process(this.sandbox, p, this.id);
403
+ options.onStart?.(process);
404
+ if (options.onOutput || options.onExit || options.onError)
405
+ void (async () => {
406
+ for await (const e of p.output())
407
+ if (e.type === "stdout" || e.type === "stderr")
408
+ options.onOutput?.(e.type, e.data);
409
+ else if (e.type === "exit")
410
+ options.onExit?.(e.exitCode);
411
+ else if (e.type === "truncated")
412
+ throw new Error("Process output was truncated");
413
+ })().catch((e) => options.onError?.(e instanceof Error ? e : new Error(String(e))));
414
+ return process;
415
+ }
416
+ async path(path) {
417
+ if (path.startsWith("/"))
418
+ return path;
419
+ let cwd = this.options.cwd ?? "/workspace";
420
+ try {
421
+ cwd = await (await this.sandbox.native()).files.readText(`${await shellRoot(`cf:${this.id}`)}/cwd`);
422
+ }
423
+ catch (error) {
424
+ if (!(error instanceof NotFoundError))
425
+ throw error;
426
+ }
427
+ return pathAt(path, cwd);
428
+ }
429
+ async writeFile(path, content, options = {}) {
430
+ if (options.encoding && !["utf8", "utf-8", "base64"].includes(options.encoding))
431
+ throw new CompatibilityError("Cloudflare", `file encoding ${options.encoding}`);
432
+ let bytes;
433
+ if (typeof content === "string")
434
+ bytes =
435
+ options.encoding === "base64"
436
+ ? Uint8Array.from(atob(content), (c) => c.charCodeAt(0))
437
+ : new TextEncoder().encode(content);
438
+ else {
439
+ const buffer = await new Response(content).arrayBuffer();
440
+ bytes = new Uint8Array(buffer);
441
+ }
442
+ await (await this.sandbox.native()).files.write(await this.path(path), bytes);
443
+ return { success: true, path, bytesWritten: bytes.length, timestamp: new Date().toISOString() };
444
+ }
445
+ async readFile(path, options = {}) {
446
+ const s = await this.sandbox.native(), bytes = await s.files.read(await this.path(path));
447
+ if (options.encoding === "none")
448
+ return {
449
+ success: true,
450
+ path,
451
+ content: new ReadableStream({
452
+ start(c) {
453
+ c.enqueue(bytes);
454
+ c.close();
455
+ },
456
+ }),
457
+ size: bytes.length,
458
+ mimeType: "application/octet-stream",
459
+ timestamp: new Date().toISOString(),
460
+ };
461
+ const text = new TextDecoder().decode(bytes), binary = text.includes("\0") || text.includes("\ufffd"), encoded = options.encoding === "base64" || (options.encoding === undefined && binary);
462
+ let raw = "";
463
+ if (encoded)
464
+ for (let i = 0; i < bytes.length; i += 32768)
465
+ raw += String.fromCharCode(...bytes.subarray(i, i + 32768));
466
+ return {
467
+ success: true,
468
+ path,
469
+ content: encoded ? btoa(raw) : text,
470
+ encoding: encoded ? "base64" : "utf-8",
471
+ isBinary: binary,
472
+ size: bytes.length,
473
+ timestamp: new Date().toISOString(),
474
+ };
475
+ }
476
+ async mkdir(path, options = {}) {
477
+ await (await this.sandbox.native()).files.mkdir(await this.path(path), { parents: options.recursive });
478
+ return { success: true, path, timestamp: new Date().toISOString() };
479
+ }
480
+ async deleteFile(path) {
481
+ await (await this.sandbox.native()).files.remove(await this.path(path));
482
+ return { success: true, path, timestamp: new Date().toISOString() };
483
+ }
484
+ async renameFile(path, newPath) {
485
+ await (await this.sandbox.native()).files.rename(await this.path(path), await this.path(newPath));
486
+ return { success: true, path, newPath, timestamp: new Date().toISOString() };
487
+ }
488
+ async exists(path) {
489
+ return {
490
+ success: true,
491
+ path,
492
+ exists: await (await this.sandbox.native()).files.exists(await this.path(path)),
493
+ timestamp: new Date().toISOString(),
494
+ };
495
+ }
496
+ async listFiles(path, options = {}) {
497
+ const s = await this.sandbox.native();
498
+ const directory = await this.path(path);
499
+ const entries = await s.files.list(directory, { hidden: options.includeHidden });
500
+ if (options.recursive) {
501
+ for (let i = 0; i < entries.length; i++)
502
+ if (entries[i].type === "directory")
503
+ entries.push(...(await s.files.list(entries[i].path, { hidden: options.includeHidden })));
504
+ }
505
+ const files = entries.map((e) => ({
506
+ ...e,
507
+ absolutePath: e.path,
508
+ relativePath: e.path.slice(directory.replace(/\/$/, "").length + 1),
509
+ permissions: {
510
+ readable: !!(parseInt(e.mode, 8) & 0o444),
511
+ writable: !!(parseInt(e.mode, 8) & 0o222),
512
+ executable: !!(parseInt(e.mode, 8) & 0o111),
513
+ },
514
+ }));
515
+ return { success: true, path, files, count: files.length, timestamp: new Date().toISOString() };
516
+ }
517
+ async setEnvVars(env) {
518
+ this.options = { ...this.options, env: { ...this.options.env, ...cleanEnv(env) } };
519
+ await (await this.sandbox.native()).files.write(`/workspace/.runtime-compat/cloudflare/${encodeURIComponent(this.id)}.json`, JSON.stringify(this.options), { mode: 0o600 });
520
+ }
521
+ }
522
+ export class Process {
523
+ sandbox;
524
+ native;
525
+ sessionId;
526
+ constructor(sandbox, native, sessionId) {
527
+ this.sandbox = sandbox;
528
+ this.native = native;
529
+ this.sessionId = sessionId;
530
+ }
531
+ get id() {
532
+ return this.native.id;
533
+ }
534
+ get command() {
535
+ return this.native.info.command;
536
+ }
537
+ get status() {
538
+ return this.native.info.state === "exited"
539
+ ? this.native.info.exitCode === 0
540
+ ? "completed"
541
+ : "failed"
542
+ : this.native.info.state === "timed_out"
543
+ ? "failed"
544
+ : this.native.info.state;
545
+ }
546
+ get startTime() {
547
+ return new Date(this.native.info.startedAt);
548
+ }
549
+ get endTime() {
550
+ return this.native.info.endedAt ? new Date(this.native.info.endedAt) : undefined;
551
+ }
552
+ get exitCode() {
553
+ return this.native.info.exitCode ?? undefined;
554
+ }
555
+ async kill(signal = "SIGTERM") {
556
+ if (!["SIGTERM", "SIGKILL", "SIGINT", "SIGHUP", "SIGQUIT", "SIGUSR1", "SIGUSR2"].includes(signal))
557
+ throw new TypeError(`Unsupported signal ${signal}`);
558
+ await this.native.kill(signal);
559
+ }
560
+ async getStatus() {
561
+ await this.native.refresh();
562
+ return this.status;
563
+ }
564
+ getLogs() {
565
+ return this.sandbox.getProcessLogs(this.id);
566
+ }
567
+ async waitForExit(timeout) {
568
+ const r = await this.native.wait({
569
+ signal: timeout ? AbortSignal.timeout(timeout) : undefined,
570
+ });
571
+ return { exitCode: r.exitCode ?? -1 };
572
+ }
573
+ async waitForLog(pattern, timeout = 30000) {
574
+ let output = "";
575
+ for await (const e of this.native.output({ signal: AbortSignal.timeout(timeout) })) {
576
+ if (e.type === "stdout" || e.type === "stderr") {
577
+ output += e.data;
578
+ const match = typeof pattern === "string" ? output.includes(pattern) : output.match(pattern);
579
+ if (match)
580
+ return {
581
+ line: output
582
+ .split("\n")
583
+ .find((line) => typeof pattern === "string"
584
+ ? line.includes(pattern)
585
+ : new RegExp(pattern.source, pattern.flags.replace("g", "")).test(line)) ?? output,
586
+ ...(typeof pattern === "string" ? {} : { match }),
587
+ };
588
+ }
589
+ }
590
+ throw new Error("Process exited before expected output");
591
+ }
592
+ }