@unchartedfr/zapcode-ai 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 ADDED
@@ -0,0 +1,595 @@
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 agents</p>
5
+ </p>
6
+
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=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>
10
+ <a href="https://www.npmjs.com/package/@unchartedfr/zapcode"><img src="https://img.shields.io/npm/v/@unchartedfr/zapcode" alt="npm"></a>
11
+ <a href="https://pypi.org/project/zapcode/"><img src="https://img.shields.io/pypi/v/zapcode" alt="PyPI"></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>
13
+ </p>
14
+
15
+ ---
16
+
17
+ > **Experimental** — Zapcode is under active development. APIs may change.
18
+
19
+ ## Why agents should write code
20
+
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.
22
+
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.
54
+
55
+ ## Benchmarks
56
+
57
+ All benchmarks run the full pipeline: parse → compile → execute. No caching, no warm-up.
58
+
59
+ | Benchmark | Zapcode | Docker + Node.js | V8 Isolate |
60
+ |---|---|---|---|
61
+ | Simple expression (`1 + 2 * 3`) | **2.1 µs** | ~200-500 ms | ~5-50 ms |
62
+ | Variable arithmetic | **2.8 µs** | — | — |
63
+ | String concatenation | **2.6 µs** | — | — |
64
+ | Template literal | **2.9 µs** | — | — |
65
+ | Array creation | **2.4 µs** | — | — |
66
+ | Object creation | **5.2 µs** | — | — |
67
+ | Function call | **4.6 µs** | — | — |
68
+ | Loop (100 iterations) | **77.8 µs** | — | — |
69
+ | Fibonacci (n=10, 177 calls) | **138.4 µs** | — | — |
70
+ | Snapshot size (typical agent) | **< 2 KB** | N/A | N/A |
71
+ | Memory per execution | **~10 KB** | ~50+ MB | ~20+ MB |
72
+ | Cold start | **~2 µs** | ~200-500 ms | ~5-50 ms |
73
+
74
+ No background thread, no GC, no runtime — CPU usage is exactly proportional to the instructions executed.
75
+
76
+ ```bash
77
+ cargo bench # run benchmarks yourself
78
+ ```
79
+
80
+ ## Installation
81
+
82
+ **TypeScript / JavaScript**
83
+ ```bash
84
+ npm install @unchartedfr/zapcode # npm / yarn / pnpm / bun
85
+ ```
86
+
87
+ **Python**
88
+ ```bash
89
+ pip install zapcode # pip / uv
90
+ ```
91
+
92
+ **Rust**
93
+ ```toml
94
+ # Cargo.toml
95
+ [dependencies]
96
+ zapcode-core = "1.0.0"
97
+ ```
98
+
99
+ **WebAssembly**
100
+ ```bash
101
+ wasm-pack build crates/zapcode-wasm --target web
102
+ ```
103
+
104
+ ## Basic Usage
105
+
106
+ ### TypeScript / JavaScript
107
+
108
+ ```typescript
109
+ import { Zapcode, ZapcodeSnapshotHandle } from '@unchartedfr/zapcode';
110
+
111
+ // Simple expression
112
+ const b = new Zapcode('1 + 2 * 3');
113
+ console.log(b.run().output); // 7
114
+
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);
121
+
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
+ }
146
+ ```
147
+
148
+ See [`examples/typescript/basic.ts`](examples/typescript/basic.ts) for more.
149
+
150
+ ### Python
151
+
152
+ ```python
153
+ from zapcode import Zapcode, ZapcodeSnapshot
154
+
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})
183
+ ```
184
+
185
+ See [`examples/python/basic.py`](examples/python/basic.py) for more.
186
+
187
+ <details>
188
+ <summary><strong>Rust</strong></summary>
189
+
190
+ ```rust
191
+ use zapcode_core::{ZapcodeRun, Value, ResourceLimits, VmState};
192
+
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));
199
+
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
+ )?;
208
+
209
+ let state = runner.start(vec![
210
+ ("city".to_string(), Value::String("London".into())),
211
+ ])?;
212
+
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
+ }
221
+ ```
222
+
223
+ See [`examples/rust/basic.rs`](examples/rust/basic.rs) for more.
224
+ </details>
225
+
226
+ <details>
227
+ <summary><strong>WebAssembly (browser)</strong></summary>
228
+
229
+ ```html
230
+ <script type="module">
231
+ import init, { Zapcode } from './zapcode-wasm/zapcode_wasm.js';
232
+
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>
242
+ ```
243
+
244
+ See [`examples/wasm/index.html`](examples/wasm/index.html) for a full playground.
245
+ </details>
246
+
247
+ ## AI Agent Usage
248
+
249
+ ### Vercel AI SDK (@unchartedfr/zapcode-ai)
250
+
251
+ The recommended way — one call gives you `{ system, tools }` that plug directly into `generateText` / `streamText`:
252
+
253
+ ```typescript
254
+ import { zapcode } from "@unchartedfr/zapcode-ai";
255
+ import { generateText } from "ai";
256
+ import { anthropic } from "@ai-sdk/anthropic";
257
+
258
+ const { system, tools } = zapcode({
259
+ system: "You are a helpful travel assistant.",
260
+ tools: {
261
+ getWeather: {
262
+ description: "Get current weather for a city",
263
+ parameters: { city: { type: "string", description: "City name" } },
264
+ execute: async ({ city }) => {
265
+ const res = await fetch(`https://api.weather.com/${city}`);
266
+ return res.json();
267
+ },
268
+ },
269
+ searchFlights: {
270
+ description: "Search flights between two cities",
271
+ parameters: {
272
+ from: { type: "string" },
273
+ to: { type: "string" },
274
+ date: { type: "string" },
275
+ },
276
+ execute: async ({ from, to, date }) => {
277
+ return flightAPI.search(from, to, date);
278
+ },
279
+ },
280
+ },
281
+ });
282
+
283
+ // Works with any AI SDK model — Anthropic, OpenAI, Google, etc.
284
+ const { text } = await generateText({
285
+ model: anthropic("claude-sonnet-4-20250514"),
286
+ system,
287
+ tools,
288
+ messages: [{ role: "user", content: "Weather in Tokyo and cheapest flight from London?" }],
289
+ });
290
+ ```
291
+
292
+ 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.
293
+
294
+ See [`examples/typescript/ai-agent-zapcode-ai.ts`](examples/typescript/ai-agent-zapcode-ai.ts) for the full working example.
295
+
296
+ <details>
297
+ <summary><strong>Anthropic SDK</strong></summary>
298
+
299
+ **TypeScript:**
300
+
301
+ ```typescript
302
+ import Anthropic from "@anthropic-ai/sdk";
303
+ import { Zapcode, ZapcodeSnapshotHandle } from "@unchartedfr/zapcode";
304
+
305
+ const tools = {
306
+ getWeather: async (city: string) => {
307
+ const res = await fetch(`https://api.weather.com/${city}`);
308
+ return res.json();
309
+ },
310
+ };
311
+
312
+ const client = new Anthropic();
313
+ const response = await client.messages.create({
314
+ model: "claude-sonnet-4-20250514",
315
+ max_tokens: 1024,
316
+ system: `Write TypeScript to answer the user's question.
317
+ Available functions (use await): getWeather(city: string) → { condition, temp }
318
+ Last expression = output. No markdown fences.`,
319
+ messages: [{ role: "user", content: "What's the weather in Tokyo?" }],
320
+ });
321
+
322
+ const code = response.content[0].type === "text" ? response.content[0].text : "";
323
+
324
+ // Execute + resolve tool calls via snapshot/resume
325
+ const sandbox = new Zapcode(code, { externalFunctions: ["getWeather"] });
326
+ let state = sandbox.start();
327
+ while (!state.completed) {
328
+ const result = await tools[state.functionName](...state.args);
329
+ state = ZapcodeSnapshotHandle.load(state.snapshot).resume(result);
330
+ }
331
+ console.log(state.output);
332
+ ```
333
+
334
+ **Python:**
335
+
336
+ ```python
337
+ import anthropic
338
+ from zapcode import Zapcode
339
+
340
+ client = anthropic.Anthropic()
341
+ response = client.messages.create(
342
+ model="claude-sonnet-4-20250514",
343
+ max_tokens=1024,
344
+ system="""Write TypeScript to answer the user's question.
345
+ Available functions (use await): getWeather(city: string) → { condition, temp }
346
+ Last expression = output. No markdown fences.""",
347
+ messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
348
+ )
349
+ code = response.content[0].text
350
+
351
+ sandbox = Zapcode(code, external_functions=["getWeather"])
352
+ state = sandbox.start()
353
+ while state.get("suspended"):
354
+ result = get_weather(*state["args"])
355
+ state = state["snapshot"].resume(result)
356
+ print(state["output"])
357
+ ```
358
+
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).
360
+ </details>
361
+
362
+ <details>
363
+ <summary><strong>Multi-SDK support</strong></summary>
364
+
365
+ `zapcode()` returns adapters for all major AI SDKs from a single call:
366
+
367
+ ```typescript
368
+ const { system, tools, openaiTools, anthropicTools, handleToolCall } = zapcode({
369
+ tools: { getWeather: { ... } },
370
+ });
371
+
372
+ // Vercel AI SDK
373
+ await generateText({ model: anthropic("claude-sonnet-4-20250514"), system, tools, messages });
374
+
375
+ // OpenAI SDK
376
+ await openai.chat.completions.create({
377
+ messages: [{ role: "system", content: system }, ...userMessages],
378
+ tools: openaiTools,
379
+ });
380
+
381
+ // Anthropic SDK
382
+ await anthropic.messages.create({ system, tools: anthropicTools, messages });
383
+
384
+ // Any SDK — just extract the code from the tool call and pass it to handleToolCall
385
+ const result = await handleToolCall(codeFromToolCall);
386
+ ```
387
+
388
+ ```python
389
+ b = zapcode(tools={...})
390
+ b.anthropic_tools # → Anthropic SDK format
391
+ b.openai_tools # → OpenAI SDK format
392
+ b.handle_tool_call(code) # → Universal handler
393
+ ```
394
+ </details>
395
+
396
+ <details>
397
+ <summary><strong>Custom adapters</strong></summary>
398
+
399
+ Build a custom adapter for any AI SDK without forking Zapcode:
400
+
401
+ ```typescript
402
+ import { zapcode, createAdapter } from "@unchartedfr/zapcode-ai";
403
+
404
+ const myAdapter = createAdapter("my-sdk", (ctx) => {
405
+ return {
406
+ systemMessage: ctx.system,
407
+ actions: [{
408
+ id: ctx.toolName,
409
+ schema: ctx.toolSchema,
410
+ run: async (input: { code: string }) => {
411
+ return ctx.handleToolCall(input.code);
412
+ },
413
+ }],
414
+ };
415
+ });
416
+
417
+ const { custom } = zapcode({
418
+ tools: { ... },
419
+ adapters: [myAdapter],
420
+ });
421
+
422
+ const myConfig = custom["my-sdk"];
423
+ ```
424
+
425
+ ```python
426
+ from zapcode_ai import zapcode, Adapter, AdapterContext
427
+
428
+ class LangChainAdapter(Adapter):
429
+ name = "langchain"
430
+
431
+ def adapt(self, ctx: AdapterContext):
432
+ from langchain_core.tools import StructuredTool
433
+ return StructuredTool.from_function(
434
+ func=lambda code: ctx.handle_tool_call(code),
435
+ name=ctx.tool_name,
436
+ description=ctx.tool_description,
437
+ )
438
+
439
+ b = zapcode(tools={...}, adapters=[LangChainAdapter()])
440
+ langchain_tool = b.custom["langchain"]
441
+ ```
442
+
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.
444
+ </details>
445
+
446
+ ## What Zapcode Can and Cannot Do
447
+
448
+ **Can do:**
449
+
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
456
+
457
+ **Cannot do:**
458
+
459
+ - Run arbitrary npm packages or the full Node.js standard library
460
+ - Execute regular expressions (parsing supported, execution is a no-op)
461
+ - Provide full `Promise` semantics (`.then()` chains, `Promise.race`, etc.)
462
+ - Run code that requires `this` in non-class contexts
463
+
464
+ These are intentional constraints, not bugs. Zapcode targets one use case: **running code written by AI agents** inside a secure, embeddable sandbox.
465
+
466
+ ## Supported Syntax
467
+
468
+ | Feature | Status |
469
+ |---|---|
470
+ | Variables (`const`, `let`) | Supported |
471
+ | Functions (declarations, arrows, expressions) | Supported |
472
+ | Classes (`constructor`, methods, `extends`, `super`, `static`) | Supported |
473
+ | Generators (`function*`, `yield`, `.next()`) | Supported |
474
+ | Async/await | Supported |
475
+ | Control flow (`if`, `for`, `while`, `do-while`, `switch`, `for-of`) | Supported |
476
+ | Try/catch/finally, `throw` | Supported |
477
+ | Closures with mutable capture | Supported |
478
+ | Destructuring (object and array) | Supported |
479
+ | Spread/rest operators | Supported |
480
+ | Optional chaining (`?.`) | Supported |
481
+ | Nullish coalescing (`??`) | Supported |
482
+ | Template literals | Supported |
483
+ | Type annotations, interfaces, type aliases | Stripped at parse time |
484
+ | String methods (30+) | Supported |
485
+ | Array methods (25+, including `map`, `filter`, `reduce`) | Supported |
486
+ | Math, JSON, Object, Promise | Supported |
487
+ | `import` / `require` / `eval` | Blocked (sandbox) |
488
+ | Regular expressions | Parsed, not executed |
489
+ | `var` declarations | Not supported (use `let`/`const`) |
490
+ | Decorators | Not supported |
491
+ | `Symbol`, `WeakMap`, `WeakSet` | Not supported |
492
+
493
+ ## Security
494
+
495
+ Running AI-generated code is inherently dangerous. Unlike Docker, which isolates at the OS level, Zapcode isolates at the **language level** — no container, no process boundary, no syscall filter. The sandbox must be correct by construction, not by configuration.
496
+
497
+ ### Deny-by-default sandbox
498
+
499
+ Guest code runs inside a bytecode VM with no access to the host:
500
+
501
+ | Blocked | How |
502
+ |---|---|
503
+ | Filesystem (`fs`, `path`) | No `std::fs` in the core crate |
504
+ | Network (`net`, `http`, `fetch`) | No `std::net` in the core crate |
505
+ | Environment (`process.env`, `os`) | No `std::env` in the core crate |
506
+ | `eval`, `Function()`, dynamic import | Blocked at parse time |
507
+ | `import`, `require` | Blocked at parse time |
508
+ | `globalThis`, `global` | Blocked at parse time |
509
+ | Prototype pollution | Not applicable — objects are plain `IndexMap` values |
510
+
511
+ The **only** escape hatch is external functions that you explicitly register. When guest code calls one, the VM suspends and returns a snapshot — your code resolves the call, not the guest.
512
+
513
+ ### Resource limits
514
+
515
+ | Limit | Default | Configurable |
516
+ |---|---|---|
517
+ | Memory | 32 MB | `memory_limit_bytes` |
518
+ | Execution time | 5 seconds | `time_limit_ms` |
519
+ | Call stack depth | 512 frames | `max_stack_depth` |
520
+ | Heap allocations | 100,000 | `max_allocations` |
521
+
522
+ ### Zero `unsafe` code
523
+
524
+ The `zapcode-core` crate contains **zero `unsafe` blocks**. Memory safety is guaranteed by the Rust compiler.
525
+
526
+ <details>
527
+ <summary><strong>Adversarial test suite — 65 tests across 19 attack categories</strong></summary>
528
+
529
+ | Attack category | Tests | Result |
530
+ |---|---|---|
531
+ | Prototype pollution (`Object.prototype`, `__proto__`) | 4 | Blocked |
532
+ | Constructor chain escapes (`({}).constructor.constructor(...)`) | 3 | Blocked |
533
+ | `eval`, `Function()`, indirect eval, dynamic import | 5 | Blocked at parse time |
534
+ | `globalThis`, `process`, `require`, `import` | 6 | Blocked at parse time |
535
+ | Stack overflow (direct + mutual recursion) | 2 | Caught by stack depth limit |
536
+ | Memory exhaustion (huge arrays, string doubling) | 4 | Caught by allocation limit |
537
+ | Infinite loops (`while(true)`, `for(;;)`) | 2 | Caught by time/allocation limit |
538
+ | JSON bombs (deep nesting, huge payloads) | 2 | Depth-limited (max 64) |
539
+ | Sparse array attacks (`arr[1e9]`, `arr[MAX_SAFE_INTEGER]`) | 3 | Capped growth (max +1024) |
540
+ | toString/valueOf hijacking during coercion | 3 | Not invoked (by design) |
541
+ | Unicode escapes for blocked keywords | 2 | Blocked |
542
+ | Computed property access tricks | 2 | Returns undefined |
543
+ | Timing side channels (`performance.now`) | 1 | Blocked |
544
+ | Error message information leakage | 3 | No host paths/env exposed |
545
+ | Type confusion attacks | 4 | Proper TypeError |
546
+ | Promise/Generator internal abuse | 4 | No escape |
547
+ | Negative array indices | 2 | Returns undefined |
548
+ | `setTimeout`, `setInterval`, `Proxy`, `Reflect` | 6 | Blocked |
549
+ | `with` statement, `arguments.callee` | 3 | Blocked |
550
+
551
+ ```bash
552
+ cargo test -p zapcode-core --test security # run the security tests
553
+ ```
554
+
555
+ **Known limitations:**
556
+ - `Object.freeze()` is not yet implemented — frozen objects can still be mutated (correctness gap, not a sandbox escape)
557
+ - User-defined `toString()`/`valueOf()` are not called during implicit type coercion (intentional — prevents injection)
558
+ </details>
559
+
560
+ ## Architecture
561
+
562
+ ```
563
+ TypeScript source
564
+
565
+
566
+ ┌─────────┐ oxc_parser (fastest TS parser in Rust)
567
+ │ Parse │──────────────────────────────────────────► Strip types
568
+ └────┬────┘
569
+
570
+ ┌─────────┐
571
+ │ IR │ ZapcodeIR (statements, expressions, operators)
572
+ └────┬────┘
573
+
574
+ ┌─────────┐
575
+ │ Compile │ Stack-based bytecode (~50 instructions)
576
+ └────┬────┘
577
+
578
+ ┌─────────┐
579
+ │ VM │ Execute, snapshot at external calls, resume later
580
+ └────┬────┘
581
+
582
+ Result / Suspended { snapshot }
583
+ ```
584
+
585
+ ## Contributing
586
+
587
+ ```bash
588
+ git clone https://github.com/TheUncharted/zapcode.git
589
+ cd zapcode
590
+ ./scripts/dev-setup.sh # installs toolchain, builds, runs tests
591
+ ```
592
+
593
+ ## License
594
+
595
+ MIT
@@ -0,0 +1,266 @@
1
+ /**
2
+ * @unchartedfr/zapcode-ai — High-level AI SDK integration for Zapcode.
3
+ *
4
+ * Works with any AI SDK:
5
+ *
6
+ * ```typescript
7
+ * // Vercel AI SDK (recommended)
8
+ * import { zapcode } from "@unchartedfr/zapcode-ai";
9
+ * const { system, tools } = zapcode({ tools: { ... } });
10
+ * await generateText({ model, system, tools, messages });
11
+ *
12
+ * // OpenAI SDK
13
+ * import { zapcode } from "@unchartedfr/zapcode-ai";
14
+ * const { system, openaiTools, handleToolCall } = zapcode({ tools: { ... } });
15
+ * const response = await openai.chat.completions.create({
16
+ * messages: [{ role: "system", content: system }, ...],
17
+ * tools: openaiTools,
18
+ * });
19
+ *
20
+ * // Anthropic SDK
21
+ * import { zapcode } from "@unchartedfr/zapcode-ai";
22
+ * const { system, anthropicTools, handleToolCall } = zapcode({ tools: { ... } });
23
+ * const response = await anthropic.messages.create({
24
+ * system, tools: anthropicTools, messages,
25
+ * });
26
+ * ```
27
+ */
28
+ /** Definition for a single tool that guest code can call. */
29
+ export interface ToolDefinition {
30
+ /** Human-readable description shown to the LLM. */
31
+ description: string;
32
+ /** Parameter schema — keys are parameter names. */
33
+ parameters: Record<string, ParamDef>;
34
+ /** The actual implementation. Called when guest code invokes this tool. */
35
+ execute: (args: Record<string, unknown>) => unknown | Promise<unknown>;
36
+ }
37
+ /** Schema for a single parameter. */
38
+ export interface ParamDef {
39
+ type: "string" | "number" | "boolean" | "object" | "array";
40
+ description?: string;
41
+ optional?: boolean;
42
+ }
43
+ /** Configuration for the `zapcode()` wrapper. */
44
+ export interface ZapcodeAIOptions {
45
+ /** Tools available to guest code. */
46
+ tools: Record<string, ToolDefinition>;
47
+ /** Extra system prompt to prepend (optional). */
48
+ system?: string;
49
+ /** Memory limit in MB (default: 32). */
50
+ memoryLimitMb?: number;
51
+ /** Execution time limit in ms (default: 10000). */
52
+ timeLimitMs?: number;
53
+ /** Custom adapters for additional AI SDKs. */
54
+ adapters?: ZapcodeAdapter[];
55
+ }
56
+ /** Result of executing guest code. */
57
+ export interface ExecutionResult {
58
+ output: unknown;
59
+ stdout: string;
60
+ toolCalls: Array<{
61
+ name: string;
62
+ args: unknown[];
63
+ result: unknown;
64
+ }>;
65
+ }
66
+ /** What `zapcode()` returns — adapters for every major AI SDK. */
67
+ export interface ZapcodeAIResult {
68
+ /** System prompt instructing the LLM to write TypeScript. */
69
+ system: string;
70
+ /**
71
+ * Vercel AI SDK tool format.
72
+ * Use with `generateText({ tools })` or `streamText({ tools })`.
73
+ */
74
+ tools: Record<string, VercelAITool>;
75
+ /**
76
+ * OpenAI SDK tool format.
77
+ * Use with `openai.chat.completions.create({ tools: openaiTools })`.
78
+ */
79
+ openaiTools: OpenAITool[];
80
+ /**
81
+ * Anthropic SDK tool format.
82
+ * Use with `anthropic.messages.create({ tools: anthropicTools })`.
83
+ */
84
+ anthropicTools: AnthropicTool[];
85
+ /**
86
+ * Execute code from a tool call response.
87
+ * Works with any SDK — just extract the `code` argument from the
88
+ * `execute_code` tool call and pass it here.
89
+ */
90
+ handleToolCall: (code: string) => Promise<ExecutionResult>;
91
+ /**
92
+ * Output from custom adapters, keyed by adapter name.
93
+ * Access with `result.custom["my-adapter-name"]`.
94
+ */
95
+ custom: Record<string, unknown>;
96
+ }
97
+ /** Vercel AI SDK tool shape. */
98
+ export interface VercelAITool {
99
+ description: string;
100
+ parameters: {
101
+ type: "object";
102
+ properties: Record<string, unknown>;
103
+ required: string[];
104
+ };
105
+ execute: (args: {
106
+ code: string;
107
+ }) => Promise<ExecutionResult>;
108
+ }
109
+ /** OpenAI SDK tool shape. */
110
+ export interface OpenAITool {
111
+ type: "function";
112
+ function: {
113
+ name: string;
114
+ description: string;
115
+ parameters: {
116
+ type: "object";
117
+ properties: Record<string, unknown>;
118
+ required: string[];
119
+ };
120
+ };
121
+ }
122
+ /** Anthropic SDK tool shape. */
123
+ export interface AnthropicTool {
124
+ name: string;
125
+ description: string;
126
+ input_schema: {
127
+ type: "object";
128
+ properties: Record<string, unknown>;
129
+ required: string[];
130
+ };
131
+ }
132
+ /**
133
+ * Create AI SDK-compatible system prompt and tools for Zapcode.
134
+ *
135
+ * Returns adapters for every major AI SDK:
136
+ * - `tools` → Vercel AI SDK (`generateText`, `streamText`)
137
+ * - `openaiTools` → OpenAI SDK (`chat.completions.create`)
138
+ * - `anthropicTools` → Anthropic SDK (`messages.create`)
139
+ * - `handleToolCall(code)` → Universal handler for any SDK
140
+ *
141
+ * @example
142
+ * ```typescript
143
+ * // Vercel AI SDK
144
+ * const { system, tools } = zapcode({ tools: { getWeather: { ... } } });
145
+ * await generateText({ model, system, tools, messages });
146
+ *
147
+ * // OpenAI SDK
148
+ * const { system, openaiTools, handleToolCall } = zapcode({ tools: { ... } });
149
+ * const res = await openai.chat.completions.create({
150
+ * messages: [{ role: "system", content: system }, ...],
151
+ * tools: openaiTools,
152
+ * });
153
+ * const code = res.choices[0].message.tool_calls[0].function.arguments;
154
+ * const result = await handleToolCall(JSON.parse(code).code);
155
+ *
156
+ * // Anthropic SDK
157
+ * const { system, anthropicTools, handleToolCall } = zapcode({ tools: { ... } });
158
+ * const res = await anthropic.messages.create({
159
+ * system, tools: anthropicTools, messages,
160
+ * });
161
+ * const toolUse = res.content.find(b => b.type === "tool_use");
162
+ * const result = await handleToolCall(toolUse.input.code);
163
+ * ```
164
+ */
165
+ export declare function zapcode(options: ZapcodeAIOptions): ZapcodeAIResult;
166
+ /**
167
+ * Adapter interface for integrating Zapcode with any AI SDK.
168
+ *
169
+ * Implement this to add support for a new SDK. Your adapter receives
170
+ * the system prompt, tool description/schema, and a `handleToolCall`
171
+ * function, and returns whatever shape your SDK needs.
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * import { zapcode, createAdapter, ZapcodeAdapter } from "@unchartedfr/zapcode-ai";
176
+ *
177
+ * // Example: adapter for a hypothetical SDK
178
+ * const myAdapter: ZapcodeAdapter<MySDKConfig> = {
179
+ * name: "my-sdk",
180
+ * adapt({ system, toolDescription, toolSchema, handleToolCall }) {
181
+ * return {
182
+ * systemMessage: system,
183
+ * actions: [{
184
+ * id: "execute_code",
185
+ * desc: toolDescription,
186
+ * schema: toolSchema,
187
+ * run: async (input) => handleToolCall(input.code),
188
+ * }],
189
+ * };
190
+ * },
191
+ * };
192
+ *
193
+ * const { system, tools, custom } = zapcode({
194
+ * tools: { ... },
195
+ * adapters: [myAdapter],
196
+ * });
197
+ *
198
+ * const myConfig = custom["my-sdk"]; // typed as MySDKConfig
199
+ * ```
200
+ */
201
+ export interface ZapcodeAdapter<TOutput = unknown> {
202
+ /** Unique name for this adapter (used as key in `custom` output). */
203
+ name: string;
204
+ /** Transform Zapcode's tool definition into your SDK's format. */
205
+ adapt(context: AdapterContext): TOutput;
206
+ }
207
+ /** Context passed to adapters. */
208
+ export interface AdapterContext {
209
+ /** The generated system prompt. */
210
+ system: string;
211
+ /** Tool name (always "execute_code"). */
212
+ toolName: string;
213
+ /** Human-readable tool description. */
214
+ toolDescription: string;
215
+ /** JSON Schema for the tool parameters. */
216
+ toolSchema: {
217
+ type: "object";
218
+ properties: Record<string, unknown>;
219
+ required: string[];
220
+ };
221
+ /** Execute code in the sandbox. Pass the `code` string from the tool call. */
222
+ handleToolCall: (code: string) => Promise<ExecutionResult>;
223
+ }
224
+ /**
225
+ * Helper to create a typed adapter.
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * const langchainAdapter = createAdapter("langchain", (ctx) => {
230
+ * return new DynamicStructuredTool({
231
+ * name: ctx.toolName,
232
+ * description: ctx.toolDescription,
233
+ * func: async ({ code }) => JSON.stringify(await ctx.handleToolCall(code)),
234
+ * });
235
+ * });
236
+ * ```
237
+ */
238
+ export declare function createAdapter<TOutput>(name: string, adapt: (context: AdapterContext) => TOutput): ZapcodeAdapter<TOutput>;
239
+ /**
240
+ * Execute TypeScript code directly in a Zapcode sandbox with tool resolution.
241
+ *
242
+ * This is the lower-level API if you don't need AI SDK integration — you
243
+ * provide the code yourself and Zapcode executes it with tool calls resolved.
244
+ *
245
+ * @example
246
+ * ```typescript
247
+ * import { execute } from "@unchartedfr/zapcode-ai";
248
+ *
249
+ * const result = await execute(
250
+ * `const w = await getWeather("Tokyo"); w.temp`,
251
+ * {
252
+ * getWeather: {
253
+ * description: "Get weather",
254
+ * parameters: { city: { type: "string" } },
255
+ * execute: async ({ city }) => ({ temp: 26, condition: "Clear" }),
256
+ * },
257
+ * },
258
+ * );
259
+ * console.log(result.output); // 26
260
+ * ```
261
+ */
262
+ export declare function execute(code: string, tools: Record<string, ToolDefinition>, options?: {
263
+ memoryLimitMb?: number;
264
+ timeLimitMs?: number;
265
+ }): Promise<ExecutionResult>;
266
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,254 @@
1
+ /**
2
+ * @unchartedfr/zapcode-ai — High-level AI SDK integration for Zapcode.
3
+ *
4
+ * Works with any AI SDK:
5
+ *
6
+ * ```typescript
7
+ * // Vercel AI SDK (recommended)
8
+ * import { zapcode } from "@unchartedfr/zapcode-ai";
9
+ * const { system, tools } = zapcode({ tools: { ... } });
10
+ * await generateText({ model, system, tools, messages });
11
+ *
12
+ * // OpenAI SDK
13
+ * import { zapcode } from "@unchartedfr/zapcode-ai";
14
+ * const { system, openaiTools, handleToolCall } = zapcode({ tools: { ... } });
15
+ * const response = await openai.chat.completions.create({
16
+ * messages: [{ role: "system", content: system }, ...],
17
+ * tools: openaiTools,
18
+ * });
19
+ *
20
+ * // Anthropic SDK
21
+ * import { zapcode } from "@unchartedfr/zapcode-ai";
22
+ * const { system, anthropicTools, handleToolCall } = zapcode({ tools: { ... } });
23
+ * const response = await anthropic.messages.create({
24
+ * system, tools: anthropicTools, messages,
25
+ * });
26
+ * ```
27
+ */
28
+ import { Zapcode, ZapcodeSnapshotHandle } from "@unchartedfr/zapcode";
29
+ import { jsonSchema, tool } from "ai";
30
+ // ---------------------------------------------------------------------------
31
+ // System prompt generation
32
+ // ---------------------------------------------------------------------------
33
+ function generateSignature(name, def) {
34
+ const params = Object.entries(def.parameters)
35
+ .map(([pName, pDef]) => {
36
+ const opt = pDef.optional ? "?" : "";
37
+ return `${pName}${opt}: ${pDef.type}`;
38
+ })
39
+ .join(", ");
40
+ return `${name}(${params})`;
41
+ }
42
+ function buildSystemPrompt(tools, userSystem) {
43
+ const toolDocs = Object.entries(tools)
44
+ .map(([name, def]) => `- await ${generateSignature(name, def)}\n ${def.description}`)
45
+ .join("\n");
46
+ const parts = [];
47
+ if (userSystem) {
48
+ parts.push(userSystem);
49
+ }
50
+ parts.push(`When you need to use tools or compute something, write TypeScript code and pass it to the execute_code tool.
51
+ The code runs in a sandboxed interpreter with these functions available (use await):
52
+
53
+ ${toolDocs}
54
+
55
+ Rules:
56
+ - Write ONLY TypeScript code, no markdown fences, no explanation.
57
+ - The last expression in your code is the return value.
58
+ - You can use variables, loops, conditionals, array methods, etc.
59
+ - All tool calls must use \`await\`.
60
+ - If the user's question doesn't need tools, you can compute the answer directly.`);
61
+ return parts.join("\n\n");
62
+ }
63
+ // ---------------------------------------------------------------------------
64
+ // Tool schema (shared across SDK formats)
65
+ // ---------------------------------------------------------------------------
66
+ const CODE_TOOL_SCHEMA = {
67
+ type: "object",
68
+ properties: {
69
+ code: {
70
+ type: "string",
71
+ description: "TypeScript code to execute in the sandbox",
72
+ },
73
+ },
74
+ required: ["code"],
75
+ };
76
+ const CODE_TOOL_DESCRIPTION = "Execute TypeScript code in a secure sandbox. " +
77
+ "The code can call the available tool functions using await. " +
78
+ "The last expression is the return value.";
79
+ // ---------------------------------------------------------------------------
80
+ // Execution engine
81
+ // ---------------------------------------------------------------------------
82
+ async function executeCode(code, toolDefs, options) {
83
+ const toolNames = Object.keys(toolDefs);
84
+ const toolCalls = [];
85
+ const sandbox = new Zapcode(code, {
86
+ externalFunctions: toolNames,
87
+ timeLimitMs: options.timeLimitMs ?? 10_000,
88
+ memoryLimitMb: options.memoryLimitMb ?? 32,
89
+ });
90
+ let state = sandbox.start();
91
+ let stdout = "";
92
+ // Snapshot/resume loop — resolve each tool call as the VM suspends
93
+ while (!state.completed) {
94
+ const { functionName, args } = state;
95
+ const toolDef = toolDefs[functionName];
96
+ if (!toolDef) {
97
+ throw new Error(`Guest code called unknown function '${functionName}'. ` +
98
+ `Available: ${toolNames.join(", ")}`);
99
+ }
100
+ // Build named args from positional args using the parameter schema
101
+ const paramNames = Object.keys(toolDef.parameters);
102
+ const namedArgs = {};
103
+ for (let i = 0; i < paramNames.length && i < args.length; i++) {
104
+ namedArgs[paramNames[i]] = args[i];
105
+ }
106
+ const result = await toolDef.execute(namedArgs);
107
+ toolCalls.push({ name: functionName, args, result });
108
+ // Resume the VM with the tool's return value
109
+ const snapshot = ZapcodeSnapshotHandle.load(state.snapshot);
110
+ state = snapshot.resume(result);
111
+ }
112
+ if (state.stdout) {
113
+ stdout = state.stdout;
114
+ }
115
+ return {
116
+ output: state.output,
117
+ stdout,
118
+ toolCalls,
119
+ };
120
+ }
121
+ // ---------------------------------------------------------------------------
122
+ // Main entry point
123
+ // ---------------------------------------------------------------------------
124
+ /**
125
+ * Create AI SDK-compatible system prompt and tools for Zapcode.
126
+ *
127
+ * Returns adapters for every major AI SDK:
128
+ * - `tools` → Vercel AI SDK (`generateText`, `streamText`)
129
+ * - `openaiTools` → OpenAI SDK (`chat.completions.create`)
130
+ * - `anthropicTools` → Anthropic SDK (`messages.create`)
131
+ * - `handleToolCall(code)` → Universal handler for any SDK
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * // Vercel AI SDK
136
+ * const { system, tools } = zapcode({ tools: { getWeather: { ... } } });
137
+ * await generateText({ model, system, tools, messages });
138
+ *
139
+ * // OpenAI SDK
140
+ * const { system, openaiTools, handleToolCall } = zapcode({ tools: { ... } });
141
+ * const res = await openai.chat.completions.create({
142
+ * messages: [{ role: "system", content: system }, ...],
143
+ * tools: openaiTools,
144
+ * });
145
+ * const code = res.choices[0].message.tool_calls[0].function.arguments;
146
+ * const result = await handleToolCall(JSON.parse(code).code);
147
+ *
148
+ * // Anthropic SDK
149
+ * const { system, anthropicTools, handleToolCall } = zapcode({ tools: { ... } });
150
+ * const res = await anthropic.messages.create({
151
+ * system, tools: anthropicTools, messages,
152
+ * });
153
+ * const toolUse = res.content.find(b => b.type === "tool_use");
154
+ * const result = await handleToolCall(toolUse.input.code);
155
+ * ```
156
+ */
157
+ export function zapcode(options) {
158
+ const { tools: toolDefs, system: userSystem, memoryLimitMb, timeLimitMs, adapters } = options;
159
+ const system = buildSystemPrompt(toolDefs, userSystem);
160
+ const execOptions = { memoryLimitMb, timeLimitMs };
161
+ // Universal handler
162
+ const handleToolCall = async (code) => {
163
+ return executeCode(code, toolDefs, execOptions);
164
+ };
165
+ // Vercel AI SDK format — use tool() + jsonSchema() for proper integration
166
+ const tools = {
167
+ execute_code: tool({
168
+ description: CODE_TOOL_DESCRIPTION,
169
+ parameters: jsonSchema(CODE_TOOL_SCHEMA),
170
+ execute: async (args) => handleToolCall(args.code),
171
+ }),
172
+ };
173
+ // OpenAI SDK format
174
+ const openaiTools = [
175
+ {
176
+ type: "function",
177
+ function: {
178
+ name: "execute_code",
179
+ description: CODE_TOOL_DESCRIPTION,
180
+ parameters: CODE_TOOL_SCHEMA,
181
+ },
182
+ },
183
+ ];
184
+ // Anthropic SDK format
185
+ const anthropicTools = [
186
+ {
187
+ name: "execute_code",
188
+ description: CODE_TOOL_DESCRIPTION,
189
+ input_schema: CODE_TOOL_SCHEMA,
190
+ },
191
+ ];
192
+ // Run custom adapters
193
+ const custom = {};
194
+ if (adapters) {
195
+ const adapterContext = {
196
+ system,
197
+ toolName: "execute_code",
198
+ toolDescription: CODE_TOOL_DESCRIPTION,
199
+ toolSchema: CODE_TOOL_SCHEMA,
200
+ handleToolCall,
201
+ };
202
+ for (const adapter of adapters) {
203
+ custom[adapter.name] = adapter.adapt(adapterContext);
204
+ }
205
+ }
206
+ return { system, tools, openaiTools, anthropicTools, handleToolCall, custom };
207
+ }
208
+ /**
209
+ * Helper to create a typed adapter.
210
+ *
211
+ * @example
212
+ * ```typescript
213
+ * const langchainAdapter = createAdapter("langchain", (ctx) => {
214
+ * return new DynamicStructuredTool({
215
+ * name: ctx.toolName,
216
+ * description: ctx.toolDescription,
217
+ * func: async ({ code }) => JSON.stringify(await ctx.handleToolCall(code)),
218
+ * });
219
+ * });
220
+ * ```
221
+ */
222
+ export function createAdapter(name, adapt) {
223
+ return { name, adapt };
224
+ }
225
+ // ---------------------------------------------------------------------------
226
+ // Convenience: standalone execution without AI SDK
227
+ // ---------------------------------------------------------------------------
228
+ /**
229
+ * Execute TypeScript code directly in a Zapcode sandbox with tool resolution.
230
+ *
231
+ * This is the lower-level API if you don't need AI SDK integration — you
232
+ * provide the code yourself and Zapcode executes it with tool calls resolved.
233
+ *
234
+ * @example
235
+ * ```typescript
236
+ * import { execute } from "@unchartedfr/zapcode-ai";
237
+ *
238
+ * const result = await execute(
239
+ * `const w = await getWeather("Tokyo"); w.temp`,
240
+ * {
241
+ * getWeather: {
242
+ * description: "Get weather",
243
+ * parameters: { city: { type: "string" } },
244
+ * execute: async ({ city }) => ({ temp: 26, condition: "Clear" }),
245
+ * },
246
+ * },
247
+ * );
248
+ * console.log(result.output); // 26
249
+ * ```
250
+ */
251
+ export async function execute(code, tools, options) {
252
+ return executeCode(code, tools, options ?? {});
253
+ }
254
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@unchartedfr/zapcode-ai",
3
+ "version": "1.0.0",
4
+ "description": "AI SDK integration for Zapcode — let LLMs write and execute TypeScript safely",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "tsc --watch"
17
+ },
18
+ "dependencies": {
19
+ "@unchartedfr/zapcode": "^1.0.0"
20
+ },
21
+ "peerDependencies": {
22
+ "ai": ">=4.0.0"
23
+ },
24
+ "peerDependenciesMeta": {
25
+ "ai": {
26
+ "optional": true
27
+ }
28
+ },
29
+ "devDependencies": {
30
+ "typescript": "^5.7.0",
31
+ "ai": "^4.1.0",
32
+ "@types/node": "^22.0.0"
33
+ },
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "keywords": [
38
+ "typescript",
39
+ "interpreter",
40
+ "sandbox",
41
+ "ai",
42
+ "mcp",
43
+ "llm",
44
+ "tool-use",
45
+ "agent"
46
+ ],
47
+ "homepage": "https://github.com/TheUncharted/zapcode#readme",
48
+ "bugs": "https://github.com/TheUncharted/zapcode/issues",
49
+ "license": "MIT",
50
+ "author": "Uncharted",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/TheUncharted/zapcode.git",
54
+ "directory": "packages/zapcode-ai"
55
+ }
56
+ }