MCPcopy Index your code
hub / github.com/TheUncharted/zapcode

github.com/TheUncharted/zapcode @v1.5.3

Chat with this repo
repository ↗ · DeepWiki ↗ · release v1.5.3 ↗ · + Follow
683 symbols 1,871 edges 48 files 76 documented · 11%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Zapcode

Zapcode

Run AI-generated code. Safely. Instantly.

A minimal, secure TypeScript interpreter written in Rust for use by AI agents

CI crates.io npm PyPI License


Experimental — Zapcode is under active development. APIs may change.

Why agents should write code

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.

But running AI-generated code is dangerous and slow.

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.

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.

Inspired by Monty, Pydantic's Python subset interpreter that takes the same approach for Python.

Alternatives

Language completeness Security Startup Snapshots Setup
Zapcode TypeScript subset Language-level sandbox ~2 µs Built-in, < 2 KB npm install / pip install
Docker + Node.js Full Node.js Container isolation ~200-500 ms No Container runtime
V8 Isolates Full JS/TS Isolate boundary ~5-50 ms No V8 (~20 MB)
Deno Deploy Full TS Isolate + permissions ~10-50 ms No Cloud service
QuickJS Full ES2023 Process isolation ~1-5 ms No C library
WASI/Wasmer Depends on guest Wasm sandbox ~1-10 ms Possible Wasm runtime

Why not Docker?

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.

Why not V8?

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.

Benchmarks

All benchmarks run the full pipeline: parse → compile → execute. No caching, no warm-up.

Benchmark Zapcode Docker + Node.js V8 Isolate
Simple expression (1 + 2 * 3) 2.1 µs ~200-500 ms ~5-50 ms
Variable arithmetic 2.8 µs
String concatenation 2.6 µs
Template literal 2.9 µs
Array creation 2.4 µs
Object creation 5.2 µs
Function call 4.6 µs
Promise.resolve + await 3.1 µs
Promise.then (single) 5.6 µs
Promise.then chain (×3) 9.9 µs
Promise.all (3 promises) 7.4 µs
Async .map() (3 elements) 11.6 µs
Loop (100 iterations) 77.8 µs
Fibonacci (n=10, 177 calls) 138.4 µs
Snapshot size (typical agent) < 2 KB N/A N/A
Memory per execution ~10 KB ~50+ MB ~20+ MB
Cold start ~2 µs ~200-500 ms ~5-50 ms

No background thread, no GC, no runtime — CPU usage is exactly proportional to the instructions executed.

cargo bench   # run benchmarks yourself

Installation

TypeScript / JavaScript

npm install @unchartedfr/zapcode        # npm / yarn / pnpm / bun

Python

pip install zapcode                     # pip / uv

Rust

# Cargo.toml
[dependencies]
zapcode-core = "1.0.0"

WebAssembly

wasm-pack build crates/zapcode-wasm --target web

Basic Usage

TypeScript / JavaScript

import { Zapcode, ZapcodeSnapshotHandle } from '@unchartedfr/zapcode';

// Simple expression
const b = new Zapcode('1 + 2 * 3');
console.log(b.run().output);  // 7

// With inputs
const greeter = new Zapcode(
    '`Hello, ${name}! You are ${age} years old.`',
    { inputs: ['name', 'age'] },
);
console.log(greeter.run({ name: 'Zapcode', age: 30 }).output);

// Data processing
const processor = new Zapcode(`
    const items = [
        { name: "Widget", price: 25.99, qty: 3 },
        { name: "Gadget", price: 49.99, qty: 1 },
    ];
    const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
    ({ total, names: items.map(i => i.name) })
`);
console.log(processor.run().output);
// { total: 127.96, names: ["Widget", "Gadget"] }

// External function (snapshot/resume)
const app = new Zapcode(`const data = await fetch(url); data`, {
    inputs: ['url'],
    externalFunctions: ['fetch'],
});
const state = app.start({ url: 'https://api.example.com' });
if (!state.completed) {
    console.log(state.functionName);  // "fetch"
    const snapshot = ZapcodeSnapshotHandle.load(state.snapshot);
    const final_ = snapshot.resume({ status: 'ok' });
    console.log(final_.output);  // { status: "ok" }
}

See examples/typescript/basic/main.ts for more.

Python

from zapcode import Zapcode, ZapcodeSnapshot

# Simple expression
b = Zapcode("1 + 2 * 3")
print(b.run()["output"])  # 7

