TypeScript解释器

Zapcode是一个最小化、安全的TypeScript解释器,专为AI代理设计,提供微秒级启动和安全沙箱执行环境。

  • by TheUncharted
  • Rust
  • 本地部署
  • 94 星标
  • 更新于
在 GitHub 查看 TheUncharted/zapcode

使用说明

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")
}

看 examples/rust/basic/basic.rs 更多。

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

许可证

麻省理工学院