withruntime 0.8.4 → 0.9.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 (91) hide show
  1. package/COMPATIBILITY.md +145 -0
  2. package/E2B.md +23 -13
  3. package/README.md +8 -5
  4. package/README.src.md +8 -5
  5. package/compatibility-lock.json +281 -0
  6. package/dist/ai-harness/index.js +2 -2
  7. package/dist/api-defaults.d.ts +17 -0
  8. package/dist/api-defaults.js +22 -0
  9. package/dist/blaxel/preview.d.ts +1 -1
  10. package/dist/blaxel/preview.js +1 -1
  11. package/dist/blaxel/process.d.ts +1 -1
  12. package/dist/blaxel/process.js +11 -2
  13. package/dist/cli-words.d.ts +27 -0
  14. package/dist/cli-words.js +135 -0
  15. package/dist/cli.js +162 -31
  16. package/dist/client.d.ts +4 -1
  17. package/dist/client.js +8 -2
  18. package/dist/cloudflare/index.d.ts +294 -0
  19. package/dist/cloudflare/index.js +554 -0
  20. package/dist/cloudflare/worker.d.ts +415 -0
  21. package/dist/cloudflare/worker.js +297 -0
  22. package/dist/codesandbox/guest-service.d.ts +31 -0
  23. package/dist/codesandbox/guest-service.js +453 -0
  24. package/dist/codesandbox/index.d.ts +386 -0
  25. package/dist/codesandbox/index.js +1313 -0
  26. package/dist/compat/core.d.ts +39 -0
  27. package/dist/compat/core.js +199 -0
  28. package/dist/compat/files.d.ts +26 -0
  29. package/dist/compat/files.js +115 -0
  30. package/dist/compat/shell.d.ts +4 -0
  31. package/dist/compat/shell.js +165 -0
  32. package/dist/daytona/context.js +2 -1
  33. package/dist/daytona/process.d.ts +1 -1
  34. package/dist/daytona/process.js +27 -4
  35. package/dist/e2b/code-interpreter.d.ts +4 -5
  36. package/dist/e2b/code-interpreter.js +54 -56
  37. package/dist/e2b/commands.d.ts +5 -4
  38. package/dist/e2b/commands.js +37 -29
  39. package/dist/e2b/errors.js +1 -1
  40. package/dist/e2b/filesystem.d.ts +8 -6
  41. package/dist/e2b/filesystem.js +127 -28
  42. package/dist/e2b/sandbox.d.ts +4 -3
  43. package/dist/e2b/sandbox.js +10 -10
  44. package/dist/errors.d.ts +8 -0
  45. package/dist/errors.js +15 -2
  46. package/dist/freestyle/index.d.ts +301 -0
  47. package/dist/freestyle/index.js +310 -0
  48. package/dist/index.d.ts +1 -0
  49. package/dist/mcp.js +2 -2
  50. package/dist/modal/index.d.ts +205 -0
  51. package/dist/modal/index.js +485 -0
  52. package/dist/network-cli.js +1 -1
  53. package/dist/openai-agents/index.js +9 -26
  54. package/dist/products/audit.d.ts +3 -0
  55. package/dist/products/index.d.ts +2 -0
  56. package/dist/products/index.js +2 -0
  57. package/dist/products/interpreter.d.ts +11 -4
  58. package/dist/products/interpreter.js +33 -9
  59. package/dist/products/jobs.js +3 -2
  60. package/dist/products/limits.d.ts +10 -0
  61. package/dist/products/previews.d.ts +10 -2
  62. package/dist/products/previews.js +3 -2
  63. package/dist/products/tailscale.d.ts +57 -0
  64. package/dist/products/tailscale.js +17 -0
  65. package/dist/products/watch.d.ts +25 -4
  66. package/dist/products/watch.js +75 -14
  67. package/dist/runloop/index.d.ts +340 -0
  68. package/dist/runloop/index.js +438 -0
  69. package/dist/runloop/pagination.d.ts +46 -0
  70. package/dist/runloop/pagination.js +82 -0
  71. package/dist/runloop/snapshots.d.ts +52 -0
  72. package/dist/runloop/snapshots.js +244 -0
  73. package/dist/sandbox.d.ts +40 -9
  74. package/dist/sandbox.js +273 -76
  75. package/dist/snapshots.d.ts +11 -1
  76. package/dist/snapshots.js +11 -1
  77. package/dist/sprites/index.d.ts +251 -0
  78. package/dist/sprites/index.js +552 -0
  79. package/dist/tar.d.ts +8 -1
  80. package/dist/tar.js +185 -35
  81. package/dist/tools/index.js +2 -1
  82. package/dist/transport.d.ts +20 -2
  83. package/dist/transport.js +48 -11
  84. package/dist/types.d.ts +52 -3
  85. package/dist/vercel/command.js +12 -1
  86. package/dist/vercel/errors.d.ts +2 -2
  87. package/dist/vercel/errors.js +12 -1
  88. package/dist/vercel/filesystem.d.ts +21 -18
  89. package/dist/vercel/filesystem.js +95 -48
  90. package/dist/vercel/sandbox.js +5 -5
  91. package/package.json +45 -3
