@unchartedfr/zapcode 1.0.0-beta.3 → 1.1.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/README.md CHANGED
@@ -1,29 +1,61 @@
1
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>
2
+ <img src="assets/logo.png" alt="Zapcode" width="160" />
5
3
  </p>
4
+ <h1 align="center">Zapcode</h1>
5
+ <p align="center"><strong>Run AI code. Safely. Instantly.</strong></p>
6
+ <p align="center">A minimal, secure TypeScript interpreter written in Rust for use by AI agents</p>
6
7
 
7
8
  <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://github.com/TheUncharted/zapcode/actions"><img src="https://img.shields.io/github/actions/workflow/status/TheUncharted/zapcode/ci.yml?branch=master&label=CI" alt="CI"></a>
10
+ <a href="https://crates.io/crates/zapcode-core"><img src="https://img.shields.io/crates/v/zapcode-core" alt="crates.io"></a>
9
11
  <a href="https://www.npmjs.com/package/@unchartedfr/zapcode"><img src="https://img.shields.io/npm/v/@unchartedfr/zapcode" alt="npm"></a>
10
12
  <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>
13
+ <a href="https://github.com/TheUncharted/zapcode/blob/master/LICENSE"><img src="https://img.shields.io/github/license/TheUncharted/zapcode" alt="License"></a>
12
14
  </p>
13
15
 
14
16
  ---
15
17
 
16
18
  > **Experimental** — Zapcode is under active development. APIs may change.
17
19
 
