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