-
-
Notifications
You must be signed in to change notification settings - Fork 301
feat(ai-isolate): add native QuickJS Code Mode isolate driver for Bun #750
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
de6d4cc
ec8c161
4d044f1
b4dd049
3e3184a
548c418
d6b9fbf
b47c62a
431f71e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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. |
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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 | | ||
|
|
||
|
|
||
| --- | ||
|
|
||
|
|
@@ -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 | ||
|
|
||
|
|
@@ -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. | ||
|
|
@@ -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 | ||
|
|
||
| ```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. | | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] The QuickJS Bun "Options" table documents Suggestion: Add a |
||
| | `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. | ||
|
|
@@ -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 | ||
|
|
||
|
|
@@ -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"; | ||
|
|
@@ -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 | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.