18
- When LLMs write code, you need to run it — fast and safe. That's Zapcode.
20
+ ## Why agents should write code
19
21
 
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.
22
+ AI agents are more capable when they **write code** instead of chaining tool calls. Code gives agents loops, conditionals, variables, and composition things that tool chains simulate poorly.
21
23
 
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.
24
+ - [CodeMode](https://blog.cloudflare.com/codemode-ai-agent-coding)Cloudflare on why agents should write code
25
+ - [Programmatic Tool Calling](https://docs.anthropic.com/en/docs/build-with-claude/tool-use/tool-use-examples#programmatic-tool-calling) — Anthropic's approach
26
+ - [Code Execution with MCP](https://www.anthropic.com/engineering/code-execution-mcp) — Anthropic engineering
27
+ - [Smol Agents](https://huggingface.co/docs/smolagents/en/index) — Hugging Face's code-first agents
28
+
29
+ **But running AI-generated code is dangerous and slow.**
30
+
31
+ Docker adds 200-500ms of cold-start latency and requires a container runtime. V8 isolates bring ~20MB of binary and millisecond startup. Neither supports snapshotting execution mid-function.
32
+
33
+ Zapcode takes a different approach: a purpose-built TypeScript interpreter that starts in **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.
34
+
35
+ Inspired by [Monty](https://github.com/pydantic/monty), Pydantic's Python subset interpreter that takes the same approach for Python.
36
+
37
+ ## Alternatives
38
+
39
+ | | Language completeness | Security | Startup | Snapshots | Setup |
40
+ |---|---|---|---|---|---|
41
+ | **Zapcode** | TypeScript subset | Language-level sandbox | **~2 µs** | Built-in, < 2 KB | `npm install` / `pip install` |
42
+ | Docker + Node.js | Full Node.js | Container isolation | ~200-500 ms | No | Container runtime |
43
+ | V8 Isolates | Full JS/TS | Isolate boundary | ~5-50 ms | No | V8 (~20 MB) |
44
+ | Deno Deploy | Full TS | Isolate + permissions | ~10-50 ms | No | Cloud service |
45
+ | QuickJS | Full ES2023 | Process isolation | ~1-5 ms | No | C library |
46
+ | WASI/Wasmer | Depends on guest | Wasm sandbox | ~1-10 ms | Possible | Wasm runtime |
47
+
48
+ ### Why not Docker?
49
+
50
+ 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.
51
+
52
+ ### Why not V8?
53
+
54
+ 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.
23
55
 
24
56
  ## Benchmarks
25
57
 
26
- All benchmarks run on the full pipeline: parse → compile → execute. No caching, no warm-up.
58
+ All benchmarks run the full pipeline: parse → compile → execute. No caching, no warm-up.
27
59
 
28
60
  | Benchmark | Zapcode | Docker + Node.js | V8 Isolate |
29
61
  |---|---|---|---|
@@ -42,105 +74,184 @@ All benchmarks run on the full pipeline: parse → compile → execute. No cachi
42
74
 
43
75
  No background thread, no GC, no runtime — CPU usage is exactly proportional to the instructions executed.
44
76
 
45
- Run benchmarks: `cargo bench`
77
+ ```bash
78
+ cargo bench # run benchmarks yourself
79
+ ```
46
80
 
47
- ## Quick start
81
+ ## Installation
48
82
 
49
- ### One-line install
83
+ **TypeScript / JavaScript**
84
+ ```bash
85
+ npm install @unchartedfr/zapcode # npm / yarn / pnpm / bun
86
+ ```
50
87
 
88
+ **Python**
51
89
  ```bash
52
- curl -fsSL https://raw.githubusercontent.com/TheUncharted/zapcode/master/install.sh | bash
90
+ pip install zapcode # pip / uv
53
91
  ```
54
92
 
55
- The script auto-detects your project type (TypeScript, Python, Rust, WASM), installs prerequisites, and builds native bindings. Or specify explicitly:
93
+ **Rust**
94
+ ```toml
95
+ # Cargo.toml
96
+ [dependencies]
97
+ zapcode-core = "1.0.0"
98
+ ```
56
99
 
100
+ **WebAssembly**
57
101
  ```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
102
+ wasm-pack build crates/zapcode-wasm --target web
62
103
  ```
63
104
 
64
- ### Install by language
105
+ ## Basic Usage
65
106
 
66
- <details>
67
- <summary><strong>Rust</strong></summary>
107
+ ### TypeScript / JavaScript
68
108
 
69
- ```toml
70
- [dependencies]
71
- zapcode-core = { git = "https://github.com/TheUncharted/zapcode.git" }
72
- ```
73
- </details>
109
+ ```typescript
110
+ import { Zapcode, ZapcodeSnapshotHandle } from '@unchartedfr/zapcode';
74
111
 
75
- <details>
76
- <summary><strong>JavaScript / TypeScript (Node.js)</strong></summary>
112
+ // Simple expression
113
+ const b = new Zapcode('1 + 2 * 3');
114
+ console.log(b.run().output); // 7
77
115
 
78
- Once published to npm (coming soon):
116
+ // With inputs
117
+ const greeter = new Zapcode(
118
+ '`Hello, ${name}! You are ${age} years old.`',
119
+ { inputs: ['name', 'age'] },
120
+ );
121
+ console.log(greeter.run({ name: 'Zapcode', age: 30 }).output);
79
122
 
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
123
+ // Data processing
124
+ const processor = new Zapcode(`
125
+ const items = [
126
+ { name: "Widget", price: 25.99, qty: 3 },
127
+ { name: "Gadget", price: 49.99, qty: 1 },
128
+ ];
129
+ const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
130
+ ({ total, names: items.map(i => i.name) })
131
+ `);
132
+ console.log(processor.run().output);
133
+ // { total: 127.96, names: ["Widget", "Gadget"] }
134
+
135
+ // External function (snapshot/resume)
136
+ const app = new Zapcode(`const data = await fetch(url); data`, {
137
+ inputs: ['url'],
138
+ externalFunctions: ['fetch'],
139
+ });
140
+ const state = app.start({ url: 'https://api.example.com' });
141
+ if (!state.completed) {
142
+ console.log(state.functionName); // "fetch"
143
+ const snapshot = ZapcodeSnapshotHandle.load(state.snapshot);
144
+ const final_ = snapshot.resume({ status: 'ok' });
145
+ console.log(final_.output); // { status: "ok" }
146
+ }
85
147
  ```
86
148
 
87
- Until then, build from source — requires Rust toolchain:
149
+ See [`examples/typescript/basic.ts`](examples/typescript/basic.ts) for more.
88
150
 
89
- ```bash
90
- git clone https://github.com/TheUncharted/zapcode.git
91
- cd zapcode/crates/zapcode-js
92
- npm install && npm run build
151
+ ### Python
152
+
153
+ ```python
154
+ from zapcode import Zapcode, ZapcodeSnapshot
155
+
156
+ # Simple expression
157
+ b = Zapcode("1 + 2 * 3")
158
+ print(b.run()["output"]) # 7
159
+
160
+ # With inputs
161
+ b = Zapcode(
162
+ '`Hello, ${name}!`',
163
+ inputs=["name"],
164
+ )
165
+ print(b.run({"name": "Zapcode"})["output"]) # "Hello, Zapcode!"
166
+
167
+ # External function (snapshot/resume)
168
+ b = Zapcode(
169
+ "const w = await getWeather(city); `${city}: ${w.temp}°C`",
170
+ inputs=["city"],
171
+ external_functions=["getWeather"],
172
+ )
173
+ state = b.start({"city": "London"})
174
+ if state.get("suspended"):
175
+ result = state["snapshot"].resume({"condition": "Cloudy", "temp": 12})
176
+ print(result["output"]) # "London: 12°C"
93
177
 
94
- # Link into your project
95
- npm link # in zapcode-js/
96
- npm link @unchartedfr/zapcode # in your project
178
+ # Snapshot persistence
179
+ state = b.start({"city": "Tokyo"})
180
+ if state.get("suspended"):
181
+ bytes_ = state["snapshot"].dump() # serialize to bytes
182
+ restored = ZapcodeSnapshot.load(bytes_) # load from bytes
183
+ result = restored.resume({"condition": "Clear", "temp": 26})
97
184
  ```
98
- </details>
185
+
186
+ See [`examples/python/basic.py`](examples/python/basic.py) for more.
99
187
 
100
188
  <details>
101
- <summary><strong>Python</strong></summary>
189
+ <summary><strong>Rust</strong></summary>
102
190
 
103
- Once published to PyPI (coming soon):
191
+ ```rust
192
+ use zapcode_core::{ZapcodeRun, Value, ResourceLimits, VmState};
104
193
 
105
- ```bash
106
- pip install zapcode # pip
107
- uv add zapcode # uv (Astral)
108
- ```
194
+ // Simple expression
195
+ let runner = ZapcodeRun::new(
196
+ "1 + 2 * 3".to_string(), vec![], vec![],
197
+ ResourceLimits::default(),
198
+ )?;
199
+ assert_eq!(runner.run_simple()?, Value::Int(7));
109
200
 
110
- Until then, build from source requires Rust toolchain + [maturin](https://github.com/PyO3/maturin):
201
+ // With inputs and external functions (snapshot/resume)
202
+ let runner = ZapcodeRun::new(
203
+ r#"const weather = await getWeather(city);
204
+ `${city}: ${weather.condition}, ${weather.temp}°C`"#.to_string(),
205
+ vec!["city".to_string()],
206
+ vec!["getWeather".to_string()],
207
+ ResourceLimits::default(),
208
+ )?;
111
209
 
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
210
+ let state = runner.start(vec![
211
+ ("city".to_string(), Value::String("London".into())),
212
+ ])?;
118
213
 
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
214
+ if let VmState::Suspended { snapshot, .. } = state {
215
+ let weather = Value::Object(indexmap::indexmap! {
216
+ "condition".into() => Value::String("Cloudy".into()),
217
+ "temp".into() => Value::Int(12),
218
+ });
219
+ let final_state = snapshot.resume(weather)?;
220
+ // VmState::Complete("London: Cloudy, 12°C")
221
+ }
124
222
  ```
223
+
224
+ See [`examples/rust/basic.rs`](examples/rust/basic.rs) for more.
125
225
  </details>
126
226
 
127
227
  <details>
128
- <summary><strong>WebAssembly</strong></summary>
228
+ <summary><strong>WebAssembly (browser)</strong></summary>
229
+
230
+ ```html
231
+ <script type="module">
232
+ import init, { Zapcode } from './zapcode-wasm/zapcode_wasm.js';
129
233
 
130
- Requires [wasm-pack](https://rustwasm.github.io/wasm-pack/):
234
+ await init();
131
235
 
132
- ```bash
133
- git clone https://github.com/TheUncharted/zapcode.git
134
- cd zapcode/crates/zapcode-wasm
135
- wasm-pack build --target web
236
+ const b = new Zapcode(`
237
+ const items = [10, 20, 30];
238
+ items.map(x => x * 2).reduce((a, b) => a + b, 0)
239
+ `);
240
+ const result = b.run();
241
+ console.log(result.output); // 120
242
+ </script>
136
243
  ```
137
244
 
138
- This outputs a `pkg/` directory you can import in any browser or bundler.
245
+ See [`examples/wasm/index.html`](examples/wasm/index.html) for a full playground.
139
246
  </details>
140
247
 
141
- ## Usage
248
+ ## AI Agent Usage
142
249
 
143
- ### With Vercel AI SDK (`@unchartedfr/zapcode-ai`)
250
+ ### Vercel AI SDK (@unchartedfr/zapcode-ai)
251
+
252
+ ```bash
253
+ npm install @unchartedfr/zapcode-ai ai @ai-sdk/anthropic # or @ai-sdk/amazon-bedrock, @ai-sdk/openai
254
+ ```
144
255
 
145
256
  The recommended way — one call gives you `{ system, tools }` that plug directly into `generateText` / `streamText`:
146
257
 
@@ -187,10 +298,10 @@ Under the hood: the LLM writes TypeScript code that calls your tools → Zapcode
187
298
 
188
299
  See [`examples/typescript/ai-agent-zapcode-ai.ts`](examples/typescript/ai-agent-zapcode-ai.ts) for the full working example.
189
300
 
190
- ### With Anthropic SDK directly
191
-
192
301
  <details>
193
- <summary><strong>TypeScript</strong></summary>
302
+ <summary><strong>Anthropic SDK</strong></summary>
303
+
304
+ **TypeScript:**
194
305
 
195
306
  ```typescript
196
307
  import Anthropic from "@anthropic-ai/sdk";
@@ -225,11 +336,7 @@ while (!state.completed) {
225
336
  console.log(state.output);
226
337
  ```
227
338
 
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>
339
+ **Python:**
233
340
 
234
341
  ```python
235
342
  import anthropic
@@ -254,10 +361,11 @@ while state.get("suspended"):
254
361
  print(state["output"])
255
362
  ```
256
363
 
257
- See [`examples/python/ai_agent_anthropic.py`](examples/python/ai_agent_anthropic.py).
364
+ See [`examples/typescript/ai-agent-anthropic.ts`](examples/typescript/ai-agent-anthropic.ts) and [`examples/python/ai_agent_anthropic.py`](examples/python/ai_agent_anthropic.py).
258
365
  </details>
259
366
 
260
- ### Multi-SDK support
367
+ <details>
368
+ <summary><strong>Multi-SDK support</strong></summary>
261
369
 
262
370
  `zapcode()` returns adapters for all major AI SDKs from a single call:
263
371
 
@@ -278,32 +386,27 @@ await openai.chat.completions.create({
278
386
  // Anthropic SDK
279
387
  await anthropic.messages.create({ system, tools: anthropicTools, messages });
280
388
 
281
- // Any SDK — just extract the `code` from the tool call and pass it to handleToolCall
389
+ // Any SDK — just extract the code from the tool call and pass it to handleToolCall
282
390
  const result = await handleToolCall(codeFromToolCall);
283
391
  ```
284
392
 
285
- Python:
286
-
287
393
  ```python
288
394
  b = zapcode(tools={...})
289
395
  b.anthropic_tools # → Anthropic SDK format
290
396
  b.openai_tools # → OpenAI SDK format
291
397
  b.handle_tool_call(code) # → Universal handler
292
398
  ```
293
-
294
- ### Custom adapters
295
-
296
- Building a new AI SDK or framework? You can write a custom adapter without forking Zapcode:
399
+ </details>
297
400
 
298
401
  <details>
299
- <summary><strong>TypeScript</strong></summary>
402
+ <summary><strong>Custom adapters</strong></summary>
403
+
404
+ Build a custom adapter for any AI SDK without forking Zapcode:
300
405
 
301
406
  ```typescript
302
407
  import { zapcode, createAdapter } from "@unchartedfr/zapcode-ai";
303
408
 
304
- // Create a typed adapter for your SDK
305
409
  const myAdapter = createAdapter("my-sdk", (ctx) => {
306
- // ctx gives you: system, toolName, toolDescription, toolSchema, handleToolCall
307
410
  return {
308
411
  systemMessage: ctx.system,
309
412
  actions: [{
@@ -322,12 +425,7 @@ const { custom } = zapcode({
322
425
  });
323
426
 
324
427
  const myConfig = custom["my-sdk"];
325
- // { systemMessage: "...", actions: [{ id: "execute_code", ... }] }
326
428
  ```
327
- </details>
328
-
329
- <details>
330
- <summary><strong>Python</strong></summary>
331
429
 
332
430
  ```python
333
431
  from zapcode_ai import zapcode, Adapter, AdapterContext
@@ -343,188 +441,25 @@ class LangChainAdapter(Adapter):
343
441
  description=ctx.tool_description,
344
442
  )
345
443
 
346
- b = zapcode(
347
- tools={...},
348
- adapters=[LangChainAdapter()],
349
- )
350
-
444
+ b = zapcode(tools={...}, adapters=[LangChainAdapter()])
351
445
  langchain_tool = b.custom["langchain"]
352
446
  ```
353
- </details>
354
447
 
355
448
  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
449
  </details>
494
450
 
495
- <details>
496
- <summary><strong>WebAssembly (browser)</strong></summary>
451
+ ## What Zapcode Can and Cannot Do
497
452
 
498
- ```html
499
- <script type="module">
500
- import init, { Zapcode } from './zapcode-wasm/zapcode_wasm.js';
453
+ **Can do:**
501
454
 
502
- await init();
455
+ - Execute a useful subset of TypeScript — variables, functions, classes, generators, async/await, closures, destructuring, spread/rest, optional chaining, nullish coalescing, template literals, try/catch
456
+ - Strip TypeScript types at parse time via [oxc](https://oxc.rs) — no `tsc` needed
457
+ - Snapshot execution to bytes and resume later, even in a different process or machine
458
+ - Call from Rust, Node.js, Python, or WebAssembly
459
+ - Track and limit resources — memory, allocations, stack depth, and wall-clock time
460
+ - 30+ string methods, 25+ array methods, plus Math, JSON, Object, and Promise builtins
503
461
 
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
462
+ **Cannot do:**
528
463
 
529
464
  - Run arbitrary npm packages or the full Node.js standard library
530
465
  - Execute regular expressions (parsing supported, execution is a no-op)
@@ -533,8 +468,7 @@ See [`examples/wasm/index.html`](examples/wasm/index.html) for a full playground
533
468
 
534
469
  These are intentional constraints, not bugs. Zapcode targets one use case: **running code written by AI agents** inside a secure, embeddable sandbox.
535
470
 
536
- <details>
537
- <summary><strong>Full supported syntax table</strong></summary>
471
+ ## Supported Syntax
538
472
 
539
473
  | Feature | Status |
540
474
  |---|---|
@@ -560,26 +494,6 @@ These are intentional constraints, not bugs. Zapcode targets one use case: **run
560
494
  | `var` declarations | Not supported (use `let`/`const`) |
561
495
  | Decorators | Not supported |
562
496
  | `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
497
 
584
498
  ## Security
585
499
 
@@ -610,17 +524,13 @@ The **only** escape hatch is external functions that you explicitly register. Wh
610
524
  | Call stack depth | 512 frames | `max_stack_depth` |
611
525
  | Heap allocations | 100,000 | `max_allocations` |
612
526
 
613
- Limits are checked during execution, so infinite loops, deep recursion, and allocation bombs are all caught.
614
-
615
527
  ### Zero `unsafe` code
616
528
 
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.
529
+ The `zapcode-core` crate contains **zero `unsafe` blocks**. Memory safety is guaranteed by the Rust compiler.
618
530
 
619
531
  <details>
620
532
  <summary><strong>Adversarial test suite — 65 tests across 19 attack categories</strong></summary>
621
533
 
622
- The sandbox is validated by **65 adversarial security tests** (`tests/security.rs`) that simulate real attack scenarios:
623
-
624
534
  | Attack category | Tests | Result |
625
535
  |---|---|---|
626
536
  | Prototype pollution (`Object.prototype`, `__proto__`) | 4 | Blocked |
@@ -643,7 +553,9 @@ The sandbox is validated by **65 adversarial security tests** (`tests/security.r
643
553
  | `setTimeout`, `setInterval`, `Proxy`, `Reflect` | 6 | Blocked |
644
554
  | `with` statement, `arguments.callee` | 3 | Blocked |
645
555
 
646
- Run the security tests: `cargo test -p zapcode-core --test security`
556
+ ```bash
557
+ cargo test -p zapcode-core --test security # run the security tests
558
+ ```
647
559
 
648
560
  **Known limitations:**
649
561
  - `Object.freeze()` is not yet implemented — frozen objects can still be mutated (correctness gap, not a sandbox escape)
@@ -680,28 +592,9 @@ TypeScript source
680
592
  ```bash
681
593
  git clone https://github.com/TheUncharted/zapcode.git
682
594
  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
595
+ ./scripts/dev-setup.sh # installs toolchain, builds, runs tests
692
596
  ```
693
597
 
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
598
  ## License
706
599
 
707
600
  MIT
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode-darwin-arm64",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.1.0",
4
4
  "cpu": [
5
5
  "arm64"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode-darwin-x64",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.1.0",
4
4
  "cpu": [
5
5
  "x64"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode-linux-arm64-gnu",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.1.0",
4
4
  "cpu": [
5
5
  "arm64"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode-linux-x64-gnu",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.1.0",
4
4
  "cpu": [
5
5
  "x64"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode-linux-x64-musl",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.1.0",
4
4
  "cpu": [
5
5
  "x64"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode-win32-arm64-msvc",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.1.0",
4
4
  "cpu": [
5
5
  "arm64"
6
6
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode-win32-x64-msvc",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.1.0",
4
4
  "cpu": [
5
5
  "x64"
6
6
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unchartedfr/zapcode",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.1.0",
4
4
  "description": "A minimal, secure TypeScript interpreter for AI agents — Node.js bindings",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -62,12 +62,12 @@
62
62
  "@napi-rs/cli": "^3.5.1"
63
63
  },
64
64
  "optionalDependencies": {
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"
65
+ "@unchartedfr/zapcode-linux-x64-gnu": "1.1.0",
66
+ "@unchartedfr/zapcode-linux-x64-musl": "1.1.0",
67
+ "@unchartedfr/zapcode-linux-arm64-gnu": "1.1.0",
68
+ "@unchartedfr/zapcode-darwin-x64": "1.1.0",
69
+ "@unchartedfr/zapcode-darwin-arm64": "1.1.0",
70
+ "@unchartedfr/zapcode-win32-x64-msvc": "1.1.0",
71
+ "@unchartedfr/zapcode-win32-arm64-msvc": "1.1.0"
72
72
  }
73
73
  }