Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .changeset/quickjs-bun-isolate-driver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
'@tanstack/ai-isolate-quickjs-bun': minor
'@tanstack/ai-code-mode': patch
---

Add `@tanstack/ai-isolate-quickjs-bun`, a Code Mode isolate driver that runs QuickJS natively on the Bun runtime through `bun:ffi` (via [`quickjs-bun`](https://github.com/superpowerdotcom/quickjs-bun)).

It implements the same `IsolateDriver` contract as the existing drivers and is a drop-in replacement for `@tanstack/ai-isolate-quickjs` on Bun servers:

```typescript
import { createQuickJSBunIsolateDriver } from '@tanstack/ai-isolate-quickjs-bun'
import { createCodeModeTool } from '@tanstack/ai-code-mode'

const executeTypescript = createCodeModeTool({
driver: createQuickJSBunIsolateDriver(),
tools: [myTool],
})
```

Compared to the WASM driver:

- Native QuickJS through `bun:ffi` — no WebAssembly or asyncify overhead, and no native build step (the QuickJS sources are compiled once per process by Bun's embedded TinyCC).
- Each context gets a dedicated QuickJS runtime with its own memory and stack limits, so executions on different contexts are not serialized through a shared VM.
- Same normalized `MemoryLimitError` / `StackOverflowError` / `DisposedError` contract, console capture prefixes, and JSON tool-call protocol as the other drivers, plus a normalized `TimeoutError` for deadline expiry (the WASM driver surfaces timeouts as `InternalError: interrupted`).
- A configurable `maxToolCalls` limit (default 1000) bounds output and memory growth from untrusted sandbox code.

The driver requires Bun `>= 1.3.14` and throws an error when used on Node.js.

It pins `quickjs-bun` to an exact version (`0.1.2`) rather than a range, because that package is still pre-1.0 and compiles QuickJS through TinyCC/`bun:ffi` — its API and platform support may shift between patch releases (see the package README for the compatibility note and the Windows `QUICKJS_BUN_NATIVE_LIBRARY` requirement).

The `@tanstack/ai-code-mode` README and bundled skill are updated to document the new driver.
54 changes: 54 additions & 0 deletions .github/workflows/bun-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Bun Tests

# The `@tanstack/ai-isolate-quickjs-bun` driver runs QuickJS natively through
# `bun:ffi`, so its substantive test suite (escape attempts, timeouts, memory
# limits, maxToolCalls, concurrency) is gated with
# `describe.skipIf(typeof Bun === 'undefined')` and only runs under Bun. The
# standard PR job (`pr.yml`) runs Vitest on Node and therefore only exercises
# the "rejects createContext on Node.js" case. This workflow runs the Bun-gated
# suite so that behavior is actually covered in CI.
#
# It is path-filtered to the package (and this file) to keep it off unrelated
# PRs.

on:
pull_request:
paths:
- 'packages/ai-isolate-quickjs-bun/**'
- 'packages/ai-code-mode/**'
- '.github/workflows/bun-test.yml'

concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.ref }}
cancel-in-progress: true

env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}

permissions:
contents: read