@@ -0,0 +1,485 @@
1
+ import { all, client, CompatibilityError, create, destroy, execOptions, only, region, } from "../compat/core.js";
2
+ import { GuestFiles } from "../compat/files.js";
3
+ export { CompatibilityError };
4
+ export class NotFoundError extends Error {
5
+ }
6
+ export class TimeoutError extends Error {
7
+ }
8
+ export class SandboxTimeoutError extends TimeoutError {
9
+ }
10
+ export class App {
11
+ appId;
12
+ name;
13
+ environmentName;
14
+ constructor(appId, name, environmentName) {
15
+ this.appId = appId;
16
+ this.name = name;
17
+ this.environmentName = environmentName;
18
+ }
19
+ }
20
+ export class Secret {
21
+ values;
22
+ constructor(values) {
23
+ this.values = values;
24
+ }
25
+ }
26
+ export class ModalClient {
27
+ runtime;
28
+ apps;
29
+ images;
30
+ sandboxes;
31
+ secrets = { fromObject: (values) => new Secret(values) };
32
+ constructor(options = {}) {
33
+ only("Modal client", options, [
34
+ "apiKey",
35
+ "baseUrl",
36
+ "fetch",
37
+ "client",
38
+ "timeoutMs",
39
+ "maxRetries",
40
+ "tokenId",
41
+ "tokenSecret",
42
+ "environment",
43
+ ]);
44
+ this.runtime = client(options);
45
+ const runtime = this.runtime;
46
+ this.apps = {
47
+ fromName: async (name, params = {}) => {
48
+ only("Modal app", params, ["createIfMissing", "environment"]);
49
+ if (params.environment || options.environment)
50
+ throw new CompatibilityError("Modal", "named Modal environments");
51
+ if (!params.createIfMissing &&
52
+ !(await all(runtime, "modal")).some((s) => s.info.labels["modal.app"] === name))
53
+ throw new NotFoundError(`App ${name} has no Runtime sandboxes; pass createIfMissing: true.`);
54
+ return new App(name, name);
55
+ },
56
+ };
57
+ this.images = {
58
+ fromRegistry: (tag, secret) => {
59
+ if (secret)
60
+ throw new CompatibilityError("Modal", "registry Secret; configure Runtime image registry credentials");
61
+ return new Image(runtime, tag);
62
+ },
63
+ fromId: async (id) => new Image(runtime, undefined, (await runtime.images.get(id)).id),
64
+ fromName: async (name) => new Image(runtime, undefined, (await runtime.images.resolve(name)).id),
65
+ delete: (id) => runtime.images.delete(id),
66
+ };
67
+ this.sandboxes = new SandboxService(runtime);
68
+ }
69
+ close() {
70
+ /* Transport has no owned background connection to close. */
71
+ }
72
+ }
73
+ export class Image {
74
+ runtime;
75
+ tag;
76
+ commands;
77
+ env;
78
+ cache;
79
+ built;
80
+ constructor(runtime, tag, id, commands = [], env = {}, cache = true) {
81
+ this.runtime = runtime;
82
+ this.tag = tag;
83
+ this.commands = commands;
84
+ this.env = env;
85
+ this.cache = cache;
86
+ this.built = id;
87
+ }
88
+ get imageId() {
89
+ if (!this.built)
90
+ throw new Error("Image is not built; await image.build(app) first.");
91
+ return this.built;
92
+ }
93
+ dockerfileCommands(commands, params = {}) {
94
+ only("Modal image", params, ["env", "forceBuild"]);
95
+ if (!this.tag)
96
+ throw new CompatibilityError("Modal", "extending a prebuilt Runtime image through Dockerfile layers");
97
+ return new Image(this.runtime, this.tag, undefined, [...this.commands, ...commands], { ...this.env, ...params.env }, params.forceBuild ? false : this.cache);
98
+ }
99
+ async build(_app) {
100
+ if (!this.built) {
101
+ const built = this.commands.length
102
+ ? await this.runtime.images.build({
103
+ dockerfile: [`FROM ${this.tag}`, ...this.commands].join("\n"),
104
+ env: this.env,
105
+ cache: this.cache,
106
+ })
107
+ : await this.runtime.images.build({ image: this.tag, env: this.env, cache: this.cache });
108
+ this.built = built.id;
109
+ }
110
+ return this;
111
+ }
112
+ }
113
+ export class SandboxService {
114
+ runtime;
115
+ constructor(runtime) {
116
+ this.runtime = runtime;
117
+ }
118
+ async create(app, image, params = {}) {
119
+ only("Modal sandbox", params, [
120
+ "name",
121
+ "cpu",
122
+ "cpuLimit",
123
+ "memoryMiB",
124
+ "memoryLimitMiB",
125
+ "timeoutMs",
126
+ "workdir",
127
+ "command",
128
+ "env",
129
+ "encryptedPorts",
130
+ "blockNetwork",
131
+ "outboundDomainAllowlist",
132
+ "outboundCidrAllowlist",
133
+ "tags",
134
+ "regions",
135
+ "gpu",
136
+ "pty",
137
+ "secrets",
138
+ "experimentalEnableSnapshot",
139
+ ]);
140
+ if (params.gpu)
141
+ throw new CompatibilityError("Modal", "GPU allocation");
142
+ if (params.cpuLimit !== undefined && params.cpuLimit !== params.cpu)
143
+ throw new CompatibilityError("Modal", "separate CPU reservation and hard limit");
144
+ if (params.memoryLimitMiB !== undefined && params.memoryLimitMiB !== params.memoryMiB)
145
+ throw new CompatibilityError("Modal", "separate memory reservation and hard limit");
146
+ if (params.regions && params.regions.length !== 1)
147
+ throw new CompatibilityError("Modal", "multiple region placement");
148
+ if (params.cpu !== undefined && !Number.isInteger(params.cpu))
149
+ throw new CompatibilityError("Modal", "fractional CPU reservations");
150
+ if (params.blockNetwork && (params.outboundDomainAllowlist || params.outboundCidrAllowlist))
151
+ throw new TypeError("blockNetwork cannot be combined with allowlists");
152
+ const built = await image.build(app);
153
+ let primary;
154
+ const s = await create(this.runtime, "modal", {
155
+ name: params.name ? `${app.name ?? app.appId}-${params.name}` : undefined,
156
+ labels: {
157
+ "modal.app": app.name ?? app.appId,
158
+ ...(params.name ? { "modal.name": params.name } : {}),
159
+ "modal.tags": JSON.stringify(params.tags ?? {}),
160
+ },
161
+ image: built.imageId,
162
+ cpu: "reserved",
163
+ vcpu: params.cpu,
164
+ memoryMiB: params.memoryMiB,
165
+ region: region("Modal", params.regions?.[0]),
166
+ timeoutSeconds: Math.ceil((params.timeoutMs ?? 300_000) / 1000),
167
+ idlePauseSeconds: 0,
168
+ onLeaseEnd: "stop",
169
+ network: {
170
+ internet: !params.blockNetwork,
171
+ allow: [
172
+ ...(params.outboundDomainAllowlist ?? []),
173
+ ...(params.outboundCidrAllowlist ?? []),
174
+ ],
175
+ },
176
+ }, Object.assign({}, ...(params.secrets?.map((s) => s.values) ?? []), params.env ?? {}), async (s) => {
177
+ primary = await s.spawn(params.command ?? ["sleep", "infinity"], {
178
+ ...(await execOptions(s, { cwd: params.workdir })),
179
+ stdin: "pipe",
180
+ pty: params.pty ? {} : undefined,
181
+ });
182
+ await s.update({ labels: { ...s.info.labels, "modal.entrypoint": primary.id } });
183
+ for (const port of params.encryptedPorts ?? [])
184
+ await s.previews.create(port, { visibility: "public" });
185
+ });
186
+ const sandbox = new Sandbox(s, new ContainerProcess(primary, "text"));
187
+ sandbox.monitorEntrypoint();
188
+ return sandbox;
189
+ }
190
+ async fromId(id) {
191
+ const s = await this.runtime.sandboxes.get(id);
192
+ const p = s.info.labels["modal.entrypoint"];
193
+ if (!p)
194
+ throw new NotFoundError("Sandbox is not a Modal-compatible sandbox");
195
+ return new Sandbox(s, new ContainerProcess(await s.processes.get(p), "text"));
196
+ }
197
+ async fromName(appName, name) {
198
+ const s = (await all(this.runtime, "modal")).find((s) => s.info.labels["modal.app"] === appName &&
199
+ s.info.labels["modal.name"] === name &&
200
+ s.state === "running");
201
+ if (!s)
202
+ throw new NotFoundError(`Sandbox ${name} not found`);
203
+ return this.fromId(s.id);
204
+ }
205
+ async *list(params = {}) {
206
+ only("Modal list", params, ["appId", "tags"]);
207
+ for (const s of await all(this.runtime, "modal"))
208
+ if (s.state === "running" &&
209
+ (!params.appId || s.info.labels["modal.app"] === params.appId) &&
210
+ Object.entries(params.tags ?? {}).every(([k, v]) => JSON.parse(s.info.labels["modal.tags"] ?? "{}")[k] === v))
211
+ yield await this.fromId(s.id);
212
+ }
213
+ }
214
+ export class ModalReadStream extends ReadableStream {
215
+ constructor(process, channel, mode, ignore = false) {
216
+ const iterator = mode === "binary" ? process.outputBytes() : process.output();
217
+ super({
218
+ async pull(controller) {
219
+ if (ignore) {
220
+ controller.close();
221
+ return;
222
+ }
223
+ try {
224
+ while (true) {
225
+ const next = await iterator.next();
226
+ if (next.done) {
227
+ controller.close();
228
+ return;
229
+ }
230
+ if (next.value.type === "truncated")
231
+ throw new Error("Process output was truncated");
232
+ if (next.value.type === channel) {
233
+ controller.enqueue(next.value.data);
234
+ return;
235
+ }
236
+ }
237
+ }
238
+ catch (error) {
239
+ controller.error(error);
240
+ }
241
+ },
242
+ async cancel() {
243
+ await iterator.return(undefined);
244
+ },
245
+ });
246
+ }
247
+ async readBytes() {
248
+ const reader = this.getReader();
249
+ const chunks = [];
250
+ let size = 0;
251
+ try {
252
+ while (true) {
253
+ const next = await reader.read();
254
+ if (next.done)
255
+ break;
256
+ const bytes = typeof next.value === "string" ? new TextEncoder().encode(next.value) : next.value;
257
+ chunks.push(bytes);
258
+ size += bytes.length;
259
+ }
260
+ }
261
+ finally {
262
+ reader.releaseLock();
263
+ }
264
+ const output = new Uint8Array(size);
265
+ let offset = 0;
266
+ for (const chunk of chunks) {
267
+ output.set(chunk, offset);
268
+ offset += chunk.length;
269
+ }
270
+ return output;
271
+ }
272
+ async readText() {
273
+ return new TextDecoder().decode(await this.readBytes());
274
+ }
275
+ }
276
+ export class ModalWriteStream extends WritableStream {
277
+ process;
278
+ constructor(process) {
279
+ super({ write: (data) => process.write(data), close: () => process.write("", { eof: true }) });
280
+ this.process = process;
281
+ }
282
+ async writeText(text) {
283
+ const writer = this.getWriter();
284
+ try {
285
+ await writer.write(text);
286
+ }
287
+ finally {
288
+ writer.releaseLock();
289
+ }
290
+ }
291
+ async writeBytes(bytes) {
292
+ const writer = this.getWriter();
293
+ try {
294
+ await writer.write(bytes);
295
+ }
296
+ finally {
297
+ writer.releaseLock();
298
+ }
299
+ }
300
+ }
301
+ export class ContainerProcess {
302
+ native;
303
+ stdin;
304
+ stdout;
305
+ stderr;
306
+ constructor(native, mode, params = {}) {
307
+ this.native = native;
308
+ this.stdin = new ModalWriteStream(native);
309
+ this.stdout = new ModalReadStream(native, "stdout", mode, params.stdout === "ignore");
310
+ this.stderr = new ModalReadStream(native, "stderr", mode, params.stderr === "ignore");
311
+ }
312
+ async wait() {
313
+ const r = await this.native.wait();
314
+ if (r.timedOut)
315
+ throw new SandboxTimeoutError("Process timed out");
316
+ return r.exitCode ?? -1;
317
+ }
318
+ closeStdin() {
319
+ return this.native.write("", { eof: true });
320
+ }
321
+ async _poll() {
322
+ const p = await this.native.refresh();
323
+ return p.state === "running" ? null : p.exitCode;
324
+ }
325
+ }
326
+ export class Sandbox {
327
+ native;
328
+ primary;
329
+ filesystem;
330
+ stdin;
331
+ stdout;
332
+ stderr;
333
+ detached = false;
334
+ lifecycle;
335
+ monitorEntrypoint() {
336
+ this.lifecycle = this.primary.wait().then(async (code) => {
337
+ await destroy(this.native);
338
+ return code;
339
+ });
340
+ this.lifecycle.catch(() => undefined);
341
+ }
342
+ constructor(native, primary) {
343
+ this.native = native;
344
+ this.primary = primary;
345
+ this.filesystem = new SandboxFilesystem(native);
346
+ this.stdin = primary.stdin;
347
+ this.stdout = primary.stdout;
348
+ this.stderr = primary.stderr;
349
+ }
350
+ get sandboxId() {
351
+ return this.native.id;
352
+ }
353
+ async exec(command, params = {}) {
354
+ if (this.detached)
355
+ throw new Error("Sandbox is detached");
356
+ only("Modal exec", params, [
357
+ "mode",
358
+ "stdout",
359
+ "stderr",
360
+ "workdir",
361
+ "timeoutMs",
362
+ "env",
363
+ "pty",
364
+ "secrets",
365
+ ]);
366
+ const p = await this.native.spawn(command, {
367
+ ...(await execOptions(this.native, {
368
+ cwd: params.workdir,
369
+ timeoutMs: params.timeoutMs === 0 ? 86_400_000 : params.timeoutMs,
370
+ env: Object.assign({}, ...(params.secrets?.map((s) => s.values) ?? []), params.env ?? {}),
371
+ })),
372
+ stdin: "pipe",
373
+ ...(params.mode === "binary" ? { outputEncoding: "base64" } : {}),
374
+ pty: params.pty ? {} : undefined,
375
+ });
376
+ return new ContainerProcess(p, params.mode ?? "text", params);
377
+ }
378
+ async setTags(tags) {
379
+ if (this.detached)
380
+ throw new Error("Sandbox is detached");
381
+ await this.native.update({
382
+ labels: {
383
+ ...Object.fromEntries(Object.entries(this.native.info.labels).filter(([k]) => k.startsWith("modal.") || k === "compat.provider")),
384
+ "modal.tags": JSON.stringify(tags),
385
+ },
386
+ });
387
+ }
388
+ async getTags() {
389
+ if (this.detached)
390
+ throw new Error("Sandbox is detached");
391
+ await this.native.refresh();
392
+ return JSON.parse(this.native.info.labels["modal.tags"] ?? "{}");
393
+ }
394
+ async terminate(params = {}) {
395
+ await destroy(this.native);
396
+ return params.wait ? this.primary.wait() : undefined;
397
+ }
398
+ detach() {
399
+ this.detached = true;
400
+ }
401
+ async wait() {
402
+ if (this.lifecycle)
403
+ return this.lifecycle;
404
+ const code = await this.primary.wait();
405
+ await destroy(this.native);
406
+ return code;
407
+ }
408
+ poll() {
409
+ return this.primary._poll();
410
+ }
411
+ async tunnels() {
412
+ return Object.fromEntries((await this.native.previews.list()).map((p) => [
413
+ p.port,
414
+ { host: new URL(p.url).hostname, port: 443, url: p.urlWithToken ?? p.url },
415
+ ]));
416
+ }
417
+ async updateNetworkPolicy(params) {
418
+ await this.native.network.set({
419
+ internet: true,
420
+ allow: [...params.outboundCidrAllowlist, ...params.outboundDomainAllowlist],
421
+ });
422
+ }
423
+ async experimentalSnapshot() {
424
+ const snapshot = await this.native.snapshot();
425
+ return { sandboxSnapshotId: snapshot.id };
426
+ }
427
+ }
428
+ export class SandboxFilesystem {
429
+ native;
430
+ files;
431
+ constructor(native) {
432
+ this.native = native;
433
+ this.files = new GuestFiles(async () => native, "/");
434
+ }
435
+ readBytes(path) {
436
+ return this.files.readFile(path);
437
+ }
438
+ readText(path) {
439
+ return this.files.readTextFile(path);
440
+ }
441
+ writeBytes(content, path) {
442
+ return this.files.writeFile(path, content instanceof Uint8Array ? content : new Uint8Array(content));
443
+ }
444
+ writeText(content, path) {
445
+ return this.files.writeTextFile(path, content);
446
+ }
447
+ makeDirectory(path, options = {}) {
448
+ return this.files.mkdir(path, options.createParents ?? true);
449
+ }
450
+ remove(path, options = {}) {
451
+ return this.files.remove(path, options.recursive);
452
+ }
453
+ copyFromLocal(localPath, remotePath) {
454
+ return this.native.files.upload(localPath, remotePath);
455
+ }
456
+ copyToLocal(remotePath, localPath) {
457
+ return this.native.files.download(remotePath, localPath);
458
+ }
459
+ async listFiles(path) {
460
+ return Promise.all((await this.files.readdir(path))
461
+ .sort((a, b) => a.name.localeCompare(b.name))
462
+ .map((e) => this.stat(e.path)));
463
+ }
464
+ async stat(path) {
465
+ const e = await this.files.stat(path);
466
+ const r = await this.native.exec(["stat", "-c", "%U:%G", "--", this.files.path(path)], {
467
+ check: true,
468
+ });
469
+ const [owner, group] = r.stdout.trim().split(":");
470
+ return {
471
+ name: e.name,
472
+ path: e.path,
473
+ type: e.type,
474
+ size: e.size,
475
+ mode: parseInt(e.mode, 8),
476
+ permissions: e.mode,
477
+ owner: owner,
478
+ group: group,
479
+ modifiedTime: Date.parse(e.modifiedAt) / 1000,
480
+ symlinkTarget: e.type === "symlink"
481
+ ? (await this.native.exec(["readlink", "--", this.files.path(path)], { check: true })).stdout.trimEnd()
482
+ : null,
483
+ };
484
+ }
485
+ }
@@ -30,7 +30,7 @@ export const NETWORK_HELP = {
30
30
  Redis, a game server, anything that is not HTTP
31
31
  Prints address:port for your client
32
32
  ls [--sandbox <id>] Your open ports
33
- close <portId> Close one; open connections end within seconds
33
+ close <portId> Close one, its open connections too
34
34
  Paid accounts only. TCP, carried as it is: use your service's own password and TLS.
35
35
  `,
36
36
  address: `runtime address <command>
@@ -18,6 +18,7 @@
18
18
  * RUNTIME_API_KEY or this machine's `runtime login`. Plain function tools for
19
19
  * an ordinary `Agent` are `runtimeFunctionTools(sandbox)`. */
20
20
  import { applyDiff, tool, UserError, } from "@openai/agents-core";
21
+ import { OPENAI_AGENTS_EXEC_TIMEOUT_MS } from "../api-defaults.js";
21
22
  import { SandboxProviderError, SandboxUnsupportedFeatureError, normalizeSandboxClientCreateArgs, } from "@openai/agents-core/sandbox";
22
23
  import { deserializeManifest, elapsedSeconds, formatExecResponse, imageOutputFromBytes, materializeEnvironment, mergeManifestDelta, mergeManifestEntryDelta, serializeManifestRecord, shellQuote, truncateOutput, } from "@openai/agents-core/sandbox/internal";
23
24
  import { Runtime } from "../client.js";
@@ -477,31 +478,11 @@ export class RuntimeCloudSandboxSession {
477
478
  await this.#writeBytes(path, await readFile(entry.src));
478
479
  return;
479
480
  }
480
- case "local_dir": {
481
- // One gzipped tar, staged inside /workspace where uploads of any size are accepted.
482
- const { packDirectory } = await import("../tar.js");
483
- const staging = `${STAGING}/${crypto.randomUUID()}.tar.gz`;
484
- const archive = await packDirectory(entry.src);
485
- try {
486
- await this.#call((sbx) => sbx.files.write(staging, archive));
487
- const unpacked = await this.#call((sbx) => sbx.exec([
488
- "sh",
489
- "-c",
490
- 'mkdir -p -- "$1" && tar -xzf "$2" -C "$1"',
491
- "sh",
492
- path,
493
- staging,
494
- ]));
495
- if (unpacked.exitCode !== 0)
496
- throw new SandboxProviderError(`${PROVIDER} could not place ${entry.src}.`, {
497
- stderr: unpacked.stderr,
498
- });
499
- }
500
- finally {
501
- await this.#call((sbx) => sbx.files.remove(staging)).catch(() => undefined);
502
- }
481
+ case "local_dir":
482
+ // One archive through the API's folder routes, unpacked by the
483
+ // sandbox's own tar as it arrives.
484
+ await this.#call((sbx) => sbx.files.upload(entry.src, path));
503
485
  return;
