@unchartedfr/zapcode 1.0.0-beta.1 → 1.0.0-beta.3

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/README.md ADDED
@@ -0,0 +1,707 @@
1
+ <p align="center">
2
+ <h1 align="center">Zapcode</h1>
3
+ <p align="center"><strong>Run AI code. Safely. Instantly.</strong></p>
4
+ <p align="center">A minimal, secure TypeScript interpreter written in Rust for use by AI</p>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://github.com/TheUncharted/zapcode/actions"><img src="https://img.shields.io/github/actions/workflow/status/TheUncharted/zapcode/ci.yml?branch=main&label=CI" alt="CI"></a>
9
+ <a href="https://www.npmjs.com/package/@unchartedfr/zapcode"><img src="https://img.shields.io/npm/v/@unchartedfr/zapcode" alt="npm"></a>
10
+ <a href="https://pypi.org/project/zapcode/"><img src="https://img.shields.io/pypi/v/zapcode" alt="PyPI"></a>
11
+ <a href="https://github.com/TheUncharted/zapcode/blob/main/LICENSE"><img src="https://img.shields.io/github/license/TheUncharted/zapcode" alt="License"></a>
12
+ </p>
13
+
14
+ ---
15
+
16
+ > **Experimental** — Zapcode is under active development. APIs may change.
17
+
18
+ When LLMs write code, you need to run it — fast and safe. That's Zapcode.
19
+
20
+ When LLMs write TypeScript, you need to run it safely. Containers add hundreds of milliseconds of startup overhead and operational complexity. V8 isolates are fast but bring a 20MB+ runtime and a massive attack surface.
21
+
22
+ Zapcode takes a different approach: a purpose-built TypeScript interpreter that starts in **under 2 microseconds**, enforces a security sandbox at the language level, and can snapshot execution state to bytes for later resumption — all in a single, embeddable library with zero dependencies on Node.js or V8.
23
+
24
+ ## Benchmarks
25
+
26
+ All benchmarks run on the full pipeline: parse → compile → execute. No caching, no warm-up.
27
+
28
+ | Benchmark | Zapcode | Docker + Node.js | V8 Isolate |
29
+ |---|---|---|---|
30
+ | Simple expression (`1 + 2 * 3`) | **2.1 µs** | ~200-500 ms | ~5-50 ms |
31
+ | Variable arithmetic | **2.8 µs** | — | — |
32
+ | String concatenation | **2.6 µs** | — | — |
33
+ | Template literal | **2.9 µs** | — | — |
34
+ | Array creation | **2.4 µs** | — | — |
35
+ | Object creation | **5.2 µs** | — | — |
36
+ | Function call | **4.6 µs** | — | — |
37
+ | Loop (100 iterations) | **77.8 µs** | — | — |
38
+ | Fibonacci (n=10, 177 calls) | **138.4 µs** | — | — |
39
+ | Snapshot size (typical agent) | **< 2 KB** | N/A | N/A |
40
+ | Memory per execution | **~10 KB** | ~50+ MB | ~20+ MB |
41
+ | Cold start | **~2 µs** | ~200-500 ms | ~5-50 ms |
42
+
43
+ No background thread, no GC, no runtime — CPU usage is exactly proportional to the instructions executed.
44
+
45
+ Run benchmarks: `cargo bench`
46
+
47
+ ## Quick start
48
+
49
+ ### One-line install
50
+
51
+ ```bash
52
+ curl -fsSL https://raw.githubusercontent.com/TheUncharted/zapcode/master/install.sh | bash
53
+ ```
54
+
55
+ The script auto-detects your project type (TypeScript, Python, Rust, WASM), installs prerequisites, and builds native bindings. Or specify explicitly:
56
+
57
+ ```bash
58
+ curl -fsSL ... | bash -s -- --lang ts # TypeScript / JavaScript
59
+ curl -fsSL ... | bash -s -- --lang python # Python
60
+ curl -fsSL ... | bash -s -- --lang rust # Rust
61
+ curl -fsSL ... | bash -s -- --lang wasm # WebAssembly
62
+ ```
63
+
64
+ ### Install by language
65
+
66
+ <details>
67
+ <summary><strong>Rust</strong></summary>
68
+
69
+ ```toml
70
+ [dependencies]
71
+ zapcode-core = { git = "https://github.com/TheUncharted/zapcode.git" }
72
+ ```
73
+ </details>
74
+
75
+ <details>
76
+ <summary><strong>JavaScript / TypeScript (Node.js)</strong></summary>
77
+
78
+ Once published to npm (coming soon):
79
+
80
+ ```bash
81
+ npm install @unchartedfr/zapcode # npm
82
+ yarn add @unchartedfr/zapcode # yarn
83
+ pnpm add @unchartedfr/zapcode # pnpm
84
+ bun add @unchartedfr/zapcode # bun
85
+ ```
86
+
87
+ Until then, build from source — requires Rust toolchain:
88
+
89
+ ```bash
90
+ git clone https://github.com/TheUncharted/zapcode.git
91
+ cd zapcode/crates/zapcode-js
92
+ npm install && npm run build
93
+
94
+ # Link into your project
95
+ npm link # in zapcode-js/
96
+ npm link @unchartedfr/zapcode # in your project
97
+ ```
98
+ </details>
99
+
100
+ <details>
101
+ <summary><strong>Python</strong></summary>
102
+
103
+ Once published to PyPI (coming soon):
104
+
105
+ ```bash
106
+ pip install zapcode # pip
107
+ uv add zapcode # uv (Astral)
108
+ ```
109
+
110
+ Until then, build from source — requires Rust toolchain + [maturin](https://github.com/PyO3/maturin):
111
+
112
+ ```bash
113
+ # With uv (recommended)
114
+ uv tool install maturin
115
+ git clone https://github.com/TheUncharted/zapcode.git
116
+ cd zapcode/crates/zapcode-py
117
+ maturin develop --release --uv
118
+
119
+ # With pip
120
+ pip install maturin
121
+ git clone https://github.com/TheUncharted/zapcode.git
122
+ cd zapcode/crates/zapcode-py
123
+ maturin develop --release
124
+ ```
125
+ </details>
126
+
127
+ <details>
128
+ <summary><strong>WebAssembly</strong></summary>
129
+
130
+ Requires [wasm-pack](https://rustwasm.github.io/wasm-pack/):
131
+
132
+ ```bash
133
+ git clone https://github.com/TheUncharted/zapcode.git
134
+ cd zapcode/crates/zapcode-wasm
135
+ wasm-pack build --target web
136
+ ```
137
+
138
+ This outputs a `pkg/` directory you can import in any browser or bundler.
139
+ </details>
140
+
141
+ ## Usage
142
+
143
+ ### With Vercel AI SDK (`@unchartedfr/zapcode-ai`)
144
+
145
+ The recommended way — one call gives you `{ system, tools }` that plug directly into `generateText` / `streamText`:
146
+
147
+ ```typescript
148
+ import { zapcode } from "@unchartedfr/zapcode-ai";
149
+ import { generateText } from "ai";
150
+ import { anthropic } from "@ai-sdk/anthropic";
151
+
152
+ const { system, tools } = zapcode({
153
+ system: "You are a helpful travel assistant.",
154
+ tools: {
155
+ getWeather: {
156
+ description: "Get current weather for a city",
157
+ parameters: { city: { type: "string", description: "City name" } },
158
+ execute: async ({ city }) => {
159
+ const res = await fetch(`https://api.weather.com/${city}`);
160
+ return res.json();
161
+ },
162
+ },
163
+ searchFlights: {
164
+ description: "Search flights between two cities",
165
+ parameters: {
166
+ from: { type: "string" },
167
+ to: { type: "string" },
168
+ date: { type: "string" },
169
+ },
170
+ execute: async ({ from, to, date }) => {
171
+ return flightAPI.search(from, to, date);
172
+ },
173
+ },
174
+ },
175
+ });
176
+
177
+ // Works with any AI SDK model — Anthropic, OpenAI, Google, etc.
178
+ const { text } = await generateText({
179
+ model: anthropic("claude-sonnet-4-20250514"),
180
+ system,
181
+ tools,
182
+ messages: [{ role: "user", content: "Weather in Tokyo and cheapest flight from London?" }],
183
+ });
184
+ ```
185
+
186
+ Under the hood: the LLM writes TypeScript code that calls your tools → Zapcode executes it in a sandbox → tool calls suspend the VM → your `execute` functions run on the host → results flow back in. All in ~2µs startup + tool execution time.
187
+
188
+ See [`examples/typescript/ai-agent-zapcode-ai.ts`](examples/typescript/ai-agent-zapcode-ai.ts) for the full working example.
189
+
190
+ ### With Anthropic SDK directly
191
+
192
+ <details>
193
+ <summary><strong>TypeScript</strong></summary>
194
+
195
+ ```typescript
196
+ import Anthropic from "@anthropic-ai/sdk";
197
+ import { Zapcode, ZapcodeSnapshotHandle } from "@unchartedfr/zapcode";
198
+
199
+ const tools = {
200
+ getWeather: async (city: string) => {
201
+ const res = await fetch(`https://api.weather.com/${city}`);
202
+ return res.json();
203
+ },
204
+ };
205
+
206
+ const client = new Anthropic();
207
+ const response = await client.messages.create({
208
+ model: "claude-sonnet-4-20250514",
209
+ max_tokens: 1024,
210
+ system: `Write TypeScript to answer the user's question.
211
+ Available functions (use await): getWeather(city: string) → { condition, temp }
212
+ Last expression = output. No markdown fences.`,
213
+ messages: [{ role: "user", content: "What's the weather in Tokyo?" }],
214
+ });
215
+
216
+ const code = response.content[0].type === "text" ? response.content[0].text : "";
217
+
218
+ // Execute + resolve tool calls via snapshot/resume
219
+ const sandbox = new Zapcode(code, { externalFunctions: ["getWeather"] });
220
+ let state = sandbox.start();
221
+ while (!state.completed) {
222
+ const result = await tools[state.functionName](...state.args);
223
+ state = ZapcodeSnapshotHandle.load(state.snapshot).resume(result);
224
+ }
225
+ console.log(state.output);
226
+ ```
227
+
228
+ See [`examples/typescript/ai-agent-anthropic.ts`](examples/typescript/ai-agent-anthropic.ts).
229
+ </details>
230
+
231
+ <details>
232
+ <summary><strong>Python</strong></summary>
233
+
234
+ ```python
235
+ import anthropic
236
+ from zapcode import Zapcode
237
+
238
+ client = anthropic.Anthropic()
239
+ response = client.messages.create(
240
+ model="claude-sonnet-4-20250514",
241
+ max_tokens=1024,
242
+ system="""Write TypeScript to answer the user's question.
243
+ Available functions (use await): getWeather(city: string) → { condition, temp }
244
+ Last expression = output. No markdown fences.""",
245
+ messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
246
+ )
247
+ code = response.content[0].text
248
+
249
+ sandbox = Zapcode(code, external_functions=["getWeather"])
250
+ state = sandbox.start()
251
+ while state.get("suspended"):
252
+ result = get_weather(*state["args"])
253
+ state = state["snapshot"].resume(result)
254
+ print(state["output"])
255
+ ```
256
+
257
+ See [`examples/python/ai_agent_anthropic.py`](examples/python/ai_agent_anthropic.py).
258
+ </details>
259
+
260
+ ### Multi-SDK support
261
+
262
+ `zapcode()` returns adapters for all major AI SDKs from a single call:
263
+
264
+ ```typescript
265
+ const { system, tools, openaiTools, anthropicTools, handleToolCall } = zapcode({
266
+ tools: { getWeather: { ... } },
267
+ });
268
+
269
+ // Vercel AI SDK
270
+ await generateText({ model: anthropic("claude-sonnet-4-20250514"), system, tools, messages });
271
+
272
+ // OpenAI SDK
273
+ await openai.chat.completions.create({
274
+ messages: [{ role: "system", content: system }, ...userMessages],
275
+ tools: openaiTools,
276
+ });
277
+
278
+ // Anthropic SDK
279
+ await anthropic.messages.create({ system, tools: anthropicTools, messages });
280
+
281
+ // Any SDK — just extract the `code` from the tool call and pass it to handleToolCall
282
+ const result = await handleToolCall(codeFromToolCall);
283
+ ```
284
+
285
+ Python:
286
+
287
+ ```python
288
+ b = zapcode(tools={...})
289
+ b.anthropic_tools # → Anthropic SDK format
290
+ b.openai_tools # → OpenAI SDK format
291
+ b.handle_tool_call(code) # → Universal handler
292
+ ```
293
+
294
+ ### Custom adapters
295
+
296
+ Building a new AI SDK or framework? You can write a custom adapter without forking Zapcode:
297
+
298
+ <details>
299
+ <summary><strong>TypeScript</strong></summary>
300
+
301
+ ```typescript
302
+ import { zapcode, createAdapter } from "@unchartedfr/zapcode-ai";
303
+
304
+ // Create a typed adapter for your SDK
305
+ const myAdapter = createAdapter("my-sdk", (ctx) => {
306
+ // ctx gives you: system, toolName, toolDescription, toolSchema, handleToolCall
307
+ return {
308
+ systemMessage: ctx.system,
309
+ actions: [{
310
+ id: ctx.toolName,
311
+ schema: ctx.toolSchema,
312
+ run: async (input: { code: string }) => {
313
+ return ctx.handleToolCall(input.code);
314
+ },
315
+ }],
316
+ };
317
+ });
318
+
319
+ const { custom } = zapcode({
320
+ tools: { ... },
321
+ adapters: [myAdapter],
322
+ });
323
+
324
+ const myConfig = custom["my-sdk"];
325
+ // { systemMessage: "...", actions: [{ id: "execute_code", ... }] }
326
+ ```
327
+ </details>
328
+
329
+ <details>
330
+ <summary><strong>Python</strong></summary>
331
+
332
+ ```python
333
+ from zapcode_ai import zapcode, Adapter, AdapterContext
334
+
335
+ class LangChainAdapter(Adapter):
336
+ name = "langchain"
337
+
338
+ def adapt(self, ctx: AdapterContext):
339
+ from langchain_core.tools import StructuredTool
340
+ return StructuredTool.from_function(
341
+ func=lambda code: ctx.handle_tool_call(code),
342
+ name=ctx.tool_name,
343
+ description=ctx.tool_description,
344
+ )
345
+
346
+ b = zapcode(
347
+ tools={...},
348
+ adapters=[LangChainAdapter()],
349
+ )
350
+
351
+ langchain_tool = b.custom["langchain"]
352
+ ```
353
+ </details>
354
+
355
+ The adapter receives an `AdapterContext` with everything needed: system prompt, tool name, tool JSON schema, and a `handleToolCall` function. Return whatever shape your SDK expects.
356
+
357
+ ### Basic usage by language
358
+
359
+ <details>
360
+ <summary><strong>TypeScript / JavaScript</strong></summary>
361
+
362
+ ```typescript
363
+ import { Zapcode, ZapcodeSnapshotHandle } from '@unchartedfr/zapcode';
364
+
365
+ // Simple expression
366
+ const b = new Zapcode('1 + 2 * 3');
367
+ console.log(b.run().output); // 7
368
+
369
+ // With inputs
370
+ const greeter = new Zapcode(
371
+ '`Hello, ${name}! You are ${age} years old.`',
372
+ { inputs: ['name', 'age'] },
373
+ );
374
+ console.log(greeter.run({ name: 'Zapcode', age: 30 }).output);
375
+
376
+ // Data processing
377
+ const processor = new Zapcode(`
378
+ const items = [
379
+ { name: "Widget", price: 25.99, qty: 3 },
380
+ { name: "Gadget", price: 49.99, qty: 1 },
381
+ ];
382
+ const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
383
+ ({ total, names: items.map(i => i.name) })
384
+ `);
385
+ console.log(processor.run().output);
386
+ // { total: 127.96, names: ["Widget", "Gadget"] }
387
+
388
+ // External function (snapshot/resume)
389
+ const app = new Zapcode(`const data = await fetch(url); data`, {
390
+ inputs: ['url'],
391
+ externalFunctions: ['fetch'],
392
+ });
393
+ const state = app.start({ url: 'https://api.example.com' });
394
+ if (!state.completed) {
395
+ console.log(state.functionName); // "fetch"
396
+ const snapshot = ZapcodeSnapshotHandle.load(state.snapshot);
397
+ const final_ = snapshot.resume({ status: 'ok' });
398
+ console.log(final_.output); // { status: "ok" }
399
+ }
400
+
401
+ // Classes
402
+ const counter = new Zapcode(`
403
+ class Counter {
404
+ count: number;
405
+ constructor(start: number) { this.count = start; }
406
+ increment() { return ++this.count; }
407
+ }
408
+ const c = new Counter(10);
409
+ [c.increment(), c.increment(), c.increment()]
410
+ `);
411
+ console.log(counter.run().output); // [11, 12, 13]
412
+ ```
413
+
414
+ See [`examples/typescript/basic.ts`](examples/typescript/basic.ts) for the full example.
415
+ </details>
416
+
417
+ <details>
418
+ <summary><strong>Rust</strong></summary>
419
+
420
+ ```rust
421
+ use zapcode_core::{ZapcodeRun, Value, ResourceLimits, VmState};
422
+
423
+ // Simple expression
424
+ let runner = ZapcodeRun::new(
425
+ "1 + 2 * 3".to_string(), vec![], vec![],
426
+ ResourceLimits::default(),
427
+ )?;
428
+ assert_eq!(runner.run_simple()?, Value::Int(7));
429
+
430
+ // With inputs and external functions (snapshot/resume)
431
+ let runner = ZapcodeRun::new(
432
+ r#"const weather = await getWeather(city);
433
+ `${city}: ${weather.condition}, ${weather.temp}°C`"#.to_string(),
434
+ vec!["city".to_string()],
435
+ vec!["getWeather".to_string()],
436
+ ResourceLimits::default(),
437
+ )?;
438
+
439
+ let state = runner.start(vec![
440
+ ("city".to_string(), Value::String("London".into())),
441
+ ])?;
442
+
443
+ if let VmState::Suspended { snapshot, .. } = state {
444
+ let weather = Value::Object(indexmap::indexmap! {
445
+ "condition".into() => Value::String("Cloudy".into()),
446
+ "temp".into() => Value::Int(12),
447
+ });
448
+ let final_state = snapshot.resume(weather)?;
449
+ // VmState::Complete("London: Cloudy, 12°C")
450
+ }
451
+ ```
452
+
453
+ See [`examples/rust/basic.rs`](examples/rust/basic.rs) for the full example.
454
+ </details>
455
+
456
+ <details>
457
+ <summary><strong>Python</strong></summary>
458
+
459
+ ```python
460
+ from zapcode import Zapcode, ZapcodeSnapshot
461
+
462
+ # Simple expression
463
+ b = Zapcode("1 + 2 * 3")
464
+ print(b.run()["output"]) # 7
465
+
466
+ # With inputs
467
+ b = Zapcode(
468
+ '`Hello, ${name}!`',
469
+ inputs=["name"],
470
+ )
471
+ print(b.run({"name": "Zapcode"})["output"]) # "Hello, Zapcode!"
472
+
473
+ # External function (snapshot/resume)
474
+ b = Zapcode(
475
+ "const w = await getWeather(city); `${city}: ${w.temp}°C`",
476
+ inputs=["city"],
477
+ external_functions=["getWeather"],
478
+ )
479
+ state = b.start({"city": "London"})
480
+ if state.get("suspended"):
481
+ result = state["snapshot"].resume({"condition": "Cloudy", "temp": 12})
482
+ print(result["output"]) # "London: 12°C"
483
+
484
+ # Snapshot persistence
485
+ state = b.start({"city": "Tokyo"})
486
+ if state.get("suspended"):
487
+ bytes_ = state["snapshot"].dump() # serialize to bytes
488
+ restored = ZapcodeSnapshot.load(bytes_) # load from bytes
489
+ result = restored.resume({"condition": "Clear", "temp": 26})
490
+ ```
491
+
492
+ See [`examples/python/basic.py`](examples/python/basic.py) for the full example.
493
+ </details>
494
+
495
+ <details>
496
+ <summary><strong>WebAssembly (browser)</strong></summary>
497
+
498
+ ```html
499
+ <script type="module">
500
+ import init, { Zapcode } from './zapcode-wasm/zapcode_wasm.js';
501
+
502
+ await init();
503
+
504
+ const b = new Zapcode(`
505
+ const items = [10, 20, 30];
506
+ items.map(x => x * 2).reduce((a, b) => a + b, 0)
507
+ `);
508
+ const result = b.run();
509
+ console.log(result.output); // 120
510
+ </script>
511
+ ```
512
+
513
+ See [`examples/wasm/index.html`](examples/wasm/index.html) for a full playground.
514
+ </details>
515
+
516
+ ## What Zapcode can and cannot do
517
+
518
+ ### Can do
519
+
520
+ - **Execute a useful subset of TypeScript** — variables, functions, classes, generators, async/await, closures, destructuring, spread/rest, optional chaining, nullish coalescing, template literals, try/catch
521
+ - **Strip TypeScript types** at parse time via [oxc](https://oxc.rs) — no `tsc` needed
522
+ - **Snapshot execution to bytes** and resume later, even in a different process or machine
523
+ - **Call from Rust, Node.js, Python, or WebAssembly**
524
+ - **Track and limit resources** — memory, allocations, stack depth, and wall-clock time
525
+ - **30+ string methods, 25+ array methods**, plus Math, JSON, Object, and Promise builtins
526
+
527
+ ### Cannot do
528
+
529
+ - Run arbitrary npm packages or the full Node.js standard library
530
+ - Execute regular expressions (parsing supported, execution is a no-op)
531
+ - Provide full `Promise` semantics (`.then()` chains, `Promise.race`, etc.)
532
+ - Run code that requires `this` in non-class contexts
533
+
534
+ These are intentional constraints, not bugs. Zapcode targets one use case: **running code written by AI agents** inside a secure, embeddable sandbox.
535
+
536
+ <details>
537
+ <summary><strong>Full supported syntax table</strong></summary>
538
+
539
+ | Feature | Status |
540
+ |---|---|
541
+ | Variables (`const`, `let`) | Supported |
542
+ | Functions (declarations, arrows, expressions) | Supported |
543
+ | Classes (`constructor`, methods, `extends`, `super`, `static`) | Supported |
544
+ | Generators (`function*`, `yield`, `.next()`) | Supported |
545
+ | Async/await | Supported |
546
+ | Control flow (`if`, `for`, `while`, `do-while`, `switch`, `for-of`) | Supported |
547
+ | Try/catch/finally, `throw` | Supported |
548
+ | Closures with mutable capture | Supported |
549
+ | Destructuring (object and array) | Supported |
550
+ | Spread/rest operators | Supported |
551
+ | Optional chaining (`?.`) | Supported |
552
+ | Nullish coalescing (`??`) | Supported |
553
+ | Template literals | Supported |
554
+ | Type annotations, interfaces, type aliases | Stripped at parse time |
555
+ | String methods (30+) | Supported |
556
+ | Array methods (25+, including `map`, `filter`, `reduce`) | Supported |
557
+ | Math, JSON, Object, Promise | Supported |
558
+ | `import` / `require` / `eval` | Blocked (sandbox) |
559
+ | Regular expressions | Parsed, not executed |
560
+ | `var` declarations | Not supported (use `let`/`const`) |
561
+ | Decorators | Not supported |
562
+ | `Symbol`, `WeakMap`, `WeakSet` | Not supported |
563
+ </details>
564
+
565
+ ## Alternatives
566
+
567
+ | | Language completeness | Security | Startup | Snapshots | Setup |
568
+ |---|---|---|---|---|---|
569
+ | **Zapcode** | TypeScript subset | Language-level sandbox | **~2 µs** | Built-in, < 2 KB | `cargo add` / `npm install` |
570
+ | Docker + Node.js | Full Node.js | Container isolation | ~200-500 ms | No | Container runtime |
571
+ | V8 Isolates | Full JS/TS | Isolate boundary | ~5-50 ms | No | V8 (~20 MB) |
572
+ | Deno Deploy | Full TS | Isolate + permissions | ~10-50 ms | No | Cloud service |
573
+ | QuickJS | Full ES2023 | Process isolation | ~1-5 ms | No | C library |
574
+ | WASI/Wasmer | Depends on guest | Wasm sandbox | ~1-10 ms | Possible | Wasm runtime |
575
+
576
+ ### Why not Docker?
577
+
578
+ Docker provides strong isolation but adds hundreds of milliseconds of cold-start latency, requires a container runtime, and doesn't support snapshotting execution state mid-function. For AI agent loops that execute thousands of small code snippets, the overhead dominates.
579
+
580
+ ### Why not V8?
581
+
582
+ V8 is the gold standard for JavaScript execution. But it brings ~20 MB of binary size, millisecond startup times, and a vast API surface that must be carefully restricted for sandboxing. If you need full ECMAScript compliance, use V8. If you need microsecond startup, byte-sized snapshots, and a security model where "blocked by default" is the foundation rather than an afterthought, use Zapcode.
583
+
584
+ ## Security
585
+
586
+ Running AI-generated code is inherently dangerous. Unlike Docker, which isolates at the OS level, Zapcode isolates at the **language level** — no container, no process boundary, no syscall filter. The sandbox must be correct by construction, not by configuration.
587
+
588
+ ### Deny-by-default sandbox
589
+
590
+ Guest code runs inside a bytecode VM with no access to the host:
591
+
592
+ | Blocked | How |
593
+ |---|---|
594
+ | Filesystem (`fs`, `path`) | No `std::fs` in the core crate |
595
+ | Network (`net`, `http`, `fetch`) | No `std::net` in the core crate |
596
+ | Environment (`process.env`, `os`) | No `std::env` in the core crate |
597
+ | `eval`, `Function()`, dynamic import | Blocked at parse time |
598
+ | `import`, `require` | Blocked at parse time |
599
+ | `globalThis`, `global` | Blocked at parse time |
600
+ | Prototype pollution | Not applicable — objects are plain `IndexMap` values |
601
+
602
+ The **only** escape hatch is external functions that you explicitly register. When guest code calls one, the VM suspends and returns a snapshot — your code resolves the call, not the guest.
603
+
604
+ ### Resource limits
605
+
606
+ | Limit | Default | Configurable |
607
+ |---|---|---|
608
+ | Memory | 32 MB | `memory_limit_bytes` |
609
+ | Execution time | 5 seconds | `time_limit_ms` |
610
+ | Call stack depth | 512 frames | `max_stack_depth` |
611
+ | Heap allocations | 100,000 | `max_allocations` |
612
+
613
+ Limits are checked during execution, so infinite loops, deep recursion, and allocation bombs are all caught.
614
+
615
+ ### Zero `unsafe` code
616
+
617
+ The `zapcode-core` crate contains **zero `unsafe` blocks**. Memory safety is guaranteed by the Rust compiler. No FFI calls, no raw pointers, no transmutes.
618
+
619
+ <details>
620
+ <summary><strong>Adversarial test suite — 65 tests across 19 attack categories</strong></summary>
621
+
622
+ The sandbox is validated by **65 adversarial security tests** (`tests/security.rs`) that simulate real attack scenarios:
623
+
624
+ | Attack category | Tests | Result |
625
+ |---|---|---|
626
+ | Prototype pollution (`Object.prototype`, `__proto__`) | 4 | Blocked |
627
+ | Constructor chain escapes (`({}).constructor.constructor(...)`) | 3 | Blocked |
628
+ | `eval`, `Function()`, indirect eval, dynamic import | 5 | Blocked at parse time |
629
+ | `globalThis`, `process`, `require`, `import` | 6 | Blocked at parse time |
630
+ | Stack overflow (direct + mutual recursion) | 2 | Caught by stack depth limit |
631
+ | Memory exhaustion (huge arrays, string doubling) | 4 | Caught by allocation limit |
632
+ | Infinite loops (`while(true)`, `for(;;)`) | 2 | Caught by time/allocation limit |
633
+ | JSON bombs (deep nesting, huge payloads) | 2 | Depth-limited (max 64) |
634
+ | Sparse array attacks (`arr[1e9]`, `arr[MAX_SAFE_INTEGER]`) | 3 | Capped growth (max +1024) |
635
+ | toString/valueOf hijacking during coercion | 3 | Not invoked (by design) |
636
+ | Unicode escapes for blocked keywords | 2 | Blocked |
637
+ | Computed property access tricks | 2 | Returns undefined |
638
+ | Timing side channels (`performance.now`) | 1 | Blocked |
639
+ | Error message information leakage | 3 | No host paths/env exposed |
640
+ | Type confusion attacks | 4 | Proper TypeError |
641
+ | Promise/Generator internal abuse | 4 | No escape |
642
+ | Negative array indices | 2 | Returns undefined |
643
+ | `setTimeout`, `setInterval`, `Proxy`, `Reflect` | 6 | Blocked |
644
+ | `with` statement, `arguments.callee` | 3 | Blocked |
645
+
646
+ Run the security tests: `cargo test -p zapcode-core --test security`
647
+
648
+ **Known limitations:**
649
+ - `Object.freeze()` is not yet implemented — frozen objects can still be mutated (correctness gap, not a sandbox escape)
650
+ - User-defined `toString()`/`valueOf()` are not called during implicit type coercion (intentional — prevents injection)
651
+ </details>
652
+
653
+ ## Architecture
654
+
655
+ ```
656
+ TypeScript source
657
+
658
+
659
+ ┌─────────┐ oxc_parser (fastest TS parser in Rust)
660
+ │ Parse │──────────────────────────────────────────► Strip types
661
+ └────┬────┘
662
+
663
+ ┌─────────┐
664
+ │ IR │ ZapcodeIR (statements, expressions, operators)
665
+ └────┬────┘
666
+
667
+ ┌─────────┐
668
+ │ Compile │ Stack-based bytecode (~50 instructions)
669
+ └────┬────┘
670
+
671
+ ┌─────────┐
672
+ │ VM │ Execute, snapshot at external calls, resume later
673
+ └────┬────┘
674
+
675
+ Result / Suspended { snapshot }
676
+ ```
677
+
678
+ ## Contributing
679
+
680
+ ```bash
681
+ git clone https://github.com/TheUncharted/zapcode.git
682
+ cd zapcode
683
+
684
+ # Run all tests (214 tests)
685
+ cargo test
686
+
687
+ # Run benchmarks
688
+ cargo bench
689
+
690
+ # Check all crates (including bindings)
691
+ cargo check --workspace
692
+ ```
693
+
694
+ ## Why AI agents should write code
695
+
696
+ For motivation on why you might want LLMs to write and execute code instead of chaining tool calls:
697
+
698
+ - [CodeMode](https://blog.cloudflare.com/codemode-ai-agent-coding) from Cloudflare
699
+ - [Programmatic Tool Calling](https://docs.anthropic.com/en/docs/build-with-claude/tool-use/tool-use-examples#programmatic-tool-calling) from Anthropic
700
+ - [Code Execution with MCP](https://www.anthropic.com/engineering/code-execution-mcp) from Anthropic
701
+ - [Smol Agents](https://huggingface.co/docs/smolagents/en/index) from Hugging Face
702
+
703
+ Zapcode is inspired by [Monty](https://github.com/pydantic/monty), Pydantic's Python subset interpreter that takes the same approach for Python.
704
+
705
+ ## License
706
+
707
+ MIT
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode-darwin-arm64",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "cpu": [
5
5
  "arm64"
6
6
  ],
@@ -9,12 +9,28 @@
9
9
  "zapcode.darwin-arm64.node"
10
10
  ],
11
11
  "description": "A minimal, secure TypeScript interpreter for AI agents — Node.js bindings",
12
+ "keywords": [
13
+ "typescript",
14
+ "interpreter",
15
+ "sandbox",
16
+ "ai",
17
+ "mcp",
18
+ "wasm",
19
+ "snapshot",
20
+ "secure"
21
+ ],
22
+ "author": "Uncharted",
23
+ "homepage": "https://github.com/TheUncharted/zapcode#readme",
12
24
  "license": "MIT",
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
13
28
  "repository": {
14
29
  "type": "git",
15
30
  "url": "https://github.com/TheUncharted/zapcode.git",
16
31
  "directory": "crates/zapcode-js"
17
32
  },
33
+ "bugs": "https://github.com/TheUncharted/zapcode/issues",
18
34
  "os": [
19
35
  "darwin"
20
36
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode-darwin-x64",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "cpu": [
5
5
  "x64"
6
6
  ],
@@ -9,12 +9,28 @@
9
9
  "zapcode.darwin-x64.node"
10
10
  ],
11
11
  "description": "A minimal, secure TypeScript interpreter for AI agents — Node.js bindings",
12
+ "keywords": [
13
+ "typescript",
14
+ "interpreter",
15
+ "sandbox",
16
+ "ai",
17
+ "mcp",
18
+ "wasm",
19
+ "snapshot",
20
+ "secure"
21
+ ],
22
+ "author": "Uncharted",
23
+ "homepage": "https://github.com/TheUncharted/zapcode#readme",
12
24
  "license": "MIT",
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
13
28
  "repository": {
14
29
  "type": "git",
15
30
  "url": "https://github.com/TheUncharted/zapcode.git",
16
31
  "directory": "crates/zapcode-js"
17
32
  },
33
+ "bugs": "https://github.com/TheUncharted/zapcode/issues",
18
34
  "os": [
19
35
  "darwin"
20
36
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode-linux-arm64-gnu",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "cpu": [
5
5
  "arm64"
6
6
  ],
@@ -9,12 +9,28 @@
9
9
  "zapcode.linux-arm64-gnu.node"
10
10
  ],
11
11
  "description": "A minimal, secure TypeScript interpreter for AI agents — Node.js bindings",
12
+ "keywords": [
13
+ "typescript",
14
+ "interpreter",
15
+ "sandbox",
16
+ "ai",
17
+ "mcp",
18
+ "wasm",
19
+ "snapshot",
20
+ "secure"
21
+ ],
22
+ "author": "Uncharted",
23
+ "homepage": "https://github.com/TheUncharted/zapcode#readme",
12
24
  "license": "MIT",
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
13
28
  "repository": {
14
29
  "type": "git",
15
30
  "url": "https://github.com/TheUncharted/zapcode.git",
16
31
  "directory": "crates/zapcode-js"
17
32
  },
33
+ "bugs": "https://github.com/TheUncharted/zapcode/issues",
18
34
  "os": [
19
35
  "linux"
20
36
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode-linux-x64-gnu",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "cpu": [
5
5
  "x64"
6
6
  ],
@@ -9,12 +9,28 @@
9
9
  "zapcode.linux-x64-gnu.node"
10
10
  ],
11
11
  "description": "A minimal, secure TypeScript interpreter for AI agents — Node.js bindings",
12
+ "keywords": [
13
+ "typescript",
14
+ "interpreter",
15
+ "sandbox",
16
+ "ai",
17
+ "mcp",
18
+ "wasm",
19
+ "snapshot",
20
+ "secure"
21
+ ],
22
+ "author": "Uncharted",
23
+ "homepage": "https://github.com/TheUncharted/zapcode#readme",
12
24
  "license": "MIT",
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
13
28
  "repository": {
14
29
  "type": "git",
15
30
  "url": "https://github.com/TheUncharted/zapcode.git",
16
31
  "directory": "crates/zapcode-js"
17
32
  },
33
+ "bugs": "https://github.com/TheUncharted/zapcode/issues",
18
34
  "os": [
19
35
  "linux"
20
36
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode-linux-x64-musl",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "cpu": [
5
5
  "x64"
6
6
  ],
@@ -9,12 +9,28 @@
9
9
  "zapcode.linux-x64-musl.node"
10
10
  ],
11
11
  "description": "A minimal, secure TypeScript interpreter for AI agents — Node.js bindings",
12
+ "keywords": [
13
+ "typescript",
14
+ "interpreter",
15
+ "sandbox",
16
+ "ai",
17
+ "mcp",
18
+ "wasm",
19
+ "snapshot",
20
+ "secure"
21
+ ],
22
+ "author": "Uncharted",
23
+ "homepage": "https://github.com/TheUncharted/zapcode#readme",
12
24
  "license": "MIT",
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
13
28
  "repository": {
14
29
  "type": "git",
15
30
  "url": "https://github.com/TheUncharted/zapcode.git",
16
31
  "directory": "crates/zapcode-js"
17
32
  },
33
+ "bugs": "https://github.com/TheUncharted/zapcode/issues",
18
34
  "os": [
19
35
  "linux"
20
36
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode-win32-arm64-msvc",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "cpu": [
5
5
  "arm64"
6
6
  ],
@@ -9,12 +9,28 @@
9
9
  "zapcode.win32-arm64-msvc.node"
10
10
  ],
11
11
  "description": "A minimal, secure TypeScript interpreter for AI agents — Node.js bindings",
12
+ "keywords": [
13
+ "typescript",
14
+ "interpreter",
15
+ "sandbox",
16
+ "ai",
17
+ "mcp",
18
+ "wasm",
19
+ "snapshot",
20
+ "secure"
21
+ ],
22
+ "author": "Uncharted",
23
+ "homepage": "https://github.com/TheUncharted/zapcode#readme",
12
24
  "license": "MIT",
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
13
28
  "repository": {
14
29
  "type": "git",
15
30
  "url": "https://github.com/TheUncharted/zapcode.git",
16
31
  "directory": "crates/zapcode-js"
17
32
  },
33
+ "bugs": "https://github.com/TheUncharted/zapcode/issues",
18
34
  "os": [
19
35
  "win32"
20
36
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode-win32-x64-msvc",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "cpu": [
5
5
  "x64"
6
6
  ],
@@ -9,12 +9,28 @@
9
9
  "zapcode.win32-x64-msvc.node"
10
10
  ],
11
11
  "description": "A minimal, secure TypeScript interpreter for AI agents — Node.js bindings",
12
+ "keywords": [
13
+ "typescript",
14
+ "interpreter",
15
+ "sandbox",
16
+ "ai",
17
+ "mcp",
18
+ "wasm",
19
+ "snapshot",
20
+ "secure"
21
+ ],
22
+ "author": "Uncharted",
23
+ "homepage": "https://github.com/TheUncharted/zapcode#readme",
12
24
  "license": "MIT",
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
13
28
  "repository": {
14
29
  "type": "git",
15
30
  "url": "https://github.com/TheUncharted/zapcode.git",
16
31
  "directory": "crates/zapcode-js"
17
32
  },
33
+ "bugs": "https://github.com/TheUncharted/zapcode/issues",
18
34
  "os": [
19
35
  "win32"
20
36
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "description": "A minimal, secure TypeScript interpreter for AI agents — Node.js bindings",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -12,16 +12,33 @@
12
12
  "require": "./index.js"
13
13
  }
14
14
  },
15
+ "keywords": [
16
+ "typescript",
17
+ "interpreter",
18
+ "sandbox",
19
+ "ai",
20
+ "mcp",
21
+ "wasm",
22
+ "snapshot",
23
+ "secure"
24
+ ],
25
+ "homepage": "https://github.com/TheUncharted/zapcode#readme",
26
+ "bugs": "https://github.com/TheUncharted/zapcode/issues",
15
27
  "license": "MIT",
28
+ "author": "Uncharted",
16
29
  "repository": {
17
30
  "type": "git",
18
31
  "url": "https://github.com/TheUncharted/zapcode.git",
19
32
  "directory": "crates/zapcode-js"
20
33
  },
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
21
37
  "files": [
22
38
  "index.js",
23
39
  "index.d.ts",
24
- "npm"
40
+ "npm",
41
+ "README.md"
25
42
  ],
26
43
  "napi": {
27
44
  "binaryName": "zapcode",
@@ -45,12 +62,12 @@
45
62
  "@napi-rs/cli": "^3.5.1"
46
63
  },
47
64
  "optionalDependencies": {
48
- "@unchartedfr/zapcode-linux-x64-gnu": "1.0.0-beta.1",
49
- "@unchartedfr/zapcode-linux-x64-musl": "1.0.0-beta.1",
50
- "@unchartedfr/zapcode-linux-arm64-gnu": "1.0.0-beta.1",
51
- "@unchartedfr/zapcode-darwin-x64": "1.0.0-beta.1",
52
- "@unchartedfr/zapcode-darwin-arm64": "1.0.0-beta.1",
53
- "@unchartedfr/zapcode-win32-x64-msvc": "1.0.0-beta.1",
54
- "@unchartedfr/zapcode-win32-arm64-msvc": "1.0.0-beta.1"
65
+ "@unchartedfr/zapcode-linux-x64-gnu": "1.0.0-beta.3",
66
+ "@unchartedfr/zapcode-linux-x64-musl": "1.0.0-beta.3",
67
+ "@unchartedfr/zapcode-linux-arm64-gnu": "1.0.0-beta.3",
68
+ "@unchartedfr/zapcode-darwin-x64": "1.0.0-beta.3",
69
+ "@unchartedfr/zapcode-darwin-arm64": "1.0.0-beta.3",
70
+ "@unchartedfr/zapcode-win32-x64-msvc": "1.0.0-beta.3",
71
+ "@unchartedfr/zapcode-win32-arm64-msvc": "1.0.0-beta.3"
55
72
  }
56
73
  }