@anvia/sandbox 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Indra Zulfi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,136 @@
1
+ import { AnyTool } from '@anvia/core/tool';
2
+
3
+ type SandboxFileType = "file" | "directory" | "symlink" | "other";
4
+ type SandboxNetworkMode = boolean | "none" | "host" | string;
5
+ interface Sandbox {
6
+ readonly provider: string;
7
+ createSession(options?: SandboxCreateSessionOptions): Promise<SandboxSession>;
8
+ }
9
+ interface SandboxSession {
10
+ readonly id: string;
11
+ readonly provider: string;
12
+ readonly workdir: string;
13
+ exec(options: SandboxExecOptions): Promise<SandboxExecResult>;
14
+ readFile(path: string): Promise<Uint8Array>;
15
+ readTextFile(path: string): Promise<string>;
16
+ writeFile(path: string, data: string | Uint8Array): Promise<void>;
17
+ writeTextFile(path: string, content: string): Promise<void>;
18
+ listFiles(path?: string): Promise<SandboxFileEntry[]>;
19
+ destroy(): Promise<void>;
20
+ }
21
+ interface SandboxCreateSessionOptions {
22
+ id?: string;
23
+ manifest?: SandboxManifest;
24
+ metadata?: Record<string, string>;
25
+ }
26
+ interface SandboxManifest {
27
+ files?: Record<string, string | Uint8Array>;
28
+ directories?: string[];
29
+ env?: Record<string, string>;
30
+ }
31
+ interface SandboxLimits {
32
+ timeoutMs?: number;
33
+ maxOutputBytes?: number;
34
+ memoryMb?: number;
35
+ cpus?: number;
36
+ pidsLimit?: number;
37
+ }
38
+ interface SandboxExecOptions {
39
+ command: string;
40
+ args?: string[];
41
+ cwd?: string;
42
+ env?: Record<string, string>;
43
+ timeoutMs?: number;
44
+ input?: string | Uint8Array;
45
+ signal?: AbortSignal;
46
+ onStdout?: (chunk: Uint8Array) => void;
47
+ onStderr?: (chunk: Uint8Array) => void;
48
+ }
49
+ interface SandboxExecResult {
50
+ stdout: string;
51
+ stderr: string;
52
+ exitCode: number;
53
+ durationMs: number;
54
+ timedOut: boolean;
55
+ aborted: boolean;
56
+ stdoutTruncated: boolean;
57
+ stderrTruncated: boolean;
58
+ }
59
+ interface SandboxFileEntry {
60
+ path: string;
61
+ type: SandboxFileType;
62
+ size?: number;
63
+ }
64
+ interface DockerSandboxSecurityOptions {
65
+ readonlyRootfs?: boolean;
66
+ noNewPrivileges?: boolean;
67
+ dropCapabilities?: string[];
68
+ }
69
+ interface DockerSandboxOptions {
70
+ image?: string;
71
+ pull?: "missing" | "always" | "never";
72
+ workdir?: string;
73
+ network?: SandboxNetworkMode;
74
+ user?: string;
75
+ dockerPath?: string;
76
+ labels?: Record<string, string>;
77
+ limits?: SandboxLimits;
78
+ security?: DockerSandboxSecurityOptions;
79
+ }
80
+ interface SandboxToolsOptions {
81
+ include?: SandboxToolName[];
82
+ execTimeoutMs?: number;
83
+ }
84
+ type SandboxToolName = "exec_command" | "read_file" | "write_file" | "list_files";
85
+ type SandboxToolsFactory = (session: SandboxSession, options?: SandboxToolsOptions) => AnyTool[];
86
+
87
+ declare class DockerSandbox implements Sandbox {
88
+ readonly provider = "docker";
89
+ private readonly image;
90
+ private readonly pull;
91
+ private readonly workdir;
92
+ private readonly network;
93
+ private readonly dockerPath;
94
+ private readonly labels;
95
+ private readonly limits;
96
+ private readonly security;
97
+ private readonly user;
98
+ constructor(options?: DockerSandboxOptions);
99
+ createSession(options?: SandboxCreateSessionOptions): Promise<SandboxSession>;
100
+ private ensureImage;
101
+ private createRunArgs;
102
+ private appendNetworkArgs;
103
+ private appendLimitArgs;
104
+ private appendSecurityArgs;
105
+ private cliOptions;
106
+ private cleanup;
107
+ }
108
+
109
+ declare class SandboxError extends Error {
110
+ readonly cause?: unknown | undefined;
111
+ constructor(message: string, cause?: unknown | undefined);
112
+ }
113
+ declare class SandboxDockerUnavailableError extends SandboxError {
114
+ }
115
+ declare class SandboxDockerCommandError extends SandboxError {
116
+ readonly result: {
117
+ stdout: string;
118
+ stderr: string;
119
+ exitCode: number;
120
+ };
121
+ constructor(message: string, result: {
122
+ stdout: string;
123
+ stderr: string;
124
+ exitCode: number;
125
+ });
126
+ }
127
+ declare class SandboxSessionDestroyedError extends SandboxError {
128
+ }
129
+ declare class SandboxPathError extends SandboxError {
130
+ }
131
+ declare class SandboxTimeoutError extends SandboxError {
132
+ }
133
+
134
+ declare function createSandboxTools(session: SandboxSession, options?: SandboxToolsOptions): AnyTool[];
135
+
136
+ export { DockerSandbox, type DockerSandboxOptions, type DockerSandboxSecurityOptions, type Sandbox, type SandboxCreateSessionOptions, SandboxDockerCommandError, SandboxDockerUnavailableError, SandboxError, type SandboxExecOptions, type SandboxExecResult, type SandboxFileEntry, type SandboxFileType, type SandboxLimits, type SandboxManifest, type SandboxNetworkMode, SandboxPathError, type SandboxSession, SandboxSessionDestroyedError, SandboxTimeoutError, type SandboxToolName, type SandboxToolsFactory, type SandboxToolsOptions, createSandboxTools };
package/dist/index.js ADDED
@@ -0,0 +1,648 @@
1
+ // src/docker-sandbox.ts
2
+ import { randomUUID } from "crypto";
3
+ import { mkdtemp, rm, writeFile } from "fs/promises";
4
+ import os from "os";
5
+ import path2 from "path";
6
+
7
+ // src/docker-cli.ts
8
+ import { spawn } from "child_process";
9
+
10
+ // src/errors.ts
11
+ var SandboxError = class extends Error {
12
+ constructor(message, cause) {
13
+ super(message);
14
+ this.cause = cause;
15
+ this.name = new.target.name;
16
+ }
17
+ cause;
18
+ };
19
+ var SandboxDockerUnavailableError = class extends SandboxError {
20
+ };
21
+ var SandboxDockerCommandError = class extends SandboxError {
22
+ constructor(message, result) {
23
+ super(message);
24
+ this.result = result;
25
+ }
26
+ result;
27
+ };
28
+ var SandboxSessionDestroyedError = class extends SandboxError {
29
+ };
30
+ var SandboxPathError = class extends SandboxError {
31
+ };
32
+ var SandboxTimeoutError = class extends SandboxError {
33
+ };
34
+
35
+ // src/docker-cli.ts
36
+ var defaultMaxOutputBytes = 1024 * 1024;
37
+ async function runDockerCli(args, options) {
38
+ const startedAt = Date.now();
39
+ const maxOutputBytes = options.maxOutputBytes ?? defaultMaxOutputBytes;
40
+ const stdout = createOutputCollector(maxOutputBytes, options.onStdout);
41
+ const stderr = createOutputCollector(maxOutputBytes, options.onStderr);
42
+ return new Promise((resolve, reject) => {
43
+ const child = spawn(options.dockerPath, args, {
44
+ stdio: ["pipe", "pipe", "pipe"]
45
+ });
46
+ let timedOut = false;
47
+ let aborted = false;
48
+ let settled = false;
49
+ const timeout = options.timeoutMs === void 0 ? void 0 : setTimeout(() => {
50
+ timedOut = true;
51
+ child.kill("SIGKILL");
52
+ }, options.timeoutMs);
53
+ const abort = () => {
54
+ aborted = true;
55
+ child.kill("SIGKILL");
56
+ };
57
+ if (options.signal?.aborted === true) {
58
+ abort();
59
+ } else {
60
+ options.signal?.addEventListener("abort", abort, { once: true });
61
+ }
62
+ child.stdout.on("data", (chunk) => stdout.accept(chunk));
63
+ child.stderr.on("data", (chunk) => stderr.accept(chunk));
64
+ child.on("error", (error) => {
65
+ if (settled) {
66
+ return;
67
+ }
68
+ settled = true;
69
+ clearTimeout(timeout);
70
+ options.signal?.removeEventListener("abort", abort);
71
+ if (error.code === "ENOENT") {
72
+ reject(new SandboxDockerUnavailableError("Docker CLI was not found.", error));
73
+ return;
74
+ }
75
+ reject(error);
76
+ });
77
+ child.on("close", (code) => {
78
+ if (settled) {
79
+ return;
80
+ }
81
+ settled = true;
82
+ clearTimeout(timeout);
83
+ options.signal?.removeEventListener("abort", abort);
84
+ resolve({
85
+ stdout: stdout.text(),
86
+ stderr: stderr.text(),
87
+ exitCode: code ?? 1,
88
+ durationMs: Date.now() - startedAt,
89
+ timedOut,
90
+ aborted,
91
+ stdoutTruncated: stdout.truncated,
92
+ stderrTruncated: stderr.truncated
93
+ });
94
+ });
95
+ if (options.input !== void 0) {
96
+ child.stdin.end(options.input);
97
+ } else {
98
+ child.stdin.end();
99
+ }
100
+ });
101
+ }
102
+ async function assertDockerCli(args, options) {
103
+ const result = await runDockerCli(args, options);
104
+ if (result.exitCode !== 0) {
105
+ throw new SandboxDockerCommandError(`Docker command failed: docker ${args.join(" ")}`, result);
106
+ }
107
+ return result.stdout.trim();
108
+ }
109
+ function createOutputCollector(maxBytes, onChunk) {
110
+ const chunks = [];
111
+ let length = 0;
112
+ let truncated = false;
113
+ return {
114
+ get truncated() {
115
+ return truncated;
116
+ },
117
+ accept(chunk) {
118
+ onChunk?.(chunk);
119
+ if (length >= maxBytes) {
120
+ truncated = true;
121
+ return;
122
+ }
123
+ const remaining = maxBytes - length;
124
+ const next = chunk.length > remaining ? chunk.subarray(0, remaining) : chunk;
125
+ chunks.push(next);
126
+ length += next.length;
127
+ if (next.length < chunk.length) {
128
+ truncated = true;
129
+ }
130
+ },
131
+ text() {
132
+ return Buffer.concat(chunks, length).toString("utf8");
133
+ }
134
+ };
135
+ }
136
+
137
+ // src/path.ts
138
+ import path from "path";
139
+ function normalizeSandboxPath(input, options = {}) {
140
+ if (input.length === 0) {
141
+ throw new SandboxPathError("Sandbox path cannot be empty.");
142
+ }
143
+ if (input.includes("\0")) {
144
+ throw new SandboxPathError("Sandbox path cannot contain null bytes.");
145
+ }
146
+ const normalized = path.posix.normalize(input.replaceAll("\\", "/"));
147
+ if (path.posix.isAbsolute(normalized)) {
148
+ throw new SandboxPathError(`Sandbox path must be relative: ${input}`);
149
+ }
150
+ if (normalized === ".." || normalized.startsWith("../")) {
151
+ throw new SandboxPathError(`Sandbox path cannot leave the workspace: ${input}`);
152
+ }
153
+ if (normalized === "." && options.allowRoot !== true) {
154
+ throw new SandboxPathError(
155
+ "Sandbox path must refer to a file or directory inside the workspace."
156
+ );
157
+ }
158
+ return normalized;
159
+ }
160
+ function containerPath(workdir, relativePath) {
161
+ const normalizedWorkdir = path.posix.normalize(workdir);
162
+ const normalizedPath = normalizeSandboxPath(relativePath, { allowRoot: true });
163
+ return normalizedPath === "." ? normalizedWorkdir : path.posix.join(normalizedWorkdir, normalizedPath);
164
+ }
165
+ function parentSandboxPath(relativePath) {
166
+ const normalized = normalizeSandboxPath(relativePath);
167
+ const parent = path.posix.dirname(normalized);
168
+ return parent === "." ? "." : parent;
169
+ }
170
+
171
+ // src/docker-sandbox.ts
172
+ var defaultImage = "node:22-bookworm";
173
+ var defaultWorkdir = "/workspace";
174
+ var defaultTimeoutMs = 3e4;
175
+ var defaultMaxOutputBytes2 = 1024 * 1024;
176
+ var DockerSandbox = class {
177
+ provider = "docker";
178
+ image;
179
+ pull;
180
+ workdir;
181
+ network;
182
+ dockerPath;
183
+ labels;
184
+ limits;
185
+ security;
186
+ user;
187
+ constructor(options = {}) {
188
+ this.image = options.image ?? defaultImage;
189
+ this.pull = options.pull ?? "missing";
190
+ this.workdir = options.workdir ?? defaultWorkdir;
191
+ this.network = options.network ?? false;
192
+ this.dockerPath = options.dockerPath ?? "docker";
193
+ this.labels = options.labels ?? {};
194
+ this.limits = options.limits ?? {};
195
+ this.security = {
196
+ readonlyRootfs: options.security?.readonlyRootfs ?? false,
197
+ noNewPrivileges: options.security?.noNewPrivileges ?? true,
198
+ dropCapabilities: options.security?.dropCapabilities ?? ["ALL"]
199
+ };
200
+ this.user = options.user;
201
+ }
202
+ async createSession(options = {}) {
203
+ await this.ensureImage();
204
+ const id = sanitizeResourceId(options.id ?? randomUUID());
205
+ const containerName = `anvia-sandbox-${id}`;
206
+ const volumeName = `anvia-sandbox-${id}-workspace`;
207
+ await assertDockerCli(["volume", "create", volumeName], this.cliOptions());
208
+ try {
209
+ await assertDockerCli(this.createRunArgs(containerName, volumeName, options.metadata), {
210
+ ...this.cliOptions(),
211
+ timeoutMs: this.limits.timeoutMs ?? defaultTimeoutMs
212
+ });
213
+ const session = new DockerSandboxSession({
214
+ id,
215
+ containerName,
216
+ volumeName,
217
+ workdir: this.workdir,
218
+ dockerPath: this.dockerPath,
219
+ limits: this.limits,
220
+ env: options.manifest?.env ?? {}
221
+ });
222
+ await session.applyManifest(options.manifest);
223
+ return session;
224
+ } catch (error) {
225
+ await this.cleanup(containerName, volumeName);
226
+ throw error;
227
+ }
228
+ }
229
+ async ensureImage() {
230
+ if (this.pull === "always") {
231
+ await assertDockerCli(["pull", this.image], this.cliOptions());
232
+ return;
233
+ }
234
+ if (this.pull === "missing") {
235
+ const inspect = await runDockerCli(["image", "inspect", this.image], this.cliOptions());
236
+ if (inspect.exitCode !== 0) {
237
+ await assertDockerCli(["pull", this.image], this.cliOptions());
238
+ }
239
+ }
240
+ }
241
+ createRunArgs(containerName, volumeName, metadata) {
242
+ const args = [
243
+ "run",
244
+ "-d",
245
+ "--name",
246
+ containerName,
247
+ "-v",
248
+ `${volumeName}:${this.workdir}`,
249
+ "-w",
250
+ this.workdir,
251
+ "--label",
252
+ "anvia.sandbox=true"
253
+ ];
254
+ for (const [key, value] of Object.entries(this.labels)) {
255
+ args.push("--label", `${key}=${value}`);
256
+ }
257
+ if (metadata !== void 0) {
258
+ for (const [key, value] of Object.entries(metadata)) {
259
+ args.push("--label", `anvia.sandbox.metadata.${key}=${value}`);
260
+ }
261
+ }
262
+ this.appendNetworkArgs(args);
263
+ this.appendLimitArgs(args);
264
+ this.appendSecurityArgs(args);
265
+ if (this.user !== void 0) {
266
+ args.push("-u", this.user);
267
+ }
268
+ args.push(
269
+ this.image,
270
+ "sh",
271
+ "-c",
272
+ "trap 'exit 0' TERM INT; while :; do sleep 3600 & wait $!; done"
273
+ );
274
+ return args;
275
+ }
276
+ appendNetworkArgs(args) {
277
+ if (this.network === false || this.network === "none") {
278
+ args.push("--network", "none");
279
+ return;
280
+ }
281
+ if (this.network !== true) {
282
+ args.push("--network", this.network);
283
+ }
284
+ }
285
+ appendLimitArgs(args) {
286
+ if (this.limits.memoryMb !== void 0) {
287
+ args.push("--memory", `${this.limits.memoryMb}m`);
288
+ }
289
+ if (this.limits.cpus !== void 0) {
290
+ args.push("--cpus", String(this.limits.cpus));
291
+ }
292
+ if (this.limits.pidsLimit !== void 0) {
293
+ args.push("--pids-limit", String(this.limits.pidsLimit));
294
+ }
295
+ }
296
+ appendSecurityArgs(args) {
297
+ if (this.security.readonlyRootfs) {
298
+ args.push("--read-only");
299
+ }
300
+ if (this.security.noNewPrivileges) {
301
+ args.push("--security-opt", "no-new-privileges");
302
+ }
303
+ for (const capability of this.security.dropCapabilities) {
304
+ args.push("--cap-drop", capability);
305
+ }
306
+ }
307
+ cliOptions() {
308
+ return {
309
+ dockerPath: this.dockerPath,
310
+ maxOutputBytes: this.limits.maxOutputBytes ?? defaultMaxOutputBytes2
311
+ };
312
+ }
313
+ async cleanup(containerName, volumeName) {
314
+ await runDockerCli(["rm", "-f", containerName], this.cliOptions()).catch(() => void 0);
315
+ await runDockerCli(["volume", "rm", "-f", volumeName], this.cliOptions()).catch(
316
+ () => void 0
317
+ );
318
+ }
319
+ };
320
+ var DockerSandboxSession = class {
321
+ provider = "docker";
322
+ id;
323
+ workdir;
324
+ containerName;
325
+ volumeName;
326
+ dockerPath;
327
+ limits;
328
+ env;
329
+ destroyed = false;
330
+ constructor(options) {
331
+ this.id = options.id;
332
+ this.containerName = options.containerName;
333
+ this.volumeName = options.volumeName;
334
+ this.workdir = options.workdir;
335
+ this.dockerPath = options.dockerPath;
336
+ this.limits = options.limits;
337
+ this.env = options.env;
338
+ }
339
+ async applyManifest(manifest) {
340
+ this.assertActive();
341
+ for (const directory of manifest?.directories ?? []) {
342
+ await this.mkdir(directory);
343
+ }
344
+ for (const [filePath, content] of Object.entries(manifest?.files ?? {})) {
345
+ await this.writeFile(filePath, content);
346
+ }
347
+ }
348
+ async exec(options) {
349
+ this.assertActive();
350
+ if (options.command.trim().length === 0) {
351
+ throw new SandboxDockerCommandError("Sandbox command cannot be empty.", {
352
+ stdout: "",
353
+ stderr: "",
354
+ exitCode: 1
355
+ });
356
+ }
357
+ const args = ["exec"];
358
+ if (options.input !== void 0) {
359
+ args.push("-i");
360
+ }
361
+ const cwd = containerPath(this.workdir, options.cwd ?? ".");
362
+ args.push("-w", cwd);
363
+ for (const [key, value] of Object.entries({ ...this.env, ...options.env })) {
364
+ args.push("-e", `${key}=${value}`);
365
+ }
366
+ args.push(this.containerName, options.command, ...options.args ?? []);
367
+ const cliOptions = {
368
+ dockerPath: this.dockerPath,
369
+ timeoutMs: options.timeoutMs ?? this.limits.timeoutMs ?? defaultTimeoutMs,
370
+ maxOutputBytes: this.limits.maxOutputBytes ?? defaultMaxOutputBytes2,
371
+ ...options.input === void 0 ? {} : { input: options.input },
372
+ ...options.signal === void 0 ? {} : { signal: options.signal },
373
+ ...options.onStdout === void 0 ? {} : { onStdout: options.onStdout },
374
+ ...options.onStderr === void 0 ? {} : { onStderr: options.onStderr }
375
+ };
376
+ const result = await runDockerCli(args, cliOptions);
377
+ if (result.timedOut) {
378
+ return {
379
+ ...result,
380
+ exitCode: result.exitCode === 0 ? 124 : result.exitCode
381
+ };
382
+ }
383
+ return result;
384
+ }
385
+ async readFile(filePath) {
386
+ this.assertActive();
387
+ const normalized = normalizeSandboxPath(filePath);
388
+ const tempDir = await mkdtemp(path2.join(os.tmpdir(), "anvia-sandbox-read-"));
389
+ const target = path2.join(tempDir, path2.basename(normalized));
390
+ try {
391
+ await assertDockerCli(
392
+ ["cp", `${this.containerName}:${containerPath(this.workdir, normalized)}`, target],
393
+ this.cliOptions()
394
+ );
395
+ const { readFile } = await import("fs/promises");
396
+ return await readFile(target);
397
+ } finally {
398
+ await rm(tempDir, { recursive: true, force: true });
399
+ }
400
+ }
401
+ async readTextFile(filePath) {
402
+ const bytes = await this.readFile(filePath);
403
+ return new TextDecoder().decode(bytes);
404
+ }
405
+ async writeFile(filePath, data) {
406
+ this.assertActive();
407
+ const normalized = normalizeSandboxPath(filePath);
408
+ await this.mkdir(parentSandboxPath(normalized));
409
+ const tempDir = await mkdtemp(path2.join(os.tmpdir(), "anvia-sandbox-write-"));
410
+ const source = path2.join(tempDir, path2.basename(normalized));
411
+ try {
412
+ await writeFile(source, data);
413
+ await assertDockerCli(
414
+ ["cp", source, `${this.containerName}:${containerPath(this.workdir, normalized)}`],
415
+ this.cliOptions()
416
+ );
417
+ } finally {
418
+ await rm(tempDir, { recursive: true, force: true });
419
+ }
420
+ }
421
+ async writeTextFile(filePath, content) {
422
+ await this.writeFile(filePath, content);
423
+ }
424
+ async listFiles(filePath = ".") {
425
+ this.assertActive();
426
+ const normalized = normalizeSandboxPath(filePath, { allowRoot: true });
427
+ const target = containerPath(this.workdir, normalized);
428
+ const result = await this.exec({
429
+ command: "find",
430
+ args: [target, "-mindepth", "1", "-maxdepth", "1", "-printf", "%p %y %s\n"]
431
+ });
432
+ if (result.timedOut) {
433
+ throw new SandboxTimeoutError(`Listing files timed out for ${filePath}.`);
434
+ }
435
+ if (result.exitCode !== 0) {
436
+ throw new SandboxDockerCommandError(`Unable to list sandbox path: ${filePath}`, result);
437
+ }
438
+ return result.stdout.split("\n").filter((line) => line.trim().length > 0).map((line) => this.parseFindEntry(line));
439
+ }
440
+ async destroy() {
441
+ if (this.destroyed) {
442
+ return;
443
+ }
444
+ this.destroyed = true;
445
+ await runDockerCli(["rm", "-f", this.containerName], this.cliOptions()).catch(() => void 0);
446
+ await runDockerCli(["volume", "rm", "-f", this.volumeName], this.cliOptions()).catch(
447
+ () => void 0
448
+ );
449
+ }
450
+ async mkdir(directoryPath) {
451
+ const normalized = normalizeSandboxPath(directoryPath, { allowRoot: true });
452
+ const result = await this.exec({
453
+ command: "mkdir",
454
+ args: ["-p", containerPath(this.workdir, normalized)]
455
+ });
456
+ if (result.exitCode !== 0) {
457
+ throw new SandboxDockerCommandError(
458
+ `Unable to create sandbox directory: ${directoryPath}`,
459
+ result
460
+ );
461
+ }
462
+ }
463
+ parseFindEntry(line) {
464
+ const [rawPath, rawType, rawSize] = line.split(" ");
465
+ const absolutePath = rawPath ?? "";
466
+ const relativePath = absolutePath.startsWith(`${this.workdir}/`) ? absolutePath.slice(this.workdir.length + 1) : absolutePath;
467
+ const size = rawSize === void 0 ? void 0 : Number(rawSize);
468
+ const entry = {
469
+ path: relativePath,
470
+ type: mapFindType(rawType)
471
+ };
472
+ if (size !== void 0 && Number.isFinite(size)) {
473
+ entry.size = size;
474
+ }
475
+ return entry;
476
+ }
477
+ cliOptions() {
478
+ return {
479
+ dockerPath: this.dockerPath,
480
+ maxOutputBytes: this.limits.maxOutputBytes ?? defaultMaxOutputBytes2
481
+ };
482
+ }
483
+ assertActive() {
484
+ if (this.destroyed) {
485
+ throw new SandboxSessionDestroyedError(`Sandbox session ${this.id} has been destroyed.`);
486
+ }
487
+ }
488
+ };
489
+ function sanitizeResourceId(id) {
490
+ const sanitized = id.toLowerCase().replaceAll(/[^a-z0-9_.-]/g, "-").replaceAll(/^-+|-+$/g, "");
491
+ return sanitized.length > 0 ? sanitized : randomUUID();
492
+ }
493
+ function mapFindType(type) {
494
+ if (type === "f") {
495
+ return "file";
496
+ }
497
+ if (type === "d") {
498
+ return "directory";
499
+ }
500
+ if (type === "l") {
501
+ return "symlink";
502
+ }
503
+ return "other";
504
+ }
505
+
506
+ // src/tools.ts
507
+ import { createTool } from "@anvia/core/tool";
508
+ import { z } from "zod";
509
+ var execCommandInput = z.object({
510
+ command: z.string().min(1).describe("Executable to run inside the sandbox workspace."),
511
+ args: z.array(z.string()).optional().describe("Command arguments."),
512
+ cwd: z.string().optional().describe("Relative working directory inside the sandbox."),
513
+ env: z.record(z.string(), z.string()).optional().describe("Environment variables for this command."),
514
+ timeoutMs: z.number().int().positive().max(3e5).optional().describe("Optional command timeout in milliseconds."),
515
+ input: z.string().optional().describe("Optional stdin text to pass to the command.")
516
+ });
517
+ var readFileInput = z.object({
518
+ path: z.string().min(1).describe("Relative file path inside the sandbox.")
519
+ });
520
+ var writeFileInput = z.object({
521
+ path: z.string().min(1).describe("Relative file path inside the sandbox."),
522
+ content: z.string().describe("Complete text content to write.")
523
+ });
524
+ var listFilesInput = z.object({
525
+ path: z.string().optional().describe("Relative directory path inside the sandbox. Defaults to root.")
526
+ });
527
+ var textOutput = z.string();
528
+ function createSandboxTools(session, options = {}) {
529
+ const include = new Set(
530
+ options.include ?? ["exec_command", "read_file", "write_file", "list_files"]
531
+ );
532
+ const tools = [];
533
+ if (include.has("exec_command")) {
534
+ tools.push(createExecCommandTool(session, options));
535
+ }
536
+ if (include.has("read_file")) {
537
+ tools.push(createReadFileTool(session));
538
+ }
539
+ if (include.has("write_file")) {
540
+ tools.push(createWriteFileTool(session));
541
+ }
542
+ if (include.has("list_files")) {
543
+ tools.push(createListFilesTool(session));
544
+ }
545
+ return tools;
546
+ }
547
+ function createExecCommandTool(session, options) {
548
+ return createTool({
549
+ name: "exec_command",
550
+ description: "Run a command inside the sandbox workspace. Use structured args instead of shell quoting.",
551
+ input: execCommandInput,
552
+ output: textOutput,
553
+ execute: async ({ command, args, cwd, env, timeoutMs, input }) => {
554
+ const execOptions = {
555
+ command
556
+ };
557
+ if (args !== void 0) {
558
+ execOptions.args = args;
559
+ }
560
+ if (cwd !== void 0) {
561
+ execOptions.cwd = cwd;
562
+ }
563
+ if (env !== void 0) {
564
+ execOptions.env = env;
565
+ }
566
+ const effectiveTimeoutMs = timeoutMs ?? options.execTimeoutMs;
567
+ if (effectiveTimeoutMs !== void 0) {
568
+ execOptions.timeoutMs = effectiveTimeoutMs;
569
+ }
570
+ if (input !== void 0) {
571
+ execOptions.input = input;
572
+ }
573
+ const result = await session.exec(execOptions);
574
+ return formatExecResult(result);
575
+ }
576
+ });
577
+ }
578
+ function createReadFileTool(session) {
579
+ return createTool({
580
+ name: "read_file",
581
+ description: "Read a text file from the sandbox workspace.",
582
+ input: readFileInput,
583
+ output: textOutput,
584
+ execute: async ({ path: path3 }) => session.readTextFile(path3)
585
+ });
586
+ }
587
+ function createWriteFileTool(session) {
588
+ return createTool({
589
+ name: "write_file",
590
+ description: "Write a text file inside the sandbox workspace. Creates parent directories.",
591
+ input: writeFileInput,
592
+ output: textOutput,
593
+ execute: async ({ path: path3, content }) => {
594
+ await session.writeTextFile(path3, content);
595
+ return `Wrote ${path3}`;
596
+ }
597
+ });
598
+ }
599
+ function createListFilesTool(session) {
600
+ return createTool({
601
+ name: "list_files",
602
+ description: "List files and directories inside the sandbox workspace.",
603
+ input: listFilesInput,
604
+ output: textOutput,
605
+ execute: async ({ path: path3 }) => {
606
+ const entries = await session.listFiles(path3);
607
+ if (entries.length === 0) {
608
+ return "No files found.";
609
+ }
610
+ return entries.map((entry) => {
611
+ const size = entry.size === void 0 ? "" : ` ${entry.size}b`;
612
+ return `${entry.type}${size} ${entry.path}`;
613
+ }).join("\n");
614
+ }
615
+ });
616
+ }
617
+ function formatExecResult(result) {
618
+ const parts = [`exit_code: ${result.exitCode}`];
619
+ if (result.timedOut) {
620
+ parts.push("timed_out: true");
621
+ }
622
+ if (result.aborted) {
623
+ parts.push("aborted: true");
624
+ }
625
+ if (result.stdout.length > 0) {
626
+ parts.push(`stdout:
627
+ ${result.stdout.trimEnd()}`);
628
+ }
629
+ if (result.stderr.length > 0) {
630
+ parts.push(`stderr:
631
+ ${result.stderr.trimEnd()}`);
632
+ }
633
+ if (result.stdoutTruncated || result.stderrTruncated) {
634
+ parts.push("output_truncated: true");
635
+ }
636
+ return parts.join("\n\n");
637
+ }
638
+ export {
639
+ DockerSandbox,
640
+ SandboxDockerCommandError,
641
+ SandboxDockerUnavailableError,
642
+ SandboxError,
643
+ SandboxPathError,
644
+ SandboxSessionDestroyedError,
645
+ SandboxTimeoutError,
646
+ createSandboxTools
647
+ };
648
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/docker-sandbox.ts","../src/docker-cli.ts","../src/errors.ts","../src/path.ts","../src/tools.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdtemp, rm, writeFile } from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { assertDockerCli, runDockerCli } from \"./docker-cli\";\nimport {\n SandboxDockerCommandError,\n SandboxSessionDestroyedError,\n SandboxTimeoutError,\n} from \"./errors\";\nimport { containerPath, normalizeSandboxPath, parentSandboxPath } from \"./path\";\nimport type {\n DockerSandboxOptions,\n Sandbox,\n SandboxCreateSessionOptions,\n SandboxExecOptions,\n SandboxExecResult,\n SandboxFileEntry,\n SandboxFileType,\n SandboxLimits,\n SandboxManifest,\n SandboxNetworkMode,\n SandboxSession,\n} from \"./types\";\n\nconst defaultImage = \"node:22-bookworm\";\nconst defaultWorkdir = \"/workspace\";\nconst defaultTimeoutMs = 30_000;\nconst defaultMaxOutputBytes = 1024 * 1024;\n\nexport class DockerSandbox implements Sandbox {\n readonly provider = \"docker\";\n\n private readonly image: string;\n private readonly pull: \"missing\" | \"always\" | \"never\";\n private readonly workdir: string;\n private readonly network: SandboxNetworkMode;\n private readonly dockerPath: string;\n private readonly labels: Record<string, string>;\n private readonly limits: SandboxLimits;\n private readonly security: Required<NonNullable<DockerSandboxOptions[\"security\"]>>;\n private readonly user: string | undefined;\n\n constructor(options: DockerSandboxOptions = {}) {\n this.image = options.image ?? defaultImage;\n this.pull = options.pull ?? \"missing\";\n this.workdir = options.workdir ?? defaultWorkdir;\n this.network = options.network ?? false;\n this.dockerPath = options.dockerPath ?? \"docker\";\n this.labels = options.labels ?? {};\n this.limits = options.limits ?? {};\n this.security = {\n readonlyRootfs: options.security?.readonlyRootfs ?? false,\n noNewPrivileges: options.security?.noNewPrivileges ?? true,\n dropCapabilities: options.security?.dropCapabilities ?? [\"ALL\"],\n };\n this.user = options.user;\n }\n\n async createSession(options: SandboxCreateSessionOptions = {}): Promise<SandboxSession> {\n await this.ensureImage();\n\n const id = sanitizeResourceId(options.id ?? randomUUID());\n const containerName = `anvia-sandbox-${id}`;\n const volumeName = `anvia-sandbox-${id}-workspace`;\n\n await assertDockerCli([\"volume\", \"create\", volumeName], this.cliOptions());\n\n try {\n await assertDockerCli(this.createRunArgs(containerName, volumeName, options.metadata), {\n ...this.cliOptions(),\n timeoutMs: this.limits.timeoutMs ?? defaultTimeoutMs,\n });\n\n const session = new DockerSandboxSession({\n id,\n containerName,\n volumeName,\n workdir: this.workdir,\n dockerPath: this.dockerPath,\n limits: this.limits,\n env: options.manifest?.env ?? {},\n });\n\n await session.applyManifest(options.manifest);\n return session;\n } catch (error) {\n await this.cleanup(containerName, volumeName);\n throw error;\n }\n }\n\n private async ensureImage(): Promise<void> {\n if (this.pull === \"always\") {\n await assertDockerCli([\"pull\", this.image], this.cliOptions());\n return;\n }\n\n if (this.pull === \"missing\") {\n const inspect = await runDockerCli([\"image\", \"inspect\", this.image], this.cliOptions());\n if (inspect.exitCode !== 0) {\n await assertDockerCli([\"pull\", this.image], this.cliOptions());\n }\n }\n }\n\n private createRunArgs(\n containerName: string,\n volumeName: string,\n metadata: Record<string, string> | undefined,\n ): string[] {\n const args = [\n \"run\",\n \"-d\",\n \"--name\",\n containerName,\n \"-v\",\n `${volumeName}:${this.workdir}`,\n \"-w\",\n this.workdir,\n \"--label\",\n \"anvia.sandbox=true\",\n ];\n\n for (const [key, value] of Object.entries(this.labels)) {\n args.push(\"--label\", `${key}=${value}`);\n }\n\n if (metadata !== undefined) {\n for (const [key, value] of Object.entries(metadata)) {\n args.push(\"--label\", `anvia.sandbox.metadata.${key}=${value}`);\n }\n }\n\n this.appendNetworkArgs(args);\n this.appendLimitArgs(args);\n this.appendSecurityArgs(args);\n\n if (this.user !== undefined) {\n args.push(\"-u\", this.user);\n }\n\n args.push(\n this.image,\n \"sh\",\n \"-c\",\n \"trap 'exit 0' TERM INT; while :; do sleep 3600 & wait $!; done\",\n );\n return args;\n }\n\n private appendNetworkArgs(args: string[]): void {\n if (this.network === false || this.network === \"none\") {\n args.push(\"--network\", \"none\");\n return;\n }\n\n if (this.network !== true) {\n args.push(\"--network\", this.network);\n }\n }\n\n private appendLimitArgs(args: string[]): void {\n if (this.limits.memoryMb !== undefined) {\n args.push(\"--memory\", `${this.limits.memoryMb}m`);\n }\n\n if (this.limits.cpus !== undefined) {\n args.push(\"--cpus\", String(this.limits.cpus));\n }\n\n if (this.limits.pidsLimit !== undefined) {\n args.push(\"--pids-limit\", String(this.limits.pidsLimit));\n }\n }\n\n private appendSecurityArgs(args: string[]): void {\n if (this.security.readonlyRootfs) {\n args.push(\"--read-only\");\n }\n\n if (this.security.noNewPrivileges) {\n args.push(\"--security-opt\", \"no-new-privileges\");\n }\n\n for (const capability of this.security.dropCapabilities) {\n args.push(\"--cap-drop\", capability);\n }\n }\n\n private cliOptions() {\n return {\n dockerPath: this.dockerPath,\n maxOutputBytes: this.limits.maxOutputBytes ?? defaultMaxOutputBytes,\n };\n }\n\n private async cleanup(containerName: string, volumeName: string): Promise<void> {\n await runDockerCli([\"rm\", \"-f\", containerName], this.cliOptions()).catch(() => undefined);\n await runDockerCli([\"volume\", \"rm\", \"-f\", volumeName], this.cliOptions()).catch(\n () => undefined,\n );\n }\n}\n\nclass DockerSandboxSession implements SandboxSession {\n readonly provider = \"docker\";\n readonly id: string;\n readonly workdir: string;\n\n private readonly containerName: string;\n private readonly volumeName: string;\n private readonly dockerPath: string;\n private readonly limits: SandboxLimits;\n private readonly env: Record<string, string>;\n private destroyed = false;\n\n constructor(options: {\n id: string;\n containerName: string;\n volumeName: string;\n workdir: string;\n dockerPath: string;\n limits: SandboxLimits;\n env: Record<string, string>;\n }) {\n this.id = options.id;\n this.containerName = options.containerName;\n this.volumeName = options.volumeName;\n this.workdir = options.workdir;\n this.dockerPath = options.dockerPath;\n this.limits = options.limits;\n this.env = options.env;\n }\n\n async applyManifest(manifest: SandboxManifest | undefined): Promise<void> {\n this.assertActive();\n\n for (const directory of manifest?.directories ?? []) {\n await this.mkdir(directory);\n }\n\n for (const [filePath, content] of Object.entries(manifest?.files ?? {})) {\n await this.writeFile(filePath, content);\n }\n }\n\n async exec(options: SandboxExecOptions): Promise<SandboxExecResult> {\n this.assertActive();\n\n if (options.command.trim().length === 0) {\n throw new SandboxDockerCommandError(\"Sandbox command cannot be empty.\", {\n stdout: \"\",\n stderr: \"\",\n exitCode: 1,\n });\n }\n\n const args = [\"exec\"];\n\n if (options.input !== undefined) {\n args.push(\"-i\");\n }\n\n const cwd = containerPath(this.workdir, options.cwd ?? \".\");\n args.push(\"-w\", cwd);\n\n for (const [key, value] of Object.entries({ ...this.env, ...options.env })) {\n args.push(\"-e\", `${key}=${value}`);\n }\n\n args.push(this.containerName, options.command, ...(options.args ?? []));\n\n const cliOptions = {\n dockerPath: this.dockerPath,\n timeoutMs: options.timeoutMs ?? this.limits.timeoutMs ?? defaultTimeoutMs,\n maxOutputBytes: this.limits.maxOutputBytes ?? defaultMaxOutputBytes,\n ...(options.input === undefined ? {} : { input: options.input }),\n ...(options.signal === undefined ? {} : { signal: options.signal }),\n ...(options.onStdout === undefined ? {} : { onStdout: options.onStdout }),\n ...(options.onStderr === undefined ? {} : { onStderr: options.onStderr }),\n };\n\n const result = await runDockerCli(args, cliOptions);\n\n if (result.timedOut) {\n return {\n ...result,\n exitCode: result.exitCode === 0 ? 124 : result.exitCode,\n };\n }\n\n return result;\n }\n\n async readFile(filePath: string): Promise<Uint8Array> {\n this.assertActive();\n const normalized = normalizeSandboxPath(filePath);\n const tempDir = await mkdtemp(path.join(os.tmpdir(), \"anvia-sandbox-read-\"));\n const target = path.join(tempDir, path.basename(normalized));\n\n try {\n await assertDockerCli(\n [\"cp\", `${this.containerName}:${containerPath(this.workdir, normalized)}`, target],\n this.cliOptions(),\n );\n const { readFile } = await import(\"node:fs/promises\");\n return await readFile(target);\n } finally {\n await rm(tempDir, { recursive: true, force: true });\n }\n }\n\n async readTextFile(filePath: string): Promise<string> {\n const bytes = await this.readFile(filePath);\n return new TextDecoder().decode(bytes);\n }\n\n async writeFile(filePath: string, data: string | Uint8Array): Promise<void> {\n this.assertActive();\n const normalized = normalizeSandboxPath(filePath);\n await this.mkdir(parentSandboxPath(normalized));\n\n const tempDir = await mkdtemp(path.join(os.tmpdir(), \"anvia-sandbox-write-\"));\n const source = path.join(tempDir, path.basename(normalized));\n\n try {\n await writeFile(source, data);\n await assertDockerCli(\n [\"cp\", source, `${this.containerName}:${containerPath(this.workdir, normalized)}`],\n this.cliOptions(),\n );\n } finally {\n await rm(tempDir, { recursive: true, force: true });\n }\n }\n\n async writeTextFile(filePath: string, content: string): Promise<void> {\n await this.writeFile(filePath, content);\n }\n\n async listFiles(filePath = \".\"): Promise<SandboxFileEntry[]> {\n this.assertActive();\n const normalized = normalizeSandboxPath(filePath, { allowRoot: true });\n const target = containerPath(this.workdir, normalized);\n const result = await this.exec({\n command: \"find\",\n args: [target, \"-mindepth\", \"1\", \"-maxdepth\", \"1\", \"-printf\", \"%p\\t%y\\t%s\\n\"],\n });\n\n if (result.timedOut) {\n throw new SandboxTimeoutError(`Listing files timed out for ${filePath}.`);\n }\n\n if (result.exitCode !== 0) {\n throw new SandboxDockerCommandError(`Unable to list sandbox path: ${filePath}`, result);\n }\n\n return result.stdout\n .split(\"\\n\")\n .filter((line) => line.trim().length > 0)\n .map((line) => this.parseFindEntry(line));\n }\n\n async destroy(): Promise<void> {\n if (this.destroyed) {\n return;\n }\n\n this.destroyed = true;\n await runDockerCli([\"rm\", \"-f\", this.containerName], this.cliOptions()).catch(() => undefined);\n await runDockerCli([\"volume\", \"rm\", \"-f\", this.volumeName], this.cliOptions()).catch(\n () => undefined,\n );\n }\n\n private async mkdir(directoryPath: string): Promise<void> {\n const normalized = normalizeSandboxPath(directoryPath, { allowRoot: true });\n const result = await this.exec({\n command: \"mkdir\",\n args: [\"-p\", containerPath(this.workdir, normalized)],\n });\n\n if (result.exitCode !== 0) {\n throw new SandboxDockerCommandError(\n `Unable to create sandbox directory: ${directoryPath}`,\n result,\n );\n }\n }\n\n private parseFindEntry(line: string): SandboxFileEntry {\n const [rawPath, rawType, rawSize] = line.split(\"\\t\");\n const absolutePath = rawPath ?? \"\";\n const relativePath = absolutePath.startsWith(`${this.workdir}/`)\n ? absolutePath.slice(this.workdir.length + 1)\n : absolutePath;\n const size = rawSize === undefined ? undefined : Number(rawSize);\n const entry: SandboxFileEntry = {\n path: relativePath,\n type: mapFindType(rawType),\n };\n\n if (size !== undefined && Number.isFinite(size)) {\n entry.size = size;\n }\n\n return entry;\n }\n\n private cliOptions() {\n return {\n dockerPath: this.dockerPath,\n maxOutputBytes: this.limits.maxOutputBytes ?? defaultMaxOutputBytes,\n };\n }\n\n private assertActive(): void {\n if (this.destroyed) {\n throw new SandboxSessionDestroyedError(`Sandbox session ${this.id} has been destroyed.`);\n }\n }\n}\n\nfunction sanitizeResourceId(id: string): string {\n const sanitized = id\n .toLowerCase()\n .replaceAll(/[^a-z0-9_.-]/g, \"-\")\n .replaceAll(/^-+|-+$/g, \"\");\n return sanitized.length > 0 ? sanitized : randomUUID();\n}\n\nfunction mapFindType(type: string | undefined): SandboxFileType {\n if (type === \"f\") {\n return \"file\";\n }\n if (type === \"d\") {\n return \"directory\";\n }\n if (type === \"l\") {\n return \"symlink\";\n }\n return \"other\";\n}\n","import { spawn } from \"node:child_process\";\nimport { SandboxDockerCommandError, SandboxDockerUnavailableError } from \"./errors\";\n\nexport interface DockerCliResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n durationMs: number;\n timedOut: boolean;\n aborted: boolean;\n stdoutTruncated: boolean;\n stderrTruncated: boolean;\n}\n\nexport interface DockerCliOptions {\n dockerPath: string;\n timeoutMs?: number;\n maxOutputBytes?: number;\n input?: string | Uint8Array;\n signal?: AbortSignal;\n onStdout?: (chunk: Uint8Array) => void;\n onStderr?: (chunk: Uint8Array) => void;\n}\n\nconst defaultMaxOutputBytes = 1024 * 1024;\n\nexport async function runDockerCli(\n args: string[],\n options: DockerCliOptions,\n): Promise<DockerCliResult> {\n const startedAt = Date.now();\n const maxOutputBytes = options.maxOutputBytes ?? defaultMaxOutputBytes;\n const stdout = createOutputCollector(maxOutputBytes, options.onStdout);\n const stderr = createOutputCollector(maxOutputBytes, options.onStderr);\n\n return new Promise((resolve, reject) => {\n const child = spawn(options.dockerPath, args, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n let timedOut = false;\n let aborted = false;\n let settled = false;\n\n const timeout =\n options.timeoutMs === undefined\n ? undefined\n : setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGKILL\");\n }, options.timeoutMs);\n\n const abort = () => {\n aborted = true;\n child.kill(\"SIGKILL\");\n };\n\n if (options.signal?.aborted === true) {\n abort();\n } else {\n options.signal?.addEventListener(\"abort\", abort, { once: true });\n }\n\n child.stdout.on(\"data\", (chunk: Buffer) => stdout.accept(chunk));\n child.stderr.on(\"data\", (chunk: Buffer) => stderr.accept(chunk));\n\n child.on(\"error\", (error) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n options.signal?.removeEventListener(\"abort\", abort);\n\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n reject(new SandboxDockerUnavailableError(\"Docker CLI was not found.\", error));\n return;\n }\n\n reject(error);\n });\n\n child.on(\"close\", (code) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timeout);\n options.signal?.removeEventListener(\"abort\", abort);\n\n resolve({\n stdout: stdout.text(),\n stderr: stderr.text(),\n exitCode: code ?? 1,\n durationMs: Date.now() - startedAt,\n timedOut,\n aborted,\n stdoutTruncated: stdout.truncated,\n stderrTruncated: stderr.truncated,\n });\n });\n\n if (options.input !== undefined) {\n child.stdin.end(options.input);\n } else {\n child.stdin.end();\n }\n });\n}\n\nexport async function assertDockerCli(args: string[], options: DockerCliOptions): Promise<string> {\n const result = await runDockerCli(args, options);\n\n if (result.exitCode !== 0) {\n throw new SandboxDockerCommandError(`Docker command failed: docker ${args.join(\" \")}`, result);\n }\n\n return result.stdout.trim();\n}\n\nfunction createOutputCollector(maxBytes: number, onChunk?: (chunk: Uint8Array) => void) {\n const chunks: Buffer[] = [];\n let length = 0;\n let truncated = false;\n\n return {\n get truncated() {\n return truncated;\n },\n accept(chunk: Buffer) {\n onChunk?.(chunk);\n\n if (length >= maxBytes) {\n truncated = true;\n return;\n }\n\n const remaining = maxBytes - length;\n const next = chunk.length > remaining ? chunk.subarray(0, remaining) : chunk;\n chunks.push(next);\n length += next.length;\n\n if (next.length < chunk.length) {\n truncated = true;\n }\n },\n text() {\n return Buffer.concat(chunks, length).toString(\"utf8\");\n },\n };\n}\n","export class SandboxError extends Error {\n constructor(\n message: string,\n readonly cause?: unknown,\n ) {\n super(message);\n this.name = new.target.name;\n }\n}\n\nexport class SandboxDockerUnavailableError extends SandboxError {}\n\nexport class SandboxDockerCommandError extends SandboxError {\n constructor(\n message: string,\n readonly result: {\n stdout: string;\n stderr: string;\n exitCode: number;\n },\n ) {\n super(message);\n }\n}\n\nexport class SandboxSessionDestroyedError extends SandboxError {}\n\nexport class SandboxPathError extends SandboxError {}\n\nexport class SandboxTimeoutError extends SandboxError {}\n","import path from \"node:path\";\nimport { SandboxPathError } from \"./errors\";\n\nexport function normalizeSandboxPath(input: string, options: { allowRoot?: boolean } = {}): string {\n if (input.length === 0) {\n throw new SandboxPathError(\"Sandbox path cannot be empty.\");\n }\n\n if (input.includes(\"\\0\")) {\n throw new SandboxPathError(\"Sandbox path cannot contain null bytes.\");\n }\n\n const normalized = path.posix.normalize(input.replaceAll(\"\\\\\", \"/\"));\n\n if (path.posix.isAbsolute(normalized)) {\n throw new SandboxPathError(`Sandbox path must be relative: ${input}`);\n }\n\n if (normalized === \"..\" || normalized.startsWith(\"../\")) {\n throw new SandboxPathError(`Sandbox path cannot leave the workspace: ${input}`);\n }\n\n if (normalized === \".\" && options.allowRoot !== true) {\n throw new SandboxPathError(\n \"Sandbox path must refer to a file or directory inside the workspace.\",\n );\n }\n\n return normalized;\n}\n\nexport function containerPath(workdir: string, relativePath: string): string {\n const normalizedWorkdir = path.posix.normalize(workdir);\n const normalizedPath = normalizeSandboxPath(relativePath, { allowRoot: true });\n return normalizedPath === \".\"\n ? normalizedWorkdir\n : path.posix.join(normalizedWorkdir, normalizedPath);\n}\n\nexport function parentSandboxPath(relativePath: string): string {\n const normalized = normalizeSandboxPath(relativePath);\n const parent = path.posix.dirname(normalized);\n return parent === \".\" ? \".\" : parent;\n}\n","import { type AnyTool, createTool } from \"@anvia/core/tool\";\nimport { z } from \"zod\";\nimport type {\n SandboxExecOptions,\n SandboxExecResult,\n SandboxSession,\n SandboxToolName,\n SandboxToolsOptions,\n} from \"./types\";\n\nconst execCommandInput = z.object({\n command: z.string().min(1).describe(\"Executable to run inside the sandbox workspace.\"),\n args: z.array(z.string()).optional().describe(\"Command arguments.\"),\n cwd: z.string().optional().describe(\"Relative working directory inside the sandbox.\"),\n env: z\n .record(z.string(), z.string())\n .optional()\n .describe(\"Environment variables for this command.\"),\n timeoutMs: z\n .number()\n .int()\n .positive()\n .max(300_000)\n .optional()\n .describe(\"Optional command timeout in milliseconds.\"),\n input: z.string().optional().describe(\"Optional stdin text to pass to the command.\"),\n});\n\nconst readFileInput = z.object({\n path: z.string().min(1).describe(\"Relative file path inside the sandbox.\"),\n});\n\nconst writeFileInput = z.object({\n path: z.string().min(1).describe(\"Relative file path inside the sandbox.\"),\n content: z.string().describe(\"Complete text content to write.\"),\n});\n\nconst listFilesInput = z.object({\n path: z\n .string()\n .optional()\n .describe(\"Relative directory path inside the sandbox. Defaults to root.\"),\n});\n\nconst textOutput = z.string();\n\nexport function createSandboxTools(\n session: SandboxSession,\n options: SandboxToolsOptions = {},\n): AnyTool[] {\n const include = new Set<SandboxToolName>(\n options.include ?? [\"exec_command\", \"read_file\", \"write_file\", \"list_files\"],\n );\n const tools: AnyTool[] = [];\n\n if (include.has(\"exec_command\")) {\n tools.push(createExecCommandTool(session, options));\n }\n\n if (include.has(\"read_file\")) {\n tools.push(createReadFileTool(session));\n }\n\n if (include.has(\"write_file\")) {\n tools.push(createWriteFileTool(session));\n }\n\n if (include.has(\"list_files\")) {\n tools.push(createListFilesTool(session));\n }\n\n return tools;\n}\n\nfunction createExecCommandTool(session: SandboxSession, options: SandboxToolsOptions): AnyTool {\n return createTool({\n name: \"exec_command\",\n description:\n \"Run a command inside the sandbox workspace. Use structured args instead of shell quoting.\",\n input: execCommandInput,\n output: textOutput,\n execute: async ({ command, args, cwd, env, timeoutMs, input }) => {\n const execOptions: SandboxExecOptions = {\n command,\n };\n\n if (args !== undefined) {\n execOptions.args = args;\n }\n if (cwd !== undefined) {\n execOptions.cwd = cwd;\n }\n if (env !== undefined) {\n execOptions.env = env;\n }\n const effectiveTimeoutMs = timeoutMs ?? options.execTimeoutMs;\n if (effectiveTimeoutMs !== undefined) {\n execOptions.timeoutMs = effectiveTimeoutMs;\n }\n if (input !== undefined) {\n execOptions.input = input;\n }\n\n const result = await session.exec(execOptions);\n\n return formatExecResult(result);\n },\n });\n}\n\nfunction createReadFileTool(session: SandboxSession): AnyTool {\n return createTool({\n name: \"read_file\",\n description: \"Read a text file from the sandbox workspace.\",\n input: readFileInput,\n output: textOutput,\n execute: async ({ path }) => session.readTextFile(path),\n });\n}\n\nfunction createWriteFileTool(session: SandboxSession): AnyTool {\n return createTool({\n name: \"write_file\",\n description: \"Write a text file inside the sandbox workspace. Creates parent directories.\",\n input: writeFileInput,\n output: textOutput,\n execute: async ({ path, content }) => {\n await session.writeTextFile(path, content);\n return `Wrote ${path}`;\n },\n });\n}\n\nfunction createListFilesTool(session: SandboxSession): AnyTool {\n return createTool({\n name: \"list_files\",\n description: \"List files and directories inside the sandbox workspace.\",\n input: listFilesInput,\n output: textOutput,\n execute: async ({ path }) => {\n const entries = await session.listFiles(path);\n\n if (entries.length === 0) {\n return \"No files found.\";\n }\n\n return entries\n .map((entry) => {\n const size = entry.size === undefined ? \"\" : ` ${entry.size}b`;\n return `${entry.type}${size}\\t${entry.path}`;\n })\n .join(\"\\n\");\n },\n });\n}\n\nfunction formatExecResult(result: SandboxExecResult): string {\n const parts = [`exit_code: ${result.exitCode}`];\n\n if (result.timedOut) {\n parts.push(\"timed_out: true\");\n }\n\n if (result.aborted) {\n parts.push(\"aborted: true\");\n }\n\n if (result.stdout.length > 0) {\n parts.push(`stdout:\\n${result.stdout.trimEnd()}`);\n }\n\n if (result.stderr.length > 0) {\n parts.push(`stderr:\\n${result.stderr.trimEnd()}`);\n }\n\n if (result.stdoutTruncated || result.stderrTruncated) {\n parts.push(\"output_truncated: true\");\n }\n\n return parts.join(\"\\n\\n\");\n}\n"],"mappings":";AAAA,SAAS,kBAAkB;AAC3B,SAAS,SAAS,IAAI,iBAAiB;AACvC,OAAO,QAAQ;AACf,OAAOA,WAAU;;;ACHjB,SAAS,aAAa;;;ACAf,IAAM,eAAN,cAA2B,MAAM;AAAA,EACtC,YACE,SACS,OACT;AACA,UAAM,OAAO;AAFJ;AAGT,SAAK,OAAO,WAAW;AAAA,EACzB;AAAA,EAJW;AAKb;AAEO,IAAM,gCAAN,cAA4C,aAAa;AAAC;AAE1D,IAAM,4BAAN,cAAwC,aAAa;AAAA,EAC1D,YACE,SACS,QAKT;AACA,UAAM,OAAO;AANJ;AAAA,EAOX;AAAA,EAPW;AAQb;AAEO,IAAM,+BAAN,cAA2C,aAAa;AAAC;AAEzD,IAAM,mBAAN,cAA+B,aAAa;AAAC;AAE7C,IAAM,sBAAN,cAAkC,aAAa;AAAC;;;ADLvD,IAAM,wBAAwB,OAAO;AAErC,eAAsB,aACpB,MACA,SAC0B;AAC1B,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,iBAAiB,QAAQ,kBAAkB;AACjD,QAAM,SAAS,sBAAsB,gBAAgB,QAAQ,QAAQ;AACrE,QAAM,SAAS,sBAAsB,gBAAgB,QAAQ,QAAQ;AAErE,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,QAAQ,MAAM,QAAQ,YAAY,MAAM;AAAA,MAC5C,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AAED,QAAI,WAAW;AACf,QAAI,UAAU;AACd,QAAI,UAAU;AAEd,UAAM,UACJ,QAAQ,cAAc,SAClB,SACA,WAAW,MAAM;AACf,iBAAW;AACX,YAAM,KAAK,SAAS;AAAA,IACtB,GAAG,QAAQ,SAAS;AAE1B,UAAM,QAAQ,MAAM;AAClB,gBAAU;AACV,YAAM,KAAK,SAAS;AAAA,IACtB;AAEA,QAAI,QAAQ,QAAQ,YAAY,MAAM;AACpC,YAAM;AAAA,IACR,OAAO;AACL,cAAQ,QAAQ,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;AAAA,IACjE;AAEA,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAkB,OAAO,OAAO,KAAK,CAAC;AAC/D,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAkB,OAAO,OAAO,KAAK,CAAC;AAE/D,UAAM,GAAG,SAAS,CAAC,UAAU;AAC3B,UAAI,SAAS;AACX;AAAA,MACF;AACA,gBAAU;AACV,mBAAa,OAAO;AACpB,cAAQ,QAAQ,oBAAoB,SAAS,KAAK;AAElD,UAAK,MAAgC,SAAS,UAAU;AACtD,eAAO,IAAI,8BAA8B,6BAA6B,KAAK,CAAC;AAC5E;AAAA,MACF;AAEA,aAAO,KAAK;AAAA,IACd,CAAC;AAED,UAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,UAAI,SAAS;AACX;AAAA,MACF;AACA,gBAAU;AACV,mBAAa,OAAO;AACpB,cAAQ,QAAQ,oBAAoB,SAAS,KAAK;AAElD,cAAQ;AAAA,QACN,QAAQ,OAAO,KAAK;AAAA,QACpB,QAAQ,OAAO,KAAK;AAAA,QACpB,UAAU,QAAQ;AAAA,QAClB,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB;AAAA,QACA;AAAA,QACA,iBAAiB,OAAO;AAAA,QACxB,iBAAiB,OAAO;AAAA,MAC1B,CAAC;AAAA,IACH,CAAC;AAED,QAAI,QAAQ,UAAU,QAAW;AAC/B,YAAM,MAAM,IAAI,QAAQ,KAAK;AAAA,IAC/B,OAAO;AACL,YAAM,MAAM,IAAI;AAAA,IAClB;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,gBAAgB,MAAgB,SAA4C;AAChG,QAAM,SAAS,MAAM,aAAa,MAAM,OAAO;AAE/C,MAAI,OAAO,aAAa,GAAG;AACzB,UAAM,IAAI,0BAA0B,iCAAiC,KAAK,KAAK,GAAG,CAAC,IAAI,MAAM;AAAA,EAC/F;AAEA,SAAO,OAAO,OAAO,KAAK;AAC5B;AAEA,SAAS,sBAAsB,UAAkB,SAAuC;AACtF,QAAM,SAAmB,CAAC;AAC1B,MAAI,SAAS;AACb,MAAI,YAAY;AAEhB,SAAO;AAAA,IACL,IAAI,YAAY;AACd,aAAO;AAAA,IACT;AAAA,IACA,OAAO,OAAe;AACpB,gBAAU,KAAK;AAEf,UAAI,UAAU,UAAU;AACtB,oBAAY;AACZ;AAAA,MACF;AAEA,YAAM,YAAY,WAAW;AAC7B,YAAM,OAAO,MAAM,SAAS,YAAY,MAAM,SAAS,GAAG,SAAS,IAAI;AACvE,aAAO,KAAK,IAAI;AAChB,gBAAU,KAAK;AAEf,UAAI,KAAK,SAAS,MAAM,QAAQ;AAC9B,oBAAY;AAAA,MACd;AAAA,IACF;AAAA,IACA,OAAO;AACL,aAAO,OAAO,OAAO,QAAQ,MAAM,EAAE,SAAS,MAAM;AAAA,IACtD;AAAA,EACF;AACF;;;AEtJA,OAAO,UAAU;AAGV,SAAS,qBAAqB,OAAe,UAAmC,CAAC,GAAW;AACjG,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,iBAAiB,+BAA+B;AAAA,EAC5D;AAEA,MAAI,MAAM,SAAS,IAAI,GAAG;AACxB,UAAM,IAAI,iBAAiB,yCAAyC;AAAA,EACtE;AAEA,QAAM,aAAa,KAAK,MAAM,UAAU,MAAM,WAAW,MAAM,GAAG,CAAC;AAEnE,MAAI,KAAK,MAAM,WAAW,UAAU,GAAG;AACrC,UAAM,IAAI,iBAAiB,kCAAkC,KAAK,EAAE;AAAA,EACtE;AAEA,MAAI,eAAe,QAAQ,WAAW,WAAW,KAAK,GAAG;AACvD,UAAM,IAAI,iBAAiB,4CAA4C,KAAK,EAAE;AAAA,EAChF;AAEA,MAAI,eAAe,OAAO,QAAQ,cAAc,MAAM;AACpD,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,cAAc,SAAiB,cAA8B;AAC3E,QAAM,oBAAoB,KAAK,MAAM,UAAU,OAAO;AACtD,QAAM,iBAAiB,qBAAqB,cAAc,EAAE,WAAW,KAAK,CAAC;AAC7E,SAAO,mBAAmB,MACtB,oBACA,KAAK,MAAM,KAAK,mBAAmB,cAAc;AACvD;AAEO,SAAS,kBAAkB,cAA8B;AAC9D,QAAM,aAAa,qBAAqB,YAAY;AACpD,QAAM,SAAS,KAAK,MAAM,QAAQ,UAAU;AAC5C,SAAO,WAAW,MAAM,MAAM;AAChC;;;AHlBA,IAAM,eAAe;AACrB,IAAM,iBAAiB;AACvB,IAAM,mBAAmB;AACzB,IAAMC,yBAAwB,OAAO;AAE9B,IAAM,gBAAN,MAAuC;AAAA,EACnC,WAAW;AAAA,EAEH;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,UAAgC,CAAC,GAAG;AAC9C,SAAK,QAAQ,QAAQ,SAAS;AAC9B,SAAK,OAAO,QAAQ,QAAQ;AAC5B,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,SAAS,QAAQ,UAAU,CAAC;AACjC,SAAK,SAAS,QAAQ,UAAU,CAAC;AACjC,SAAK,WAAW;AAAA,MACd,gBAAgB,QAAQ,UAAU,kBAAkB;AAAA,MACpD,iBAAiB,QAAQ,UAAU,mBAAmB;AAAA,MACtD,kBAAkB,QAAQ,UAAU,oBAAoB,CAAC,KAAK;AAAA,IAChE;AACA,SAAK,OAAO,QAAQ;AAAA,EACtB;AAAA,EAEA,MAAM,cAAc,UAAuC,CAAC,GAA4B;AACtF,UAAM,KAAK,YAAY;AAEvB,UAAM,KAAK,mBAAmB,QAAQ,MAAM,WAAW,CAAC;AACxD,UAAM,gBAAgB,iBAAiB,EAAE;AACzC,UAAM,aAAa,iBAAiB,EAAE;AAEtC,UAAM,gBAAgB,CAAC,UAAU,UAAU,UAAU,GAAG,KAAK,WAAW,CAAC;AAEzE,QAAI;AACF,YAAM,gBAAgB,KAAK,cAAc,eAAe,YAAY,QAAQ,QAAQ,GAAG;AAAA,QACrF,GAAG,KAAK,WAAW;AAAA,QACnB,WAAW,KAAK,OAAO,aAAa;AAAA,MACtC,CAAC;AAED,YAAM,UAAU,IAAI,qBAAqB;AAAA,QACvC;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,KAAK;AAAA,QACd,YAAY,KAAK;AAAA,QACjB,QAAQ,KAAK;AAAA,QACb,KAAK,QAAQ,UAAU,OAAO,CAAC;AAAA,MACjC,CAAC;AAED,YAAM,QAAQ,cAAc,QAAQ,QAAQ;AAC5C,aAAO;AAAA,IACT,SAAS,OAAO;AACd,YAAM,KAAK,QAAQ,eAAe,UAAU;AAC5C,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc,cAA6B;AACzC,QAAI,KAAK,SAAS,UAAU;AAC1B,YAAM,gBAAgB,CAAC,QAAQ,KAAK,KAAK,GAAG,KAAK,WAAW,CAAC;AAC7D;AAAA,IACF;AAEA,QAAI,KAAK,SAAS,WAAW;AAC3B,YAAM,UAAU,MAAM,aAAa,CAAC,SAAS,WAAW,KAAK,KAAK,GAAG,KAAK,WAAW,CAAC;AACtF,UAAI,QAAQ,aAAa,GAAG;AAC1B,cAAM,gBAAgB,CAAC,QAAQ,KAAK,KAAK,GAAG,KAAK,WAAW,CAAC;AAAA,MAC/D;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,cACN,eACA,YACA,UACU;AACV,UAAM,OAAO;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG,UAAU,IAAI,KAAK,OAAO;AAAA,MAC7B;AAAA,MACA,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAEA,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,MAAM,GAAG;AACtD,WAAK,KAAK,WAAW,GAAG,GAAG,IAAI,KAAK,EAAE;AAAA,IACxC;AAEA,QAAI,aAAa,QAAW;AAC1B,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACnD,aAAK,KAAK,WAAW,0BAA0B,GAAG,IAAI,KAAK,EAAE;AAAA,MAC/D;AAAA,IACF;AAEA,SAAK,kBAAkB,IAAI;AAC3B,SAAK,gBAAgB,IAAI;AACzB,SAAK,mBAAmB,IAAI;AAE5B,QAAI,KAAK,SAAS,QAAW;AAC3B,WAAK,KAAK,MAAM,KAAK,IAAI;AAAA,IAC3B;AAEA,SAAK;AAAA,MACH,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,kBAAkB,MAAsB;AAC9C,QAAI,KAAK,YAAY,SAAS,KAAK,YAAY,QAAQ;AACrD,WAAK,KAAK,aAAa,MAAM;AAC7B;AAAA,IACF;AAEA,QAAI,KAAK,YAAY,MAAM;AACzB,WAAK,KAAK,aAAa,KAAK,OAAO;AAAA,IACrC;AAAA,EACF;AAAA,EAEQ,gBAAgB,MAAsB;AAC5C,QAAI,KAAK,OAAO,aAAa,QAAW;AACtC,WAAK,KAAK,YAAY,GAAG,KAAK,OAAO,QAAQ,GAAG;AAAA,IAClD;AAEA,QAAI,KAAK,OAAO,SAAS,QAAW;AAClC,WAAK,KAAK,UAAU,OAAO,KAAK,OAAO,IAAI,CAAC;AAAA,IAC9C;AAEA,QAAI,KAAK,OAAO,cAAc,QAAW;AACvC,WAAK,KAAK,gBAAgB,OAAO,KAAK,OAAO,SAAS,CAAC;AAAA,IACzD;AAAA,EACF;AAAA,EAEQ,mBAAmB,MAAsB;AAC/C,QAAI,KAAK,SAAS,gBAAgB;AAChC,WAAK,KAAK,aAAa;AAAA,IACzB;AAEA,QAAI,KAAK,SAAS,iBAAiB;AACjC,WAAK,KAAK,kBAAkB,mBAAmB;AAAA,IACjD;AAEA,eAAW,cAAc,KAAK,SAAS,kBAAkB;AACvD,WAAK,KAAK,cAAc,UAAU;AAAA,IACpC;AAAA,EACF;AAAA,EAEQ,aAAa;AACnB,WAAO;AAAA,MACL,YAAY,KAAK;AAAA,MACjB,gBAAgB,KAAK,OAAO,kBAAkBA;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAc,QAAQ,eAAuB,YAAmC;AAC9E,UAAM,aAAa,CAAC,MAAM,MAAM,aAAa,GAAG,KAAK,WAAW,CAAC,EAAE,MAAM,MAAM,MAAS;AACxF,UAAM,aAAa,CAAC,UAAU,MAAM,MAAM,UAAU,GAAG,KAAK,WAAW,CAAC,EAAE;AAAA,MACxE,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEA,IAAM,uBAAN,MAAqD;AAAA,EAC1C,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EAEQ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAY;AAAA,EAEpB,YAAY,SAQT;AACD,SAAK,KAAK,QAAQ;AAClB,SAAK,gBAAgB,QAAQ;AAC7B,SAAK,aAAa,QAAQ;AAC1B,SAAK,UAAU,QAAQ;AACvB,SAAK,aAAa,QAAQ;AAC1B,SAAK,SAAS,QAAQ;AACtB,SAAK,MAAM,QAAQ;AAAA,EACrB;AAAA,EAEA,MAAM,cAAc,UAAsD;AACxE,SAAK,aAAa;AAElB,eAAW,aAAa,UAAU,eAAe,CAAC,GAAG;AACnD,YAAM,KAAK,MAAM,SAAS;AAAA,IAC5B;AAEA,eAAW,CAAC,UAAU,OAAO,KAAK,OAAO,QAAQ,UAAU,SAAS,CAAC,CAAC,GAAG;AACvE,YAAM,KAAK,UAAU,UAAU,OAAO;AAAA,IACxC;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,SAAyD;AAClE,SAAK,aAAa;AAElB,QAAI,QAAQ,QAAQ,KAAK,EAAE,WAAW,GAAG;AACvC,YAAM,IAAI,0BAA0B,oCAAoC;AAAA,QACtE,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAEA,UAAM,OAAO,CAAC,MAAM;AAEpB,QAAI,QAAQ,UAAU,QAAW;AAC/B,WAAK,KAAK,IAAI;AAAA,IAChB;AAEA,UAAM,MAAM,cAAc,KAAK,SAAS,QAAQ,OAAO,GAAG;AAC1D,SAAK,KAAK,MAAM,GAAG;AAEnB,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,EAAE,GAAG,KAAK,KAAK,GAAG,QAAQ,IAAI,CAAC,GAAG;AAC1E,WAAK,KAAK,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE;AAAA,IACnC;AAEA,SAAK,KAAK,KAAK,eAAe,QAAQ,SAAS,GAAI,QAAQ,QAAQ,CAAC,CAAE;AAEtE,UAAM,aAAa;AAAA,MACjB,YAAY,KAAK;AAAA,MACjB,WAAW,QAAQ,aAAa,KAAK,OAAO,aAAa;AAAA,MACzD,gBAAgB,KAAK,OAAO,kBAAkBA;AAAA,MAC9C,GAAI,QAAQ,UAAU,SAAY,CAAC,IAAI,EAAE,OAAO,QAAQ,MAAM;AAAA,MAC9D,GAAI,QAAQ,WAAW,SAAY,CAAC,IAAI,EAAE,QAAQ,QAAQ,OAAO;AAAA,MACjE,GAAI,QAAQ,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,QAAQ,SAAS;AAAA,MACvE,GAAI,QAAQ,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,QAAQ,SAAS;AAAA,IACzE;AAEA,UAAM,SAAS,MAAM,aAAa,MAAM,UAAU;AAElD,QAAI,OAAO,UAAU;AACnB,aAAO;AAAA,QACL,GAAG;AAAA,QACH,UAAU,OAAO,aAAa,IAAI,MAAM,OAAO;AAAA,MACjD;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,SAAS,UAAuC;AACpD,SAAK,aAAa;AAClB,UAAM,aAAa,qBAAqB,QAAQ;AAChD,UAAM,UAAU,MAAM,QAAQC,MAAK,KAAK,GAAG,OAAO,GAAG,qBAAqB,CAAC;AAC3E,UAAM,SAASA,MAAK,KAAK,SAASA,MAAK,SAAS,UAAU,CAAC;AAE3D,QAAI;AACF,YAAM;AAAA,QACJ,CAAC,MAAM,GAAG,KAAK,aAAa,IAAI,cAAc,KAAK,SAAS,UAAU,CAAC,IAAI,MAAM;AAAA,QACjF,KAAK,WAAW;AAAA,MAClB;AACA,YAAM,EAAE,SAAS,IAAI,MAAM,OAAO,aAAkB;AACpD,aAAO,MAAM,SAAS,MAAM;AAAA,IAC9B,UAAE;AACA,YAAM,GAAG,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IACpD;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,UAAmC;AACpD,UAAM,QAAQ,MAAM,KAAK,SAAS,QAAQ;AAC1C,WAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AAAA,EACvC;AAAA,EAEA,MAAM,UAAU,UAAkB,MAA0C;AAC1E,SAAK,aAAa;AAClB,UAAM,aAAa,qBAAqB,QAAQ;AAChD,UAAM,KAAK,MAAM,kBAAkB,UAAU,CAAC;AAE9C,UAAM,UAAU,MAAM,QAAQA,MAAK,KAAK,GAAG,OAAO,GAAG,sBAAsB,CAAC;AAC5E,UAAM,SAASA,MAAK,KAAK,SAASA,MAAK,SAAS,UAAU,CAAC;AAE3D,QAAI;AACF,YAAM,UAAU,QAAQ,IAAI;AAC5B,YAAM;AAAA,QACJ,CAAC,MAAM,QAAQ,GAAG,KAAK,aAAa,IAAI,cAAc,KAAK,SAAS,UAAU,CAAC,EAAE;AAAA,QACjF,KAAK,WAAW;AAAA,MAClB;AAAA,IACF,UAAE;AACA,YAAM,GAAG,SAAS,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IACpD;AAAA,EACF;AAAA,EAEA,MAAM,cAAc,UAAkB,SAAgC;AACpE,UAAM,KAAK,UAAU,UAAU,OAAO;AAAA,EACxC;AAAA,EAEA,MAAM,UAAU,WAAW,KAAkC;AAC3D,SAAK,aAAa;AAClB,UAAM,aAAa,qBAAqB,UAAU,EAAE,WAAW,KAAK,CAAC;AACrE,UAAM,SAAS,cAAc,KAAK,SAAS,UAAU;AACrD,UAAM,SAAS,MAAM,KAAK,KAAK;AAAA,MAC7B,SAAS;AAAA,MACT,MAAM,CAAC,QAAQ,aAAa,KAAK,aAAa,KAAK,WAAW,YAAc;AAAA,IAC9E,CAAC;AAED,QAAI,OAAO,UAAU;AACnB,YAAM,IAAI,oBAAoB,+BAA+B,QAAQ,GAAG;AAAA,IAC1E;AAEA,QAAI,OAAO,aAAa,GAAG;AACzB,YAAM,IAAI,0BAA0B,gCAAgC,QAAQ,IAAI,MAAM;AAAA,IACxF;AAEA,WAAO,OAAO,OACX,MAAM,IAAI,EACV,OAAO,CAAC,SAAS,KAAK,KAAK,EAAE,SAAS,CAAC,EACvC,IAAI,CAAC,SAAS,KAAK,eAAe,IAAI,CAAC;AAAA,EAC5C;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,WAAW;AAClB;AAAA,IACF;AAEA,SAAK,YAAY;AACjB,UAAM,aAAa,CAAC,MAAM,MAAM,KAAK,aAAa,GAAG,KAAK,WAAW,CAAC,EAAE,MAAM,MAAM,MAAS;AAC7F,UAAM,aAAa,CAAC,UAAU,MAAM,MAAM,KAAK,UAAU,GAAG,KAAK,WAAW,CAAC,EAAE;AAAA,MAC7E,MAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc,MAAM,eAAsC;AACxD,UAAM,aAAa,qBAAqB,eAAe,EAAE,WAAW,KAAK,CAAC;AAC1E,UAAM,SAAS,MAAM,KAAK,KAAK;AAAA,MAC7B,SAAS;AAAA,MACT,MAAM,CAAC,MAAM,cAAc,KAAK,SAAS,UAAU,CAAC;AAAA,IACtD,CAAC;AAED,QAAI,OAAO,aAAa,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,uCAAuC,aAAa;AAAA,QACpD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,eAAe,MAAgC;AACrD,UAAM,CAAC,SAAS,SAAS,OAAO,IAAI,KAAK,MAAM,GAAI;AACnD,UAAM,eAAe,WAAW;AAChC,UAAM,eAAe,aAAa,WAAW,GAAG,KAAK,OAAO,GAAG,IAC3D,aAAa,MAAM,KAAK,QAAQ,SAAS,CAAC,IAC1C;AACJ,UAAM,OAAO,YAAY,SAAY,SAAY,OAAO,OAAO;AAC/D,UAAM,QAA0B;AAAA,MAC9B,MAAM;AAAA,MACN,MAAM,YAAY,OAAO;AAAA,IAC3B;AAEA,QAAI,SAAS,UAAa,OAAO,SAAS,IAAI,GAAG;AAC/C,YAAM,OAAO;AAAA,IACf;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,aAAa;AACnB,WAAO;AAAA,MACL,YAAY,KAAK;AAAA,MACjB,gBAAgB,KAAK,OAAO,kBAAkBD;AAAA,IAChD;AAAA,EACF;AAAA,EAEQ,eAAqB;AAC3B,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI,6BAA6B,mBAAmB,KAAK,EAAE,sBAAsB;AAAA,IACzF;AAAA,EACF;AACF;AAEA,SAAS,mBAAmB,IAAoB;AAC9C,QAAM,YAAY,GACf,YAAY,EACZ,WAAW,iBAAiB,GAAG,EAC/B,WAAW,YAAY,EAAE;AAC5B,SAAO,UAAU,SAAS,IAAI,YAAY,WAAW;AACvD;AAEA,SAAS,YAAY,MAA2C;AAC9D,MAAI,SAAS,KAAK;AAChB,WAAO;AAAA,EACT;AACA,MAAI,SAAS,KAAK;AAChB,WAAO;AAAA,EACT;AACA,MAAI,SAAS,KAAK;AAChB,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;AI3bA,SAAuB,kBAAkB;AACzC,SAAS,SAAS;AASlB,IAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,iDAAiD;AAAA,EACrF,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,SAAS,oBAAoB;AAAA,EAClE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,gDAAgD;AAAA,EACpF,KAAK,EACF,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,EAC7B,SAAS,EACT,SAAS,yCAAyC;AAAA,EACrD,WAAW,EACR,OAAO,EACP,IAAI,EACJ,SAAS,EACT,IAAI,GAAO,EACX,SAAS,EACT,SAAS,2CAA2C;AAAA,EACvD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,6CAA6C;AACrF,CAAC;AAED,IAAM,gBAAgB,EAAE,OAAO;AAAA,EAC7B,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,wCAAwC;AAC3E,CAAC;AAED,IAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,wCAAwC;AAAA,EACzE,SAAS,EAAE,OAAO,EAAE,SAAS,iCAAiC;AAChE,CAAC;AAED,IAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,MAAM,EACH,OAAO,EACP,SAAS,EACT,SAAS,+DAA+D;AAC7E,CAAC;AAED,IAAM,aAAa,EAAE,OAAO;AAErB,SAAS,mBACd,SACA,UAA+B,CAAC,GACrB;AACX,QAAM,UAAU,IAAI;AAAA,IAClB,QAAQ,WAAW,CAAC,gBAAgB,aAAa,cAAc,YAAY;AAAA,EAC7E;AACA,QAAM,QAAmB,CAAC;AAE1B,MAAI,QAAQ,IAAI,cAAc,GAAG;AAC/B,UAAM,KAAK,sBAAsB,SAAS,OAAO,CAAC;AAAA,EACpD;AAEA,MAAI,QAAQ,IAAI,WAAW,GAAG;AAC5B,UAAM,KAAK,mBAAmB,OAAO,CAAC;AAAA,EACxC;AAEA,MAAI,QAAQ,IAAI,YAAY,GAAG;AAC7B,UAAM,KAAK,oBAAoB,OAAO,CAAC;AAAA,EACzC;AAEA,MAAI,QAAQ,IAAI,YAAY,GAAG;AAC7B,UAAM,KAAK,oBAAoB,OAAO,CAAC;AAAA,EACzC;AAEA,SAAO;AACT;AAEA,SAAS,sBAAsB,SAAyB,SAAuC;AAC7F,SAAO,WAAW;AAAA,IAChB,MAAM;AAAA,IACN,aACE;AAAA,IACF,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS,OAAO,EAAE,SAAS,MAAM,KAAK,KAAK,WAAW,MAAM,MAAM;AAChE,YAAM,cAAkC;AAAA,QACtC;AAAA,MACF;AAEA,UAAI,SAAS,QAAW;AACtB,oBAAY,OAAO;AAAA,MACrB;AACA,UAAI,QAAQ,QAAW;AACrB,oBAAY,MAAM;AAAA,MACpB;AACA,UAAI,QAAQ,QAAW;AACrB,oBAAY,MAAM;AAAA,MACpB;AACA,YAAM,qBAAqB,aAAa,QAAQ;AAChD,UAAI,uBAAuB,QAAW;AACpC,oBAAY,YAAY;AAAA,MAC1B;AACA,UAAI,UAAU,QAAW;AACvB,oBAAY,QAAQ;AAAA,MACtB;AAEA,YAAM,SAAS,MAAM,QAAQ,KAAK,WAAW;AAE7C,aAAO,iBAAiB,MAAM;AAAA,IAChC;AAAA,EACF,CAAC;AACH;AAEA,SAAS,mBAAmB,SAAkC;AAC5D,SAAO,WAAW;AAAA,IAChB,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS,OAAO,EAAE,MAAAE,MAAK,MAAM,QAAQ,aAAaA,KAAI;AAAA,EACxD,CAAC;AACH;AAEA,SAAS,oBAAoB,SAAkC;AAC7D,SAAO,WAAW;AAAA,IAChB,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS,OAAO,EAAE,MAAAA,OAAM,QAAQ,MAAM;AACpC,YAAM,QAAQ,cAAcA,OAAM,OAAO;AACzC,aAAO,SAASA,KAAI;AAAA,IACtB;AAAA,EACF,CAAC;AACH;AAEA,SAAS,oBAAoB,SAAkC;AAC7D,SAAO,WAAW;AAAA,IAChB,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS,OAAO,EAAE,MAAAA,MAAK,MAAM;AAC3B,YAAM,UAAU,MAAM,QAAQ,UAAUA,KAAI;AAE5C,UAAI,QAAQ,WAAW,GAAG;AACxB,eAAO;AAAA,MACT;AAEA,aAAO,QACJ,IAAI,CAAC,UAAU;AACd,cAAM,OAAO,MAAM,SAAS,SAAY,KAAK,IAAI,MAAM,IAAI;AAC3D,eAAO,GAAG,MAAM,IAAI,GAAG,IAAI,IAAK,MAAM,IAAI;AAAA,MAC5C,CAAC,EACA,KAAK,IAAI;AAAA,IACd;AAAA,EACF,CAAC;AACH;AAEA,SAAS,iBAAiB,QAAmC;AAC3D,QAAM,QAAQ,CAAC,cAAc,OAAO,QAAQ,EAAE;AAE9C,MAAI,OAAO,UAAU;AACnB,UAAM,KAAK,iBAAiB;AAAA,EAC9B;AAEA,MAAI,OAAO,SAAS;AAClB,UAAM,KAAK,eAAe;AAAA,EAC5B;AAEA,MAAI,OAAO,OAAO,SAAS,GAAG;AAC5B,UAAM,KAAK;AAAA,EAAY,OAAO,OAAO,QAAQ,CAAC,EAAE;AAAA,EAClD;AAEA,MAAI,OAAO,OAAO,SAAS,GAAG;AAC5B,UAAM,KAAK;AAAA,EAAY,OAAO,OAAO,QAAQ,CAAC,EAAE;AAAA,EAClD;AAEA,MAAI,OAAO,mBAAmB,OAAO,iBAAiB;AACpD,UAAM,KAAK,wBAAwB;AAAA,EACrC;AAEA,SAAO,MAAM,KAAK,MAAM;AAC1B;","names":["path","defaultMaxOutputBytes","path","path"]}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@anvia/sandbox",
3
+ "version": "0.2.0",
4
+ "description": "Sandboxed workspace tools for Anvia agents.",
5
+ "author": "anvia",
6
+ "maintainer": "Indra Zulfi",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/anvia-hq/anvia",
11
+ "directory": "packages/tools/sandbox"
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "type": "module",
20
+ "main": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js"
26
+ }
27
+ },
28
+ "dependencies": {
29
+ "zod": "^4.4.3",
30
+ "@anvia/core": "0.5.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^24.9.1",
34
+ "tsup": "^8.5.0",
35
+ "typescript": "^5.9.3",
36
+ "vitest": "^4.0.8"
37
+ },
38
+ "scripts": {
39
+ "build": "pnpm run build:deps && tsup src/index.ts --format esm --dts --sourcemap --clean",
40
+ "build:deps": "pnpm --filter @anvia/core build",
41
+ "test": "pnpm run build:deps && vitest run",
42
+ "typecheck": "pnpm run build:deps && tsc --noEmit"
43
+ }
44
+ }