# With inputs
b = Zapcode(
    '`Hello, ${name}!`',
    inputs=["name"],
)
print(b.run({"name": "Zapcode"})["output"])  # "Hello, Zapcode!"

# External function (snapshot/resume)
b = Zapcode(
    "const w = await getWeather(city); `${city}: ${w.temp}°C`",
    inputs=["city"],
    external_functions=["getWeather"],
)
state = b.start({"city": "London"})
if state.get("suspended"):
    result = state["snapshot"].resume({"condition": "Cloudy", "temp": 12})
    print(result["output"])  # "London: 12°C"

# Snapshot persistence
state = b.start({"city": "Tokyo"})
if state.get("suspended"):
    bytes_ = state["snapshot"].dump()          # serialize to bytes
    restored = ZapcodeSnapshot.load(bytes_)    # load from bytes
    result = restored.resume({"condition": "Clear", "temp": 26})

See examples/python/basic/main.py for more.

Rust

use zapcode_core::{ZapcodeRun, Value, ResourceLimits, VmState};

// Simple expression
let runner = ZapcodeRun::new(
    "1 + 2 * 3".to_string(), vec![], vec![],
    ResourceLimits::default(),
)?;
assert_eq!(runner.run_simple()?, Value::Int(7));

// With inputs and external functions (snapshot/resume)
let runner = ZapcodeRun::new(
    r#"const weather = await getWeather(city);
       `${city}: ${weather.condition}, ${weather.temp}°C`"#.to_string(),
    vec!["city".to_string()],
    vec!["getWeather".to_string()],
    ResourceLimits::default(),
)?;

let state = runner.start(vec![
    ("city".to_string(), Value::String("London".into())),
])?;

if let VmState::Suspended { snapshot, .. } = state {
    let weather = Value::Object(indexmap::indexmap! {
        "condition".into() => Value::String("Cloudy".into()),
        "temp".into() => Value::Int(12),
    });
    let final_state = snapshot.resume(weather)?;
    // VmState::Complete("London: Cloudy, 12°C")
}

See examples/rust/basic/basic.rs for more.

WebAssembly (browser)

<script type="module">
import init, { Zapcode } from './zapcode-wasm/zapcode_wasm.js';

await init();

const b = new Zapcode(`
    const items = [10, 20, 30];
    items.map(x => x * 2).reduce((a, b) => a + b, 0)
`);
const result = b.run();
console.log(result.output);  // 120
</script>

See examples/wasm/basic/index.html for a full playground.

AI Agent Usage

Vercel AI SDK (@unchartedfr/zapcode-ai)

npm install @unchartedfr/zapcode-ai ai @ai-sdk/anthropic  # or @ai-sdk/amazon-bedrock, @ai-sdk/openai

The recommended way — one call gives you { system, tools } that plug directly into generateText / streamText:

import { zapcode } from "@unchartedfr/zapcode-ai";
import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

const { system, tools } = zapcode({
  system: "You are a helpful travel assistant.",
  tools: {
    getWeather: {
      description: "Get current weather for a city",
      parameters: { city: { type: "string", description: "City name" } },
      execute: async ({ city }) => {
        const res = await fetch(`https://api.weather.com/${city}`);
        return res.json();
      },
    },
    searchFlights: {
      description: "Search flights between two cities",
      parameters: {
        from: { type: "string" },
        to: { type: "string" },
        date: { type: "string" },
      },
      execute: async ({ from, to, date }) => {
        return flightAPI.search(from, to, date);
      },
    },
  },
});

// Works with any AI SDK model — Anthropic, OpenAI, Google, etc.
const { text } = await generateText({
  model: anthropic("claude-sonnet-4-20250514"),
  system,
  tools,
  messages: [{ role: "user", content: "Weather in Tokyo and cheapest flight from London?" }],
});

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.

See examples/typescript/ai-agent/ai-agent-zapcode-ai.ts for the full working example.

Anthropic SDK

TypeScript:

import Anthropic from "@anthropic-ai/sdk";
import { Zapcode, ZapcodeSnapshotHandle } from "@unchartedfr/zapcode";

const tools = {
  getWeather: async (city: string) => {
    const res = await fetch(`https://api.weather.com/${city}`);
    return res.json();
  },
};

const client = new Anthropic();
const response = await client.messages.create({
  model: "claude-sonnet-4-20250514",
  max_tokens: 1024,
  system: `Write TypeScript to answer the user's question.
Available functions (use await): getWeather(city: string) → { condition, temp }
Last expression = output. No markdown fences.`,
  messages: [{ role: "user", content: "What's the weather in Tokyo?" }],
});

const code = response.content[0].type === "text" ? response.content[0].text : "";

// Execute + resolve tool calls via snapshot/resume
const sandbox = new Zapcode(code, { externalFunctions: ["getWeather"] });
let state = sandbox.start();
while (!state.completed) {
  const result = await tools[state.functionName](...state.args);
  state = ZapcodeSnapshotHandle.load(state.snapshot).resume(result);
}
console.log(state.output);

Python:

import anthropic
from zapcode import Zapcode

client = anthropic.Anthropic()
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    system="""Write TypeScript to answer the user's question.
Available functions (use await): getWeather(city: string) → { condition, temp }
Last expression = output. No markdown fences.""",
    messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
)
code = response.content[0].text

sandbox = Zapcode(code, external_functions=["getWeather"])
state = sandbox.start()
while state.get("suspended"):
    result = get_weather(*state["args"])
    state = state["snapshot"].resume(result)
print(state["output"])

See examples/typescript/ai-agent/ai-agent-anthropic.ts and examples/python/ai-agent/ai_agent_anthropic.py.

Multi-SDK support

zapcode() returns adapters for all major AI SDKs from a single call:

```typescript const { system, tools, openaiTools, anthropicTools, handleToolCall } = zapcode({ tools: { getWeather: { ... } }, });

// Vercel AI SDK await generateText({ model: anthropic("claude-sonnet-4-20250514"), system, tools, messages });

// OpenAI SDK await openai.chat.completions.create({ messages: [{ role: "system", content: system }, ...userMessages], tools: openaiTools, });

// Anthropic SDK await anthropic.messages.create({ system, tools: anthropicTools, messages });

// Any SDK — just extract t

Extension points exported contracts — how you extend this code

ZapcodeAdapter (Interface)
(no doc) [1 implementers]
packages/zapcode-ai/src/index.ts
ZapcodeOptions (Interface)
(no doc)
crates/zapcode-js/index.d.ts
ToolDefinition (Interface)
(no doc)
packages/zapcode-ai/src/index.ts
ZapcodeResult (Interface)
(no doc)
crates/zapcode-js/index.d.ts
ParamDef (Interface)
(no doc)
packages/zapcode-ai/src/index.ts
ZapcodeSuspension (Interface)
(no doc)
crates/zapcode-js/index.d.ts
ZapcodeAIOptions (Interface)
(no doc)
packages/zapcode-ai/src/index.ts
TraceSpan (Interface)
(no doc)
packages/zapcode-ai/src/index.ts

Core symbols most depended-on inside this repo

eval_ts
called by 300
crates/zapcode-core/src/vm/mod.rs
push
called by 191
crates/zapcode-core/src/vm/mod.rs
emit
called by 150
crates/zapcode-core/src/compiler/mod.rs
pop
called by 113
crates/zapcode-core/src/vm/mod.rs
to_number
called by 41
crates/zapcode-core/src/value.rs
patch_jump
called by 29
crates/zapcode-core/src/compiler/mod.rs
lower_expr
called by 28
crates/zapcode-core/src/parser/mod.rs
current_offset
called by 27
crates/zapcode-core/src/compiler/mod.rs

Shape

Function 477
Method 121
Class 51
Enum 20
Interface 14

Languages

Rust89%
TypeScript6%
Python5%

Modules by API surface

crates/zapcode-core/tests/builtins.rs68 symbols
crates/zapcode-core/tests/security.rs66 symbols
crates/zapcode-core/tests/async_await.rs50 symbols
crates/zapcode-core/src/vm/mod.rs44 symbols
crates/zapcode-core/src/parser/mod.rs34 symbols
crates/zapcode-core/tests/objects_arrays.rs28 symbols
crates/zapcode-core/src/vm/builtins.rs26 symbols
packages/zapcode-ai/src/index.ts24 symbols
crates/zapcode-core/tests/basic.rs24 symbols
crates/zapcode-core/src/parser/ir.rs22 symbols
packages/zapcode-ai-python/src/zapcode_ai/__init__.py19 symbols
crates/zapcode-core/src/compiler/mod.rs19 symbols

For agents

$ claude mcp add zapcode \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact