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
package/dist/tar.js CHANGED
@@ -1,6 +1,8 @@
1
- import { chmod, lstat, mkdir, readdir, readFile, realpath, symlink, unlink, writeFile, } from "node:fs/promises";
1
+ import { chmod, lstat, mkdir, mkdtemp, open, readdir, readFile, readlink, realpath, rename, rm, symlink, unlink, } from "node:fs/promises";
2
2
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
3
- import { gunzipSync, gzipSync } from "node:zlib";
3
+ import { Readable } from "node:stream";
4
+ import { createGunzip, gzipSync } from "node:zlib";
5
+ import { RuntimeError } from "./errors.js";
4
6
  /* A small ustar writer and reader for directory uploads and downloads, so the
5
7
  SDK needs no dependency. Regular files, directories and symbolic links;
6
8
  names up to 255 bytes through the ustar prefix field. */
@@ -111,34 +113,224 @@ async function linkStaysInside(root, directory, link) {
111
113
  }
112
114
  return true;
113
115
  }
114
- /** Unpacks into `target`, refusing any entry that would land outside it. */
115
- export async function unpackArchive(archive, target) {
116
- const data = gunzipSync(archive);
117
- await mkdir(resolve(target), { recursive: true });
118
- const root = await realpath(resolve(target));
119
- const text = (start, length) => {
120
- const slice = data.subarray(start, start + length);
116
+ /** A folder that did not arrive whole: tar in the sandbox stopped part way
117
+ * (a file it may not read, one that changed as it was read), which ends the
118
+ * gzip stream short, or the connection was lost (ARCHITECTURE.md section 10,
119
+ * "Folders over HTTP"). */
120
+ function cutShort(cause) {
121
+ return new RuntimeError({
122
+ message: "The folder's archive arrived cut short: tar in the sandbox stopped part way, or the connection was lost. Nothing was written.",
123
+ hint: "Try again. If it fails the same way, a file in the folder cannot be read by the sandbox user or changes as it is read.",
124
+ code: "download_incomplete",
125
+ status: 0,
126
+ ...(cause === undefined ? {} : { cause }),
127
+ });
128
+ }
129
+ function checkSignal(signal) {
130
+ if (signal?.aborted)
131
+ throw new RuntimeError({
132
+ message: "The download was cancelled or ran past its deadline.",
133
+ code: "timeout",
134
+ status: 0,
135
+ cause: signal.reason,
136
+ });
137
+ }
138
+ /** Linux path names fit well below this generous metadata-only memory bound. */
139
+ const MAX_LONG_NAME_BYTES = 65_536;
140
+ function archiveSize(field) {
141
+ const text = field.trim();
142
+ if (text !== "" && !/^[0-7]+$/.test(text))
143
+ throw new Error("The archive contains an invalid file size.");
144
+ const size = text === "" ? 0 : parseInt(text, 8);
145
+ if (!Number.isSafeInteger(size) || size < 0 || size > Number.MAX_SAFE_INTEGER - 511)
146
+ throw new Error("The archive contains an invalid file size.");
147
+ return size;
148
+ }
149
+ /** The gzip check protects the compressed bytes; tar's own checksum also
150
+ * protects the header that determines each file's name, kind and length. */
151
+ function checkHeader(header, field) {
152
+ const text = field.trim();
153
+ let unsigned = 0;
154
+ let signed = 0;
155
+ for (let index = 0; index < header.length; index++) {
156
+ const byte = index >= 148 && index < 156 ? 32 : header[index];
157
+ unsigned += byte;
158
+ signed += byte < 128 ? byte : byte - 256;
159
+ }
160
+ const expected = /^[0-7]+$/.test(text) ? parseInt(text, 8) : NaN;
161
+ // Older tar implementations used signed bytes for non-ASCII path names.
162
+ if (expected !== unsigned && expected !== signed)
163
+ throw new Error("The archive contains an invalid header checksum.");
164
+ }
165
+ /** Validate links against the tree a merge will produce. Staged entries
166
+ * take precedence; paths absent there retain the existing target's entry.
167
+ * A link safe in staging may otherwise lead through an old escaping link. */
168
+ async function mergedLinkStaysInside(staging, root, destination, link) {
169
+ let path = dirname(join(root, relative(staging, destination)));
170
+ const pending = link.split("/");
171
+ const followed = new Set();
172
+ while (pending.length) {
173
+ const part = pending.shift();
174
+ if (!part || part === ".")
175
+ continue;
176
+ path = part === ".." ? dirname(path) : join(path, part);
177
+ if (path !== root && !path.startsWith(root + sep))
178
+ return false;
179
+ const staged = join(staging, relative(root, path));
180
+ const stagedKind = await lstat(staged).catch((error) => {
181
+ if (error.code === "ENOENT" || error.code === "ENOTDIR")
182
+ return undefined;
183
+ throw error;
184
+ });
185
+ const actual = stagedKind ? staged : path;
186
+ const kind = stagedKind ??
187
+ (await lstat(actual).catch((error) => {
188
+ if (error.code === "ENOENT" || error.code === "ENOTDIR")
189
+ return undefined;
190
+ throw error;
191
+ }));
192
+ if (!kind?.isSymbolicLink())
193
+ continue;
194
+ if (followed.has(path))
195
+ return false;
196
+ followed.add(path);
197
+ const next = await readlink(actual);
198
+ if (isAbsolute(next)) {
199
+ // An existing absolute link is safe only when it stays in this root.
200
+ if (next !== root && !next.startsWith(root + sep))
201
+ return false;
202
+ path = root;
203
+ pending.unshift(...relative(root, next).split(sep));
204
+ }
205
+ else {
206
+ path = dirname(path);
207
+ pending.unshift(...next.split("/"));
208
+ }
209
+ }
210
+ return true;
211
+ }
212
+ /** The gzip stream's bytes, decompressed as they arrive. */
213
+ function gunzipped(source, signal) {
214
+ const input = Readable.from(source, { objectMode: false });
215
+ const output = createGunzip();
216
+ // A filesystem await may precede async iteration; retain early stream
217
+ // errors for that iterator without an unhandled EventEmitter error.
218
+ output.on("error", () => undefined);
219
+ input.on("error", (error) => output.destroy(error));
220
+ input.pipe(output);
221
+ const abort = () => {
222
+ try {
223
+ checkSignal(signal);
224
+ }
225
+ catch (error) {
226
+ output.destroy(error);
227
+ }
228
+ };
229
+ signal?.addEventListener("abort", abort, { once: true });
230
+ output.on("close", () => {
231
+ signal?.removeEventListener("abort", abort);
232
+ input.destroy();
233
+ });
234
+ return output;
235
+ }
236
+ /** Exact byte counts from a stream of chunks, holding at most one chunk. */
237
+ class Bytes {
238
+ #chunks;
239
+ #held = new Uint8Array(0);
240
+ constructor(source) {
241
+ this.#chunks = source[Symbol.asyncIterator]();
242
+ }
243
+ async #more() {
244
+ let next;
245
+ try {
246
+ next = await this.#chunks.next();
247
+ }
248
+ catch (error) {
249
+ throw error instanceof RuntimeError ? error : cutShort(error);
250
+ }
251
+ if (next.done)
252
+ return false;
253
+ this.#held = this.#held.length ? Buffer.concat([this.#held, next.value]) : next.value;
254
+ return true;
255
+ }
256
+ /** `n` bytes, or undefined at a clean end before any of them. */
257
+ async exactly(n) {
258
+ while (this.#held.length < n) {
259
+ if (await this.#more())
260
+ continue;
261
+ if (this.#held.length === 0)
262
+ return undefined;
263
+ throw cutShort();
264
+ }
265
+ const out = this.#held.subarray(0, n);
266
+ this.#held = this.#held.subarray(n);
267
+ return out;
268
+ }
269
+ /** The next `n` bytes as they arrive. */
270
+ async *take(n) {
271
+ while (n > 0) {
272
+ if (this.#held.length === 0 && !(await this.#more()))
273
+ throw cutShort();
274
+ const out = this.#held.subarray(0, n);
275
+ this.#held = this.#held.subarray(out.length);
276
+ n -= out.length;
277
+ yield out;
278
+ }
279
+ }
280
+ /** Reads to the end, so the gzip stream's own check has run. */
281
+ async drain() {
282
+ this.#held = new Uint8Array(0);
283
+ while (await this.#more())
284
+ this.#held = new Uint8Array(0);
285
+ }
286
+ }
287
+ /** Unpacks a tar stream into `root`, a fresh folder: files and folders as they
288
+ * arrive, links noted to make at the end. Throws unless the archive's end
289
+ * arrived. */
290
+ async function unpackEntries(bytes, root, signal) {
291
+ const links = [];
292
+ const decode = (slice) => {
121
293
  const end = slice.indexOf(0);
122
294
  return new TextDecoder().decode(end < 0 ? slice : slice.subarray(0, end));
123
295
  };
124
- const links = [];
125
296
  let longName;
126
297
  let longLink;
127
- for (let offset = 0; offset + 512 <= data.length;) {
128
- if (data.subarray(offset, offset + 512).every((byte) => byte === 0))
129
- break;
130
- const size = parseInt(text(offset + 124, 12).trim() || "0", 8);
131
- const type = String.fromCharCode(data[offset + 156] ?? 48);
132
- const prefix = text(offset + 345, 155);
133
- let name = longName ?? (prefix ? `${prefix}/${text(offset, 100)}` : text(offset, 100));
134
- const link = longLink ?? text(offset + 157, 100);
298
+ for (;;) {
299
+ checkSignal(signal);
300
+ const header = await bytes.exactly(512);
301
+ if (!header)
302
+ throw cutShort();
303
+ if (header.every((byte) => byte === 0)) {
304
+ const terminator = await bytes.exactly(512);
305
+ if (!terminator || !terminator.every((byte) => byte === 0))
306
+ throw cutShort();
307
+ await bytes.drain();
308
+ return links;
309
+ }
310
+ const text = (start, length) => decode(header.subarray(start, start + length));
311
+ const size = archiveSize(text(124, 12));
312
+ checkHeader(header, text(148, 8));
313
+ const type = String.fromCharCode(header[156] ?? 48);
314
+ const prefix = text(345, 155);
315
+ let name = longName ?? (prefix ? `${prefix}/${text(0, 100)}` : text(0, 100));
316
+ const link = longLink ?? text(157, 100);
135
317
  longName = undefined;
136
318
  longLink = undefined;
137
- const mode = parseInt(text(offset + 100, 8).trim() || "644", 8);
138
- const body = data.subarray(offset + 512, offset + 512 + size);
139
- offset += 512 + Math.ceil(size / 512) * 512;
319
+ const mode = parseInt(text(100, 8).trim() || "644", 8);
320
+ const padding = Math.ceil(size / 512) * 512 - size;
321
+ const skip = async () => {
322
+ for await (const _ of bytes.take(size + padding))
323
+ ;
324
+ };
140
325
  if (type === "L" || type === "K") {
141
- const value = new TextDecoder().decode(body).replace(/\0.*$/s, "");
326
+ if (size > MAX_LONG_NAME_BYTES)
327
+ throw new Error("The archive contains oversized path metadata.");
328
+ const parts = [];
329
+ for await (const part of bytes.take(size))
330
+ parts.push(Uint8Array.from(part));
331
+ for await (const _ of bytes.take(padding))
332
+ ;
333
+ const value = new TextDecoder().decode(Buffer.concat(parts)).replace(/\0.*$/s, "");
142
334
  if (type === "L")
143
335
  longName = value;
144
336
  else
@@ -146,29 +338,113 @@ export async function unpackArchive(archive, target) {
146
338
  continue;
147
339
  }
148
340
  name = name.replace(/^\.\//, "");
149
- if (!name || name === "." || name === "./")
341
+ if (!name || name === "." || name === "./") {
342
+ await skip();
150
343
  continue;
344
+ }
151
345
  const destination = resolve(root, name);
152
346
  await assertPlain(root, destination, name);
153
- if (type === "5")
347
+ if (type === "5") {
154
348
  await mkdir(destination, { recursive: true });
155
- else if (type === "2")
349
+ await skip();
350
+ }
351
+ else if (type === "2") {
156
352
  links.push({ destination, link, name });
353
+ await skip();
354
+ }
157
355
  else if (type === "0" || type === "\0" || type === "7") {
158
356
  await mkdir(dirname(destination), { recursive: true });
159
- await writeFile(destination, body);
357
+ const file = await open(destination, "w");
358
+ try {
359
+ for await (const part of bytes.take(size)) {
360
+ let written = 0;
361
+ while (written < part.length) {
362
+ checkSignal(signal);
363
+ const result = await file.write(part.subarray(written));
364
+ if (result.bytesWritten === 0)
365
+ throw new Error("The local file accepted no bytes.");
366
+ written += result.bytesWritten;
367
+ }
368
+ }
369
+ }
370
+ finally {
371
+ await file.close();
372
+ }
160
373
  await chmod(destination, mode & 0o777);
374
+ for await (const _ of bytes.take(padding))
375
+ ;
161
376
  }
377
+ else
378
+ await skip();
162
379
  }
163
- for (const { destination, link, name } of links) {
164
- await assertPlain(root, destination, name);
165
- await mkdir(dirname(destination), { recursive: true });
166
- await symlink(link, destination).catch(() => undefined);
380
+ }
381
+ /** Moves what was unpacked in `from` into `to`, merging with what is there
382
+ * and replacing files of the same name, never through a link in `to`. */
383
+ async function merge(from, to, root, signal) {
384
+ for (const name of await readdir(from)) {
385
+ checkSignal(signal);
386
+ const source = join(from, name);
387
+ const destination = join(to, name);
388
+ await assertPlain(root, destination, relative(root, destination));
389
+ const [kind, there] = await Promise.all([
390
+ lstat(source),
391
+ lstat(destination).catch(() => undefined),
392
+ ]);
393
+ checkSignal(signal);
394
+ if (kind.isDirectory() && there?.isDirectory())
395
+ await merge(source, destination, root, signal);
396
+ else
397
+ await rename(source, destination);
167
398
  }
168
- for (const { destination, link, name } of links) {
169
- if (await linkStaysInside(root, dirname(destination), link))
170
- continue;
171
- await unlink(destination).catch(() => undefined);
172
- throw new Error(`Refusing an archive link that leads outside the target: ${name} -> ${link}`);
399
+ }
400
+ /** Unpacks a gzipped tar that arrives as a stream into `target`, refusing any
401
+ * entry that would land outside it, holding no more than a chunk at a time.
402
+ * It unpacks into a folder beside the target and moves it into place only
403
+ * once the whole archive arrived, so an archive cut short leaves nothing
404
+ * behind that could pass for the folder. A target that exists is merged
405
+ * into, files of the same name replaced. */
406
+ export async function unpackStream(source, target, options = {}) {
407
+ checkSignal(options.signal);
408
+ const final = resolve(target);
409
+ await mkdir(dirname(final), { recursive: true });
410
+ const staging = await mkdtemp(`${final}.runtime-partial-`);
411
+ const stream = gunzipped(source, options.signal);
412
+ try {
413
+ const root = await realpath(staging);
414
+ const links = await unpackEntries(new Bytes(stream), root, options.signal);
415
+ for (const { destination, link, name } of links) {
416
+ checkSignal(options.signal);
417
+ await assertPlain(root, destination, name);
418
+ await mkdir(dirname(destination), { recursive: true });
419
+ await symlink(link, destination).catch(() => undefined);
420
+ }
421
+ for (const { destination, link, name } of links) {
422
+ checkSignal(options.signal);
423
+ if (await linkStaysInside(root, dirname(destination), link))
424
+ continue;
425
+ await unlink(destination).catch(() => undefined);
426
+ throw new Error(`Refusing an archive link that leads outside the target: ${name} -> ${link}`);
427
+ }
428
+ const there = await lstat(final).catch(() => undefined);
429
+ checkSignal(options.signal);
430
+ if (!there)
431
+ await rename(staging, final);
432
+ else {
433
+ const targetRoot = await realpath(final);
434
+ for (const { destination, link, name } of links) {
435
+ checkSignal(options.signal);
436
+ if (!(await mergedLinkStaysInside(root, targetRoot, destination, link)))
437
+ throw new Error(`Refusing an archive link that leads outside the target: ${name} -> ${link}`);
438
+ }
439
+ await merge(root, targetRoot, targetRoot, options.signal);
440
+ }
441
+ }
442
+ finally {
443
+ stream.destroy();
444
+ await rm(staging, { recursive: true, force: true });
173
445
  }
174
446
  }
447
+ /** Unpacks a whole gzipped tar held in memory, by the same rules. */
448
+ export function unpackArchive(archive, target) {
449
+ return unpackStream([archive], target);
450
+ }
@@ -10,6 +10,7 @@
10
10
  * chooses commands and paths, never the sandbox, the account or the key.
11
11
  * Relative paths are under /workspace, and output is capped so one noisy
12
12
  * command cannot flood the model's context. */
13
+ import { TOOL_EXEC_TIMEOUT_SECONDS } from "../api-defaults.js";
13
14
  import { NotFoundError } from "../errors.js";
14
15
  const WORKSPACE = "/workspace";
15
16
  /** A POSIX path joined and normalized: `..` cannot climb past `/`. */
@@ -34,7 +35,7 @@ export function clip(text, limit) {
34
35
  /** runtime_exec, runtime_read_file, runtime_write_file and runtime_list_files, bound to `sandbox`. */
35
36
  export function sandboxTools(sandbox, options = {}) {
36
37
  const root = resolvePath("/", options.root ?? WORKSPACE);
37
- const timeoutSeconds = options.timeoutSeconds ?? 300;
38
+ const timeoutSeconds = options.timeoutSeconds ?? TOOL_EXEC_TIMEOUT_SECONDS;
38
39
  const limit = options.maxOutputChars ?? 20_000;
39
40
  const inFilesApi = (path) => path.startsWith(`${WORKSPACE}/`);
40
41
  return [
@@ -1,17 +1,35 @@
1
1
  import { RuntimeError } from "./errors.js";
2
- export declare const VERSION = "0.8.4";
2
+ export declare const VERSION = "0.10.0";
3
3
  export declare const DEFAULT_BASE_URL = "https://api.withruntime.com";
4
+ /** Runtime's API as code inside a Runtime sandbox reaches it: the sandbox's
5
+ * own host sends each request on to DEFAULT_BASE_URL over HTTPS. The API runs
6
+ * on that host, whose addresses a sandbox cannot reach directly. Plain HTTP
7
+ * because the hop never leaves the machine: it goes from the program to the
8
+ * guest's own proxy and over the sandbox's private channel to its host. */
9
+ export declare const SANDBOX_BASE_URL = "http://runtime.internal";
10
+ /** True in a Runtime sandbox. Node and Bun only; a browser is never one. */
11
+ export declare function inRuntimeSandbox(marker?: string): boolean;
12
+ /** Where calls go when no base URL is given: RUNTIME_API_URL, then Runtime's
13
+ * public API, which `reachable` turns into runtime.internal in a sandbox. */
14
+ export declare function defaultBaseUrl(env?: Record<string, string | undefined>, inSandbox?: typeof inRuntimeSandbox): string;
15
+ /** The origin calls for `origin` are sent to from here. In a sandbox the
16
+ * public API is its own host, which it cannot reach directly, so calls for it
17
+ * go to runtime.internal; every other origin is left as it is. */
18
+ export declare function reachable(origin: string, inSandbox?: typeof inRuntimeSandbox): string;
4
19
  export type RequestOptions = {
5
20
  /** Sent as Idempotency-Key. Omit it: the SDK makes one per call and keeps
6
21
  * it across its own retries, so a retried write never happens twice. Pass
7
22
  * your own only to retry a call yourself after the process restarted. */
8
23
  idempotencyKey?: string;
9
24
  signal?: AbortSignal;
10
- /** Deadline for this call, retries included. */
25
+ /** Deadline for this call, retries included. 0 disables this deadline;
26
+ * an explicit signal can still cancel the call. */
11
27
  timeoutMs?: number;
12
28
  };
13
29
  export type Query = Record<string, string | number | boolean | readonly string[] | undefined>;
14
30
  export type Call = RequestOptions & {
31
+ /** Internal protocol hook: successful headers arrived, before reading the body. */
32
+ onResponse?: (response: Response) => void;
15
33
  method: "GET" | "POST" | "PUT" | "DELETE";
16
34
  path: string;
17
35
  query?: Query;
@@ -69,12 +87,9 @@ export declare class Transport {
69
87
  send(call: Call): Promise<Response>;
70
88
  json<T>(call: Call): Promise<T>;
71
89
  bytes(call: Call): Promise<Uint8Array>;
72
- /** A file's bytes, read whole and checked: the API says a body's length
73
- * (x-content-length) and, when small, its SHA-256 (x-content-sha256) before
74
- * sending it, and a body that falls short or differs is read again, twice,
75
- * then refused. Nothing between the API and here reliably turns a stream
76
- * cut part way into an error: on 25 September 2026 a 50 MB file came back
77
- * 29 MB long, exit 0. */
90
+ /** A file's bytes, read whole and checked against its promised length and
91
+ * SHA-256. A short or different body is read again twice, within the same
92
+ * call deadline. */
78
93
  fileBytes(call: Call): Promise<Uint8Array>;
79
94
  /** A file's bytes as they arrive, for files too big to hold. The stream
80
95
  * errors with `download_incomplete` if it ends short of the length the API