jobs:
test:
name: Test (Bun)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Tools
uses: TanStack/config/.github/setup@190f659075ff0845850e330883eb26d7ffd0671f # main
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
# The driver requires Bun >= 1.3.14 (see the package `engines` field).
bun-version: latest
- name: Build package and its workspace dependencies
# `@tanstack/ai-code-mode` is a `workspace:*` peer dependency and is
# imported at runtime (`wrapCode`) by the driver, so it must be built to
# `dist/` before the Bun tests can import it. nx builds the dependency
# graph first via `^build`.
run: pnpm exec nx build @tanstack/ai-isolate-quickjs-bun
- name: Run Bun test suite
run: pnpm --filter @tanstack/ai-isolate-quickjs-bun run test:bun
105 changes: 80 additions & 25 deletions docs/code-mode/code-mode-isolates.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
title: Code Mode Isolate Drivers
id: code-mode-isolates
order: 4
description: "Compare Code Mode sandbox drivers — Node isolated-vm, QuickJS WASM, and Cloudflare Workers — and choose the right runtime for your deployment."
description: "Compare Code Mode sandbox drivers — Node isolated-vm, QuickJS WASM, QuickJS Bun (bun:ffi), and Cloudflare Workers — and choose the right runtime for your deployment."
keywords:
- tanstack ai
- code mode
- isolate driver
- isolated-vm
- quickjs
- quickjs-bun
- bun
- bun:ffi
- cloudflare workers
- sandbox
- secure execution
Expand All @@ -18,15 +21,17 @@ Isolate drivers provide the secure sandbox runtimes that [Code Mode](./code-mode

## Choosing a Driver

| | Node (`isolated-vm`) | QuickJS (WASM) | Cloudflare Workers |
|---|---|---|---|
| **Best for** | Server-side Node.js apps | Browsers, edge, portability | Edge deployments on Cloudflare |
| **Performance** | Fast (V8 JIT) | Slower (interpreted) | Fast (V8 on Cloudflare edge) |
| **Native deps** | Yes (C++ addon) | None | None |
| **Browser support** | No | Yes | N/A |
| **Memory limit** | Configurable | Configurable | N/A |
| **Stack size limit** | N/A | Configurable | N/A |
| **Setup** | `pnpm add` | `pnpm add` | Deploy a Worker first |

| | Node (`isolated-vm`) | QuickJS (WASM) | QuickJS Bun (`bun:ffi`) | Cloudflare Workers |
| -------------------- | ------------------------ | --------------------------- | ------------------------ | ------------------------------ |
| **Best for** | Server-side Node.js apps | Browsers, edge, portability | Bun servers | Edge deployments on Cloudflare |
| **Performance** | Fast (V8 JIT) | Slower (interpreted) | Fast (native QuickJS) | Fast (V8 on Cloudflare edge) |
| **Native deps** | Yes (C++ addon) | None | None (TinyCC on the fly) | None |
| **Browser support** | No | Yes | No (Bun only) | N/A |
| **Memory limit** | Configurable | Configurable | Configurable | N/A |
| **Stack size limit** | N/A | Configurable | Configurable | N/A |
| **Setup** | `pnpm add` | `pnpm add` | `bun add` | Deploy a Worker first |


---

Expand Down Expand Up @@ -55,10 +60,12 @@ const driver = createNodeIsolateDriver({

### Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `memoryLimit` | `number` | `128` | Maximum heap size for the V8 isolate, in megabytes. Execution is terminated if this limit is exceeded. |
| `timeout` | `number` | `30000` | Maximum wall-clock time per execution, in milliseconds. |

| Option | Type | Default | Description |
| ------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------ |
| `memoryLimit` | `number` | `128` | Maximum heap size for the V8 isolate, in megabytes. Execution is terminated if this limit is exceeded. |
| `timeout` | `number` | `30000` | Maximum wall-clock time per execution, in milliseconds. |


### How it works

Expand Down Expand Up @@ -90,12 +97,14 @@ const driver = createQuickJSIsolateDriver({

### Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `memoryLimit` | `number` | `128` | Maximum heap memory for the QuickJS VM, in megabytes. |
| `timeout` | `number` | `30000` | Maximum wall-clock time per execution, in milliseconds. |

| Option | Type | Default | Description |
| -------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `memoryLimit` | `number` | `128` | Maximum heap memory for the QuickJS VM, in megabytes. |
| `timeout` | `number` | `30000` | Maximum wall-clock time per execution, in milliseconds. |
| `maxStackSize` | `number` | `524288` | Maximum call stack size in bytes (default: 512 KiB). Increase for deeply recursive code; decrease to catch runaway recursion sooner. |


### How it works

QuickJS WASM uses an asyncified execution model — the WASM module can pause while awaiting host async functions (your tools). Executions are serialized through a global queue to prevent concurrent WASM calls, which the asyncify model does not support. Fatal errors (memory exhaustion, stack overflow) are detected, the VM is disposed, and a structured error is returned. Console output is captured and returned with the result.
Expand All @@ -104,6 +113,49 @@ QuickJS WASM uses an asyncified execution model — the WASM module can pause wh

---

## QuickJS Bun Driver (`@tanstack/ai-isolate-quickjs-bun`)

Runs [QuickJS](https://bellard.org/quickjs/) natively on the [Bun](https://bun.sh/) runtime through `bun:ffi`, via the [`quickjs-bun`](https://github.com/superpowerdotcom/quickjs-bun) package. There are no native dependencies and no build step — the vendored QuickJS C sources are compiled on the fly with Bun's embedded TinyCC, once per process. This makes it the fastest sandboxed option for Code Mode on Bun.

### Installation

Comment thread
lithdew marked this conversation as resolved.
```bash
bun add @tanstack/ai-isolate-quickjs-bun
```

Requires Bun 1.3.14 or later. On Windows, provide a prebuilt QuickJS dynamic library via the `QUICKJS_BUN_NATIVE_LIBRARY` environment variable.

### Usage

```typescript
import { createQuickJSBunIsolateDriver } from '@tanstack/ai-isolate-quickjs-bun'

const driver = createQuickJSBunIsolateDriver({
memoryLimit: 128, // MB
timeout: 30_000, // ms
maxStackSize: 524288, // bytes (512 KiB)
})
```

### Options


| Option | Type | Default | Description |
| -------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `memoryLimit` | `number` | `128` | Maximum heap memory for the QuickJS runtime, in megabytes. |
| `timeout` | `number` | `30000` | Maximum wall-clock time per execution, in milliseconds. |
| `maxStackSize` | `number` | `524288` | Maximum call stack size in bytes (default: 512 KiB). Increase for deeply recursive code; decrease to catch runaway recursion sooner. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] The QuickJS Bun "Options" table documents memoryLimit, timeout, and maxStackSize but omits maxToolCalls, which is a first-class driver option (default 1000) documented in the package README, changeset, and QuickJSBunIsolateDriverConfig. Users reading the canonical isolate-driver doc will not discover this safety limit.

Suggestion: Add a maxToolCalls row to the Options table and mention it briefly in "How it works", consistent with packages/ai-isolate-quickjs-bun/README.md.

| `maxToolCalls` | `number` | `1000` | Maximum host tool calls per execution. Bounds output and memory growth from untrusted sandbox code that fans out (e.g. `Promise.all` over a huge array); exceeding it throws a catchable error inside the sandbox. |


### How it works

Each context gets a dedicated native QuickJS runtime with its own memory limit, stack size, and interrupt-based timeout, so contexts execute independently — unlike the WASM driver, which serializes all executions through one shared asyncified WASM module. Fatal errors (memory exhaustion, stack overflow) are detected, the VM is disposed, and a structured error is returned; create a fresh context afterwards. A per-execution `maxToolCalls` budget bounds host tool-call fan-out. Console output is captured and returned with the result.

> **Bun only:** This driver requires Bun 1.3.14 or later and throws a descriptive error when creating a context on Node.js — use the Node or QuickJS WASM driver there. On Bun, prefer this driver over the WASM one: it runs QuickJS natively, and quickjs-emscripten's asyncify bridge is unreliable for async host tool calls under Bun.

---

## Cloudflare Workers Driver (`@tanstack/ai-isolate-cloudflare`)

Runs generated code inside a [Cloudflare Worker](https://workers.cloudflare.com/) at the edge. Your application server sends code and tool schemas to the Worker via HTTP; the Worker executes the code and calls back when it needs a tool result. This keeps your tool implementations on your server while sandboxed execution happens on Cloudflare's global network.
Expand All @@ -129,12 +181,14 @@ const driver = createCloudflareIsolateDriver({

### Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `workerUrl` | `string` | — | **Required.** Full URL of the deployed Cloudflare Worker. |
| `authorization` | `string` | — | Optional value sent as the `Authorization` header on every request. Use this to prevent unauthorized access to your Worker. |
| `timeout` | `number` | `30000` | Maximum wall-clock time for the entire execution (including all tool round-trips), in milliseconds. |
| `maxToolRounds` | `number` | `10` | Maximum number of tool-call/result cycles per execution. Prevents infinite loops when generated code calls tools in a loop. |

| Option | Type | Default | Description |
| --------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------- |
| `workerUrl` | `string` | — | **Required.** Full URL of the deployed Cloudflare Worker. |
| `authorization` | `string` | — | Optional value sent as the `Authorization` header on every request. Use this to prevent unauthorized access to your Worker. |
| `timeout` | `number` | `30000` | Maximum wall-clock time for the entire execution (including all tool round-trips), in milliseconds. |
| `maxToolRounds` | `number` | `10` | Maximum number of tool-call/result cycles per execution. Prevents infinite loops when generated code calls tools in a loop. |


### Deploying the Worker

Expand Down Expand Up @@ -184,7 +238,7 @@ Each round-trip adds network latency, so the `maxToolRounds` limit both prevents

## The `IsolateDriver` Interface

All three drivers satisfy this interface, exported from `@tanstack/ai-code-mode`:
All four drivers satisfy this interface, exported from `@tanstack/ai-code-mode`:

```typescript
import type { ToolBinding, NormalizedError } from "@tanstack/ai-code-mode";
Expand Down Expand Up @@ -219,3 +273,4 @@ You can implement this interface to build a custom driver — for example, a Doc
- [Code Mode](./code-mode) — Core setup, API reference, and getting started guide
- [Showing Code Mode in the UI](./client-integration) — Display execution progress in your React app
- [Code Mode with Skills](./code-mode-with-skills) — Add persistent, reusable skill libraries

10 changes: 7 additions & 3 deletions docs/code-mode/code-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Tools you pass to Code Mode are converted to typed function stubs that appear in

### Secure sandboxing

Generated code runs in an isolated environment (V8 isolate, QuickJS WASM, or Cloudflare Worker) with no access to the host file system, network, or process. The sandbox has configurable timeouts and memory limits.
Generated code runs in an isolated environment (V8 isolate, QuickJS WASM, native QuickJS on Bun, or Cloudflare Worker) with no access to the host file system, network, or process. The sandbox has configurable timeouts and memory limits.

## Getting Started

Expand All @@ -54,6 +54,9 @@ pnpm add @tanstack/ai-isolate-node
# QuickJS WASM — no native deps, works in browsers and edge runtimes
pnpm add @tanstack/ai-isolate-quickjs

# QuickJS Bun — native QuickJS via bun:ffi, fastest option on Bun
bun add @tanstack/ai-isolate-quickjs-bun

# Cloudflare Workers — run on the edge
pnpm add @tanstack/ai-isolate-cloudflare
```
Expand Down Expand Up @@ -210,6 +213,7 @@ interface IsolateDriver {
|---------|-----------------|-------------|
| `@tanstack/ai-isolate-node` | `createNodeIsolateDriver()` | Node.js |
| `@tanstack/ai-isolate-quickjs` | `createQuickJSIsolateDriver()` | Node.js, browser, edge |
| `@tanstack/ai-isolate-quickjs-bun` | `createQuickJSBunIsolateDriver()` | Bun |
| `@tanstack/ai-isolate-cloudflare` | `createCloudflareIsolateDriver()` | Cloudflare Workers |

For full configuration options for each driver, see [Isolate Drivers](./code-mode-isolates.md).
Expand All @@ -226,7 +230,7 @@ These utilities are used internally and are exported for custom pipelines:

For a full comparison of drivers with all configuration options, see [Isolate Drivers](./code-mode-isolates.md).

In brief: use the **Node driver** for server-side Node.js (fastest, V8 JIT), **QuickJS** for browsers or portable edge deployments (no native deps), and the **Cloudflare driver** when you deploy to Cloudflare Workers.
In brief: use the **Node driver** for server-side Node.js (fastest, V8 JIT), **QuickJS** for browsers or portable edge deployments (no native deps), **QuickJS Bun** for Bun servers (native QuickJS via `bun:ffi`), and the **Cloudflare driver** when you deploy to Cloudflare Workers.

## Custom Events

Expand Down Expand Up @@ -292,4 +296,4 @@ pnpm eval -- --no-judge # skip Anthropic-based judging

- [Showing Code Mode in the UI](./client-integration) — Display execution progress in your React app
- [Code Mode with Skills](./code-mode-with-skills) — Add persistent, reusable skill libraries
- [Isolate Drivers](./code-mode-isolates) — Compare Node, QuickJS, and Cloudflare sandbox runtimes
- [Isolate Drivers](./code-mode-isolates) — Compare Node, QuickJS, QuickJS Bun, and Cloudflare sandbox runtimes
Loading
Loading