使用说明
Zapcode
Run AI-generated code. Safely. Instantly.
A minimal, secure TypeScript interpreter written in Rust for use by AI agents
实验性 --Zapcode正在积极开发中。API可能会发生变化。
为什么代理应该编写代码
AI代理在以下情况下更有能力 写代码 而不是链接工具调用。代码为代理提供了循环、条件、变量和组合——这些都是工具链模拟不佳的东西。
但运行人工智能生成的代码既危险又缓慢。
Docker增加了200-500ms的冷启动延迟,并需要容器运行时。V8隔离带来了约20MB的二进制和毫秒级启动。两者都不支持快照执行中间功能。
Zapcode采用了一种不同的方法:一个专门构建的TypeScript解释器,从 2微秒,在语言级别强制执行安全沙箱,并可以将执行状态快照为字节以供以后恢复——所有这些都在一个单一的、可嵌入的库中,对Node.js或V8没有依赖关系。
受启发于 蒙蒂Pydantic的Python子集解释器,对Python采用相同的方法。
替代方案
| 语言完整性 | 安全性 | 启动 | 快照 | 安装 | |
|---|---|---|---|---|---|
| Zapcode | TypeScript子集 | 语言级沙盒 | 约2µs | 内置,<2 KB | npm install / pip install |
| Docker+Node.js | 完全Node.js | 容器隔离 | ~200-500ms | 否 | 容器运行时 |
| V8隔离 | 完全JS/TS | 隔离边界 | ~5-50毫秒 | 否 | V8(~20 MB) |
| Deno部署 | 完全TS | 隔离+权限 | ~10-50毫秒 | 否 | 云服务 |
| QuickJS | 完整ES2023 | 进程隔离 | ~1-5毫秒 | 无 | C库 |
| WASI/Wasmer | 取决于访客 | Wasm沙盒 | ~1-10毫秒 | 可能 | Wasm运行时 |
为什么不用Docker?
Docker提供了强大的隔离,但增加了数百毫秒的冷启动延迟,需要容器运行时,并且不支持快照执行状态中间函数。对于执行数千个小代码片段的AI代理循环,开销占主导地位。
为什么不是V8?
V8是JavaScript执行的黄金标准。但它带来了大约20MB的二进制大小、毫秒级的启动时间,以及大量的API表面,必须对其进行严格限制才能进行沙盒操作。如果你需要完全符合ECMAScript,请使用V8。如果你需要微秒级的启动、字节大小的快照,以及一个以“默认阻止”为基础而非事后考虑的安全模型,请使用Zapcode。
基准测试
所有基准测试都运行完整的管道:parse→ 编译→ 执行。没有缓存,没有预热。
| 基准 | Zapcode | Docker+Node.js | V8隔离 |
|---|---|---|---|
简单表达(1 + 2 * 3) |
2.1µs | 约200-500毫秒 | 约5-50毫秒 |
| 变量算术 | 2.8µs | — | — |
| 字符串连接 | 2.6µs | — | — |
| 模板文字 | 2.9µs | — | — |
| 数组创建 | 2.4µs | — | — |
| 对象创建 | 5.2µs | — | — |
| 函数调用 | 4.6µs | — | — |
| Promise.resolve+等待 | 3.1µs | — | — |
| Promise.then(单) | 5.6µs | — | — |
| Promise.then链(×3) | 9.9µs | — | — |
| Promise.all(3个承诺) | 7.4µs | — | — |
异步 .map() (3个元素) |
11.6µs | — | — |
| 循环(100次迭代) | 77.8µs | — | — |
| 斐波那契数列(n=10177次调用) | 138.4µs | — | — |
| 快照大小(典型代理) | <2kb | 不适用 | 不适用 |
| 每次执行的内存 | ~10kb | ~50+MB | ~20+MB |
| 冷启动 | 约2µs | 约200-500毫秒 | 约5-50毫秒 |
没有后台线程,没有GC,没有运行时——CPU使用率与执行的指令成正比。
cargo bench # run benchmarks yourself
安装
Types/JavaScript
npm install @unchartedfr/zapcode # npm / yarn / pnpm / bun
python
pip install zapcode # pip / uv
锈
# Cargo.toml
[dependencies]
zapcode-core = "1.0.0"
WebAssembly
wasm-pack build crates/zapcode-wasm --target web
基本用法
Types/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" }
}
看 examples/typescript/basic/main.ts 更多。
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})
看 examples/python/basic/main.py 更多。
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")
}
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>
看 examples/wasm/basic/index.html 一个完整的游乐场。
AI代理使用
Vercel AI SDK(@uncartedfr/zapcode-AI)
npm install @unchartedfr/zapcode-ai ai @ai-sdk/anthropic # or @ai-sdk/amazon-bedrock, @ai-sdk/openai
推荐的方式——一个电话给你 { system, tools } 直接插入 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?" }],
});
幕后:LLM编写TypeScript代码来调用你的工具→ Zapcode在沙箱中执行它→ 工具调用挂起VM→ your execute 函数在主机上运行→ 结果返回。全部在约2µs的启动时间+工具执行时间内完成。
看 examples/typescript/ai-agent/ai-agent-zapcode-ai.ts 对于完整的工作示例。
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"])
看 examples/typescript/ai-agent/ai-agent-anthropic.ts 和 examples/python/ai-agent/ai_agent_anthropic.py.
Multi-SDK support
zapcode() 通过一次调用返回所有主要AI SDK的适配器:
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 the code from the tool call and pass it to handleToolCall
const result = await handleToolCall(codeFromToolCall);
b = zapcode(tools={...})
b.anthropic_tools # → Anthropic SDK format
b.openai_tools # → OpenAI SDK format
b.handle_tool_call(code) # → Universal handler
Custom adapters
无需分叉Zapcode,即可为任何AI SDK构建自定义适配器:
import { zapcode, createAdapter } from "@unchartedfr/zapcode-ai";
const myAdapter = createAdapter("my-sdk", (ctx) => {
return {
systemMessage: ctx.system,
actions: [{
id: ctx.toolName,
schema: ctx.toolSchema,
run: async (input: { code: string }) => {
return ctx.handleToolCall(input.code);
},
}],
};
});
const { custom } = zapcode({
tools: { ... },
adapters: [myAdapter],
});
const myConfig = custom["my-sdk"];
from zapcode_ai import zapcode, Adapter, AdapterContext
class LangChainAdapter(Adapter):
name = "langchain"
def adapt(self, ctx: AdapterContext):
from langchain_core.tools import StructuredTool
return StructuredTool.from_function(
func=lambda code: ctx.handle_tool_call(code),
name=ctx.tool_name,
description=ctx.tool_description,
)
b = zapcode(tools={...}, adapters=[LangChainAdapter()])
langchain_tool = b.custom["langchain"]
适配器接收 AdapterContext 包含所需的一切:系统提示、工具名称、工具JSON模式和 handleToolCall 功能。返回SDK所期望的任何形状。
自动修复、调试和执行跟踪
自动修复(autoFix)
启用后,执行错误将作为工具结果返回,而不是抛出——让LLM看到错误并在下一步进行自我纠正。
TypeScript:
const { system, tools } = zapcode({
autoFix: true,
tools: { /* ... */ },
});
python
zap = zapcode(auto_fix=True, tools={...})
执行跟踪
每次执行都会生成一个跟踪树,其中包含每个阶段的时间(解析→ 编译→ 执行)。使用 printTrace() / print_trace() 显示完整的会话跟踪,或 getTrace() / get_trace() 以编程方式访问跟踪。
TypeScript:
const { system, tools, printTrace, getTrace } = zapcode({
autoFix: true,
tools: { /* ... */ },
});
// After running...
printTrace();
// ✓ zapcode.session 12.3ms
// ✓ execute_code 8.1ms
// ✓ parse 0.2ms
// ✓ compile 0.1ms
// ✓ execute 7.8ms
const trace = getTrace(); // TraceSpan tree
python
zap = zapcode(auto_fix=True, tools={...})
# After running...
zap.print_trace()
trace = zap.get_trace() # TraceSpan tree
调试日志
有关生成代码、工具调用和输出的详细日志记录,请参阅调试跟踪示例,其中显示了如何检查每个执行步骤:
Zapcode能做什么和不能做什么
可以做到:
- 执行TypeScript的一个有用子集——变量、函数、类、生成器、async/await、闭包、解构、spread/rest、可选链接、null合并、模板文字、try/catch
- 在解析时通过以下方式删除TypeScript类型 oxc --没有
tsc需要 - 快照执行到字节,稍后恢复,即使在不同的进程或机器中也是如此
- 从Rust、Node.js、Python或WebAssembly调用
- 跟踪和限制资源——内存、分配、堆栈深度和挂钟时间
- 30+字符串方法,25+数组方法,以及Math、JSON、Object和Promise内置函数
- 异步回调
.map()和.forEach()--每个await按顺序挂起和恢复VM
无法执行以下操作:
- 运行任意npm包或完整的Node.js标准库
- 执行正则表达式(支持解析,不执行)
- 提供完整
Promise语义学(Promise.race等等)--.then(),.catch(),.finally(),以及Promise.all支持 - 运行需要以下条件的代码
this在非课堂环境中
这些是有意的约束,而不是bug。Zapcode针对一个用例: 运行由AI代理编写的代码 在一个安全的、可嵌入的沙箱中。
支持的语法
| 功能 | 状态 |
|---|---|
变量(const, let) |
支持 |
| 函数(声明、箭头、表达式) | 支持 |
课程(constructor方法, extends, super, static) |
支持 |
发电机(function*, yield, .next()) |
支持 |
| 异步/等待 | 支持 |
控制流程(if, for, while, do-while, switch, for-of) |
支持 |
尝试/抓住/终于, throw |
支持 |
| 带有可变捕获的闭包 | 支持 |
| 解构(对象和数组) | 支持 |
| Spread/rest运算符 | 支持 |
可选链接(?.) |
支持 |
无效凝聚(??) |
支持 |
| 模板文字 | 支持 |
| 类型注释、接口、类型别名 | 在解析时被剥离 |
| 字符串方法(30+) | 支持 |
数组方法(25+,包括 map, filter, reduce) |
支持 |
异步回调 .map(), .forEach() |
支持 |
| 数学、JSON、对象、Promise | 支持 |
import / require / eval |
已阻止(沙盒) |
| 正则表达式 | 已解析,未执行 |
var 声明 |
不支持(使用 let/const) |
| 装饰器 | 不支持 |
Symbol, WeakMap, WeakSet |
不支持 |
安全
运行人工智能生成的代码本质上是危险的。与在操作系统级别隔离的Docker不同,Zapcode在 语言水平 --没有容器,没有进程边界,没有系统调用过滤器。沙盒的构造必须正确,而不是配置。
默认情况下拒绝沙盒
来宾代码在字节码VM内运行,无法访问主机:
| 封锁 | 如何封锁 |
|---|---|
文件系统(fs, path) |
没有 std::fs 在岩芯箱中 |
网络(net, http, fetch) |
没有 std::net 在岩芯箱中 |
环境(process.env, os) |
没有 std::env 在岩芯箱中 |
eval, Function(),动态导入 |
解析时被阻止 |
import, require |
解析时被阻止 |
globalThis, global |
解析时被阻止 |
| 原型污染 | 不适用-对象是普通的 IndexMap 价值观 |
这 仅 escape-chatch是您显式注册的外部函数。当客户机代码调用一个时,VM会挂起并返回一个快照——您的代码会解析调用,而不是客户机。
资源限制
| 限制 | 默认值 | 可配置 |
|---|---|---|
| 内存 | 32 MB | memory_limit_bytes |
| 执行时间 | 5秒 | time_limit_ms |
| 调用堆栈深度 | 512帧 | max_stack_depth |
| 堆分配 | 100000 | max_allocations |
零 unsafe 代码
这 zapcode-core 板条箱包含 零 unsafe 块内存安全由Rust编译器保证。
Adversarial test suite — 65 tests across 19 attack categories
| 攻击类别 | 测试 | 结果 |
|---|---|---|
原型污染(Object.prototype, __proto__) |
4 | 已阻止 |
施工人员链式逃生(({}).constructor.constructor(...)) |
3 | 已阻止 |
eval, Function(),间接求值,动态导入 |
5 | 解析时被阻止 |
globalThis, process, require, import |
6 | 解析时被阻止 |
| 堆栈溢出(直接+相互递归) | 2 | 被堆栈深度限制捕获 |
| 内存耗尽(巨大数组,字符串加倍) | 4 | 被分配限制捕获 |
无限循环(while(true), for(;;)) |
2 | 受时间/分配限制 |
| JSON炸弹(深度嵌套,巨大有效载荷) | 2 | 深度有限(最多64个) |
稀疏阵列攻击(arr[1e9], arr[MAX_SAFE_INTEGER]) |
3 | 增长上限(最大+1024) |
| toString/value强制期间的劫持 | 3 | 未被调用(按设计) |
| 被阻止关键字的Unicode转义 | 2 | 被阻止 |
| 计算属性访问技巧 | 2 | 返回未定义 |
定时侧通道(performance.now) |
1 | 已阻止 |
| 错误消息信息泄漏 | 3 | 未暴露主机路径/env |
| 类型混淆攻击 | 4 | 正确的TypeError |
| Promise/Generator内部滥用 | 4 | 无法逃脱 |
| 负数组索引 | 2 | 返回未定义 |
setTimeout, setInterval, Proxy, Reflect |
6 | 已阻止 |
with 声明, arguments.callee |
3 | 已阻止 |
cargo test -p zapcode-core --test security # run the security tests
已知限制:
Object.freeze()尚未实现——冻结的对象仍然可以进行变异(正确性差距,而不是沙盒逃逸)- 用户定义的
toString()/valueOf()在隐式类型强制过程中不调用(故意——防止注入)
建筑
TypeScript source
│
▼
┌─────────┐ oxc_parser (fastest TS parser in Rust)
│ Parse │──────────────────────────────────────────► Strip types
└────┬────┘
▼
┌─────────┐
│ IR │ ZapcodeIR (statements, expressions, operators)
└────┬────┘
▼
┌─────────┐
│ Compile │ Stack-based bytecode (~50 instructions)
└────┬────┘
▼
┌─────────┐
│ VM │ Execute, snapshot at external calls, resume later
└────┬────┘
▼
Result / Suspended { snapshot }
贡献
git clone https://github.com/TheUncharted/zapcode.git
cd zapcode
./scripts/dev-setup.sh # installs toolchain, builds, runs tests
许可证
麻省理工学院