504
- }
505
486
  case "git_repo": {
506
487
  const url = /^[a-z]+:\/\//.test(entry.repo)
507
488
  ? entry.repo
@@ -674,7 +655,7 @@ export class RuntimeCloudSandboxClient {
674
655
  ...(options.exposedPorts ? { configuredExposedPorts: options.exposedPorts } : {}),
675
656
  previewVisibility: options.previewVisibility ?? "private",
676
657
  pauseOnExit: options.pauseOnExit ?? false,
677
- execTimeoutMs: Math.min(MAX_EXEC_MS, options.execTimeoutMs ?? 3_600_000),
658
+ execTimeoutMs: Math.min(MAX_EXEC_MS, options.execTimeoutMs ?? OPENAI_AGENTS_EXEC_TIMEOUT_MS),
678
659
  };
679
660
  const session = new RuntimeCloudSandboxSession({ state, runtime: this.runtime });
680
661
  await session.sandbox();
@@ -764,7 +745,9 @@ export class RuntimeCloudSandboxClient {
764
745
  : {}),
765
746
  previewVisibility: record.previewVisibility === "public" ? "public" : "private",
766
747
  pauseOnExit: record.pauseOnExit === true,
767
- execTimeoutMs: typeof record.execTimeoutMs === "number" ? record.execTimeoutMs : 3_600_000,
748
+ execTimeoutMs: typeof record.execTimeoutMs === "number"
749
+ ? record.execTimeoutMs
750
+ : OPENAI_AGENTS_EXEC_TIMEOUT_MS,
768
751
  };
769
752
  }
770
753
  }
@@ -26,6 +26,9 @@ export type AuditEvent = {
26
26
  ip: string | null;
27
27
  requestId: string | null;
28
28
  via: "web" | "api" | "mcp" | "runtime";
29
+ /** The sandbox the request came from, when code in it called the API at
30
+ * http://runtime.internal. */
31
+ sandbox: string | null;
29
32
  };
30
33
  export type AuditPage = {
31
34
  events: AuditEvent[];
@@ -15,6 +15,7 @@ import { limits } from "./limits.js";
15
15
  import { volumes } from "./volumes.js";
16
16
  import { jobs } from "./jobs.js";
17
17
  import { sandboxMounts } from "./mounts.js";
18
+ import { sandboxTailscale } from "./tailscale.js";
18
19
  import { secrets } from "./secrets.js";
19
20
  import { mcp, sandboxMcp } from "./mcp.js";
20
21
  import { events, otel, sandboxMetrics, webhooks } from "./observability.js";
@@ -45,6 +46,7 @@ export declare const sandboxExtensions: {
45
46
  network: typeof sandboxNetwork;
46
47
  desktop: typeof sandboxDesktop;
47
48
  mounts: typeof sandboxMounts;
49
+ tailscale: typeof sandboxTailscale;
48
50
  metrics: typeof sandboxMetrics;
49
51
  mcp: typeof sandboxMcp;
50
52
  };
@@ -13,6 +13,7 @@ import { limits } from "./limits.js";
13
13
  import { volumes } from "./volumes.js";
14
14
  import { jobs } from "./jobs.js";
15
15
  import { sandboxMounts } from "./mounts.js";
16
+ import { sandboxTailscale } from "./tailscale.js";
16
17
  import { secrets } from "./secrets.js";
17
18
  import { mcp, sandboxMcp } from "./mcp.js";
18
19
  import { events, otel, sandboxMetrics, webhooks } from "./observability.js";
@@ -58,6 +59,7 @@ export const sandboxExtensions = {
58
59
  network: sandboxNetwork,
59
60
  desktop: sandboxDesktop,
60
61
  mounts: sandboxMounts,
62
+ tailscale: sandboxTailscale,
61
63
  metrics: sandboxMetrics,
62
64
  mcp: sandboxMcp,
63
65
  };
@@ -50,15 +50,22 @@ export type RunOptions = RequestOptions & {
50
50
  /** A context id; each language's own name ("python", "typescript", "r"...) is
51
51
  * its default context, started on first use. */
52
52
  context?: string;
53
+ /** Cell deadline. 0 runs until completion, cancellation or the sandbox's lease ends. */
53
54
  timeoutMs?: number;
54
- onStdout?: (text: string) => void;
55
- onStderr?: (text: string) => void;
56
- onResult?: (result: ExecutionResult) => void;
55
+ /** Separate HTTP deadline, including retries. 0 disables it. */
56
+ requestTimeoutMs?: number;
57
+ /** false lets the cell continue if this reader disconnects. Default true. */
58
+ interruptOnDisconnect?: boolean;
59
+ /** @internal Compatibility protocols with separate header/body deadlines. */
60
+ onResponse?: (response: Response) => void;
61
+ onStdout?: (text: string) => unknown;
62
+ onStderr?: (text: string) => unknown;
63
+ onResult?: (result: ExecutionResult) => unknown;
57
64
  onError?: (error: {
58
65
  name: string;
59
66
  value: string;
60
67
  traceback: string;
61
- }) => void;
68
+ }) => unknown;
62
69
  };
63
70
  /** A stateful interpreter in the sandbox, in Python, JavaScript, TypeScript,
64
71
  * R, Java, Bash or Go: