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.
- package/BLAXEL.md +4 -4
- package/COMPATIBILITY.md +145 -0
- package/DAYTONA.md +13 -7
- package/E2B.md +34 -20
- package/README.md +22 -11
- package/README.src.md +22 -11
- package/VERCEL.md +13 -3
- package/compatibility-lock.json +357 -0
- package/dist/ai-harness/index.js +2 -2
- package/dist/api-defaults.d.ts +17 -0
- package/dist/api-defaults.js +22 -0
- package/dist/blaxel/errors.js +1 -1
- package/dist/blaxel/lookup.d.ts +3 -2
- package/dist/blaxel/lookup.js +3 -2
- package/dist/blaxel/pagination.d.ts +2 -2
- package/dist/blaxel/pagination.js +45 -17
- package/dist/blaxel/preview.d.ts +3 -2
- package/dist/blaxel/preview.js +16 -14
- package/dist/blaxel/process.d.ts +1 -1
- package/dist/blaxel/process.js +66 -11
- package/dist/blaxel/sandbox.d.ts +2 -0
- package/dist/blaxel/sandbox.js +2 -0
- package/dist/blaxel/snapshot.js +3 -5
- package/dist/cli-words.d.ts +27 -0
- package/dist/cli-words.js +135 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +351 -85
- package/dist/client.d.ts +33 -4
- package/dist/client.js +47 -12
- package/dist/cloudflare/identity.d.ts +7 -0
- package/dist/cloudflare/identity.js +13 -0
- package/dist/cloudflare/index.d.ts +303 -0
- package/dist/cloudflare/index.js +592 -0
- package/dist/cloudflare/worker.d.ts +419 -0
- package/dist/cloudflare/worker.js +447 -0
- package/dist/codesandbox/guest-service.d.ts +31 -0
- package/dist/codesandbox/guest-service.js +490 -0
- package/dist/codesandbox/index.d.ts +386 -0
- package/dist/codesandbox/index.js +1347 -0
- package/dist/compat/core.d.ts +40 -0
- package/dist/compat/core.js +238 -0
- package/dist/compat/files.d.ts +26 -0
- package/dist/compat/files.js +133 -0
- package/dist/compat/shell.d.ts +4 -0
- package/dist/compat/shell.js +165 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +107 -38
- package/dist/daytona/context.js +2 -1
- package/dist/daytona/daytona.js +11 -4
- package/dist/daytona/filesystem.d.ts +17 -4
- package/dist/daytona/filesystem.js +121 -38
- package/dist/daytona/index.d.ts +1 -1
- package/dist/daytona/process.d.ts +1 -1
- package/dist/daytona/process.js +27 -4
- package/dist/daytona/sandbox.d.ts +1 -1
- package/dist/daytona/sandbox.js +16 -7
- package/dist/e2b/client.js +1 -1
- package/dist/e2b/code-interpreter.d.ts +4 -5
- package/dist/e2b/code-interpreter.js +54 -56
- package/dist/e2b/commands.d.ts +35 -12
- package/dist/e2b/commands.js +169 -73
- package/dist/e2b/errors.js +1 -1
- package/dist/e2b/filesystem.d.ts +8 -6
- package/dist/e2b/filesystem.js +127 -28
- package/dist/e2b/index.d.ts +1 -0
- package/dist/e2b/index.js +1 -0
- package/dist/e2b/pty.d.ts +24 -0
- package/dist/e2b/pty.js +80 -0
- package/dist/e2b/sandbox.d.ts +6 -4
- package/dist/e2b/sandbox.js +59 -25
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +15 -2
- package/dist/file-source.d.ts +16 -0
- package/dist/file-source.js +162 -0
- package/dist/freestyle/index.d.ts +309 -0
- package/dist/freestyle/index.js +343 -0
- package/dist/index.d.ts +3 -1
- package/dist/login.js +16 -10
- package/dist/mcp.js +2 -2
- package/dist/modal/index.d.ts +205 -0
- package/dist/modal/index.js +486 -0
- package/dist/network-cli.js +112 -44
- package/dist/openai-agents/index.js +18 -71
- package/dist/page.d.ts +1 -1
- package/dist/page.js +2 -2
- package/dist/products/audit.d.ts +3 -0
- package/dist/products/index.d.ts +14 -0
- package/dist/products/index.js +12 -0
- package/dist/products/interpreter.d.ts +11 -4
- package/dist/products/interpreter.js +33 -9
- package/dist/products/jobs.js +3 -2
- package/dist/products/limits.d.ts +10 -0
- package/dist/products/observability.d.ts +44 -0
- package/dist/products/observability.js +6 -0
- package/dist/products/previews.d.ts +18 -4
- package/dist/products/previews.js +14 -5
- package/dist/products/tailscale.d.ts +57 -0
- package/dist/products/tailscale.js +17 -0
- package/dist/products/volumes.d.ts +42 -0
- package/dist/products/volumes.js +36 -0
- package/dist/products/watch.d.ts +25 -4
- package/dist/products/watch.js +132 -18
- package/dist/proxy.js +3 -1
- package/dist/runloop/index.d.ts +326 -0
- package/dist/runloop/index.js +535 -0
- package/dist/runloop/pagination.d.ts +46 -0
- package/dist/runloop/pagination.js +82 -0
- package/dist/runloop/snapshots.d.ts +52 -0
- package/dist/runloop/snapshots.js +244 -0
- package/dist/sandbox.d.ts +76 -16
- package/dist/sandbox.js +696 -135
- package/dist/snapshots.d.ts +20 -5
- package/dist/snapshots.js +11 -1
- package/dist/sprites/index.d.ts +251 -0
- package/dist/sprites/index.js +588 -0
- package/dist/tar.d.ts +10 -1
- package/dist/tar.js +311 -35
- package/dist/tools/index.js +2 -1
- package/dist/transport.d.ts +23 -8
- package/dist/transport.js +247 -127
- package/dist/types.d.ts +52 -3
- package/dist/vercel/command.d.ts +1 -1
- package/dist/vercel/command.js +15 -3
- package/dist/vercel/errors.d.ts +2 -2
- package/dist/vercel/errors.js +19 -2
- package/dist/vercel/filesystem.d.ts +21 -18
- package/dist/vercel/filesystem.js +95 -48
- package/dist/vercel/sandbox.d.ts +8 -8
- package/dist/vercel/sandbox.js +159 -64
- package/dist/vercel/snapshot.d.ts +3 -2
- package/dist/vercel/snapshot.js +9 -5
- package/package.json +46 -3
package/dist/tar.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { chmod, lstat, mkdir, readdir, readFile, realpath, symlink, unlink,
|
|
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 {
|
|
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
|
-
/**
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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 (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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(
|
|
138
|
-
const
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -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 ??
|
|
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 [
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,17 +1,35 @@
|
|
|
1
1
|
import { RuntimeError } from "./errors.js";
|
|
2
|
-
export declare const VERSION = "0.
|
|
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
|
|
73
|
-
*
|
|
74
|
-
*
|
|
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
|