Skip to content

feat(ai-isolate): add native QuickJS Code Mode isolate driver for Bun#750

Open
lithdew wants to merge 2 commits into
TanStack:mainfrom
lithdew:quickjs-bun-code-mode
Open

feat(ai-isolate): add native QuickJS Code Mode isolate driver for Bun#750
lithdew wants to merge 2 commits into
TanStack:mainfrom
lithdew:quickjs-bun-code-mode

Conversation

@lithdew

@lithdew lithdew commented Jun 11, 2026

Copy link
Copy Markdown

🎯 Changes

Adds a new Code Mode isolate driver, @tanstack/ai-isolate-quickjs-bun, that runs QuickJS natively on Bun via bun:ffi (through quickjs-bun) rather than WebAssembly.

It implements the existing IsolateDriver contract from @tanstack/ai-code-mode, so it's a drop-in replacement for @tanstack/ai-isolate-quickjs on Bun servers:

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

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

Why

On Bun, the existing WASM driver (quickjs-emscripten) is both slower and, in my testing, unreliable for async host tool calls. Its asyncify bridge crashes the shared WASM module (memory access out of bounds) and hangs once a single execution makes ≥ 4 sequential awaited tool calls (reproduced on Node 22 and Bun 1.3.14). quickjs-bun maps the QuickJS C API directly through bun:ffi, giving each context its own native runtime with no asyncify and no shared-VM serialization.

Benchmarks

packages/ai-isolate-quickjs-bun/benchmarks/compare-with-wasm.ts, both drivers driven through the public IsolateDriver interface (Apple M-series, darwin/arm64; Bun 1.3.14 for the native driver, Node 22 for the WASM driver as its native habitat):

Scenario (fresh context per run) QuickJS Bun QuickJS WASM (Node)
Cold start (first context + run) ~130 ms ~20 ms
return 1 + 1 ~0.7 ms ~10 ms (≈14× WASM)
3 sequential tool calls ~0.9 ms see note ¹
8 sequential tool calls ~1.0 ms see note ¹
compute (fib(20)) ~5.0 ms see note ¹
return 1 + 1 (reused context) ~0.04 ms

¹ The WASM driver's asyncified host tool calls repeatedly crash the shared WASM module and hang subsequent executions (≥ 4 sequential awaited host calls per process), on both Node 22 and Bun 1.3.14. Sync-only workloads are unaffected. WASM wins cold start (one-time WASM instantiate vs TinyCC compile of the QuickJS sources); the native driver wins steady-state per-execution by ~14× on the trivial case.

Notes

The suites are gated with describe.skipIf(typeof Bun === 'undefined'), mirroring how @tanstack/ai-isolate-node skips when its native addon is unavailable — would like to know if it is fine to add a oven-sh/setup-bun job (pnpm --filter @tanstack/ai-isolate-quickjs-bun test:bun) so that the full test suite is ran in CI.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • New Features

    • Added a native QuickJS isolate driver for the Bun runtime with configurable timeout, memory, stack, and tool-call limits (default 1000). Bun-only runtime check and normalized limit/timeout errors.
  • Documentation

    • Updated guides, README, and driver comparison to document the Bun driver, install instructions, selection guidance, and driver behavior/limits.
  • Examples

    • Example app updated to expose the new driver option.
  • Tests & Benchmarks

    • Added Bun-only tests and benchmark scripts validating isolation, limits, and performance.

Adds @tanstack/ai-isolate-quickjs-bun, a Code Mode IsolateDriver that
runs QuickJS natively on the Bun runtime via bun:ffi (through
quickjs-bun) instead of WebAssembly. It is a drop-in replacement for
@tanstack/ai-isolate-quickjs on Bun servers.

- Per-context native QuickJS runtime with its own memory/stack/timeout
  limits; contexts execute independently (the WASM driver serializes all
executions through one asyncified module).
- Same JSON tool-call protocol, console prefixes, and normalized
  MemoryLimit/StackOverflowError/DisposedError contract as the other
drivers, plus a normalized TimeoutError for deadline expiry.
- maxToolCalls (default 1000) and conosle log caps to bound stack and
  memory growth from untrusted snadbox code.
- Requires Bun >= 1.3.14; throws an error on Node.js.

Unit tests mirror the WASM/Node suites and run under `bun test`; the
Node-side rejection test runs in normal CI. Docs, the ai-code-mode
README + skill, and the code-mode example have been updated.
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ecc5fb72-b5b9-412c-9b0c-aa3dd66360d7

📥 Commits

Reviewing files that changed from the base of the PR and between ac5ba14 and cb7abd2.

📒 Files selected for processing (1)
  • docs/code-mode/code-mode-isolates.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/code-mode/code-mode-isolates.md

📝 Walkthrough

Walkthrough

New package @tanstack/ai-isolate-quickjs-bun implements a Bun-native QuickJS isolate driver (bun:ffi), conforming to the IsolateDriver contract. It provides per-context memory/stack limits, timeouts, maxToolCalls, normalized errors, console capture, extensive tests, benchmarks, docs, and example wiring.

Changes

Bun QuickJS Isolate Driver

Layer / File(s) Summary
Package structure and exports
packages/ai-isolate-quickjs-bun/package.json, tsconfig.json, vite.config.ts, knip.json, packages/ai-isolate-quickjs-bun/src/index.ts
Adds new package manifest, TS/Vitest configs, workspace entry, and barrel exports for the QuickJS Bun isolate driver.
Driver factory and runtime loading
packages/ai-isolate-quickjs-bun/src/isolate-driver.ts
Bun-only dynamic import of quickjs-bun, per-process QuickJS cache, driver factory validating Bun presence, per-context JSRuntime creation with memory/stack limits and normalized config.
Isolate context implementation
packages/ai-isolate-quickjs-bun/src/isolate-context.ts
QuickJSBunIsolateContext implements execute/dispose, serializes execution, drives QuickJS job queue, enforces deadlines, captures/truncates console logs, installs tool wrappers, and records/handles fatal settle errors.
Error normalization
packages/ai-isolate-quickjs-bun/src/error-normalizer.ts
Normalizes QuickJS/Bun exception shapes into NormalizedError (MemoryLimitError, StackOverflowError, TimeoutError) and provides helpers to detect fatal VM limits.
Tests: driver behavior
packages/ai-isolate-quickjs-bun/tests/isolate-driver.test.ts
Comprehensive Bun-gated Vitest suite covering evaluation, tool calls, concurrency, timeouts, memory/stack limits, maxToolCalls, console capture/truncation, disposal semantics, error normalization, and Node/Bun contract checks.
Tests: sandbox escape attempts
packages/ai-isolate-quickjs-bun/tests/escape-attempts.test.ts
Bun-gated tests validating absence of host globals/APIs, blocked std imports, prototype pollution protections, timeouts for CPU spins, and Function-constructor containment.
Benchmark harness
packages/ai-isolate-quickjs-bun/benchmarks/compare-with-wasm.ts
Benchmarks native Bun QuickJS vs WASM QuickJS across scenarios with guards, warm-context variant, and poisoning/hang handling.
Example integration
examples/ts-code-mode-web/package.json, src/components/ToolSidebar.tsx, src/lib/create-isolate-driver.ts, vite.config.ts
Adds dependency, UI option, dynamic driver import for quickjs-bun, and Vite SSR/opt exclude settings.
Docs & README updates
packages/ai-isolate-quickjs-bun/README.md, packages/ai-code-mode/README.md, packages/ai-code-mode/skills/ai-code-mode/SKILL.md, docs/code-mode/code-mode-isolates.md, docs/code-mode/code-mode.md, docs/comparison/vercel-ai-sdk.md, docs/config.json, .changeset/quickjs-bun-isolate-driver.md
Adds QuickJS Bun installation/usage/options to docs, updates driver comparison tables and interface wording, and records release notes/navigation timestamps.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • TanStack/ai#697: Overlaps on edits to the Code Execution / isolate-driver documentation area; related documentation changes.

Suggested reviewers

  • tannerlinsley
  • tombeckenham
  • schiller-manuel

Poem

🐰 I hopped into Bun with QuickJS in tow,
Native bits running fast, neat and low.
Sandbox snug, tools calling through,
Timeouts and limits keep the chaos true.
A tiny carrot toast — ship it, let's go!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding a native QuickJS isolate driver for Bun, which is the primary feature in this changeset.
Description check ✅ Passed The description covers all template sections (Changes, Checklist, Release Impact), explains the motivation, provides usage examples, benchmarks, and notes about testing and CI considerations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@socket-security

socket-security Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​types/​bun@​1.3.141001004992100
Addedquickjs-bun@​0.1.27810010090100
Addednitro@​3.0.260603-beta981008293100

View full report

@socket-security

socket-security Bot commented Jun 11, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm env-runner is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yamlnpm/nitro@3.0.260603-betanpm/env-runner@0.1.10

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/env-runner@0.1.10. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
examples/ts-code-mode-web/src/components/ToolSidebar.tsx (1)

106-111: 💤 Low value

Consider runtime detection for better UX.

The option is marked available: true unconditionally, so users on Node.js can select it but will encounter a runtime error when the driver attempts to create a context. While the description warns "requires running the server with Bun," detecting the runtime at page load and conditionally setting available: false on Node would prevent confusing errors.

🎨 Optional runtime detection pattern
  {
    id: 'quickjs-bun',
    name: 'QuickJS Bun',
    description: 'Native QuickJS engine (requires running the server with Bun)',
-   available: true,
+   available: typeof Bun !== 'undefined',
  },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/ts-code-mode-web/src/components/ToolSidebar.tsx` around lines 106 -
111, The quickjs-bun item in ToolSidebar.tsx currently hard-codes available:
true, which allows Node clients to select it and hit runtime errors; change it
to compute availability at load time (e.g., derive a boolean like isBunRuntime
from a server-provided endpoint or a prop/initial state and set available:
isBunRuntime) so the entry for id 'quickjs-bun' is disabled when the
server/runtime is not Bun; update the ToolSidebar component to fetch or accept
the runtime indicator (or feature flag) and use that to set the available
property for the 'quickjs-bun' item and the UI disabled state.
packages/ai-isolate-quickjs-bun/src/isolate-driver.ts (1)

141-142: 💤 Low value

Consider caching the module import alongside the library.

importQuickJSBun() is called on every createContext() invocation. While JavaScript runtimes cache dynamic imports, you could align this with the libraryPromise pattern for consistency and to make the caching explicit.

♻️ Suggested refactor
-let libraryPromise: Promise<QuickJS> | undefined
+let modulePromise: Promise<QuickJSBunModule> | undefined
+let libraryPromise: Promise<QuickJS> | undefined

+async function loadQuickJSModule(): Promise<QuickJSBunModule> {
+  modulePromise ??= importQuickJSBun()
+  try {
+    return await modulePromise
+  } catch (error) {
+    modulePromise = undefined
+    throw error
+  }
+}

 async function loadQuickJSLibrary(): Promise<QuickJS> {
-  libraryPromise ??= importQuickJSBun().then((mod) => new mod.QuickJS())
+  libraryPromise ??= loadQuickJSModule().then((mod) => new mod.QuickJS())
   // ...
 }

Then in createContext:

-      const quickjs = await importQuickJSBun()
+      const quickjs = await loadQuickJSModule()
       const library = await loadQuickJSLibrary()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-isolate-quickjs-bun/src/isolate-driver.ts` around lines 141 -
142, importQuickJSBun() is being invoked on every createContext() call; make
this explicit and consistent with the existing libraryPromise pattern by caching
the dynamic import result. Add a top-level promise (e.g., quickjsModulePromise)
that stores importQuickJSBun(), use that cached promise inside createContext()
instead of calling importQuickJSBun() directly, and ensure you still await
loadQuickJSLibrary() (libraryPromise) as before so both the module and library
are only loaded once.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/code-mode/code-mode-isolates.md`:
- Around line 115-120: The markdown link in the QuickJS Bun Driver paragraph
uses backticks around the link text
(`[quickjs-bun](https://github.com/superpowerdotcom/quickjs-bun)`) which
prevents it rendering as a proper clickable link; update the text in the QuickJS
Bun Driver section (the line containing
`[quickjs-bun](https://github.com/superpowerdotcom/quickjs-bun)`) to either use
monospace label with a linked URL like via the
[`quickjs-bun`](https://github.com/superpowerdotcom/quickjs-bun) package or a
normal link via the
[quickjs-bun](https://github.com/superpowerdotcom/quickjs-bun) package so the
link renders correctly.

---

Nitpick comments:
In `@examples/ts-code-mode-web/src/components/ToolSidebar.tsx`:
- Around line 106-111: The quickjs-bun item in ToolSidebar.tsx currently
hard-codes available: true, which allows Node clients to select it and hit
runtime errors; change it to compute availability at load time (e.g., derive a
boolean like isBunRuntime from a server-provided endpoint or a prop/initial
state and set available: isBunRuntime) so the entry for id 'quickjs-bun' is
disabled when the server/runtime is not Bun; update the ToolSidebar component to
fetch or accept the runtime indicator (or feature flag) and use that to set the
available property for the 'quickjs-bun' item and the UI disabled state.

In `@packages/ai-isolate-quickjs-bun/src/isolate-driver.ts`:
- Around line 141-142: importQuickJSBun() is being invoked on every
createContext() call; make this explicit and consistent with the existing
libraryPromise pattern by caching the dynamic import result. Add a top-level
promise (e.g., quickjsModulePromise) that stores importQuickJSBun(), use that
cached promise inside createContext() instead of calling importQuickJSBun()
directly, and ensure you still await loadQuickJSLibrary() (libraryPromise) as
before so both the module and library are only loaded once.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: efa0ea2b-d53c-4c49-ac81-f8007e2d5a1d

📥 Commits

Reviewing files that changed from the base of the PR and between 984ac3c and ac5ba14.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • .changeset/quickjs-bun-isolate-driver.md
  • docs/code-mode/code-mode-isolates.md
  • docs/code-mode/code-mode.md
  • docs/comparison/vercel-ai-sdk.md
  • docs/config.json
  • examples/ts-code-mode-web/package.json
  • examples/ts-code-mode-web/src/components/ToolSidebar.tsx
  • examples/ts-code-mode-web/src/lib/create-isolate-driver.ts
  • examples/ts-code-mode-web/vite.config.ts
  • knip.json
  • packages/ai-code-mode/README.md
  • packages/ai-code-mode/skills/ai-code-mode/SKILL.md
  • packages/ai-isolate-quickjs-bun/README.md
  • packages/ai-isolate-quickjs-bun/benchmarks/compare-with-wasm.ts
  • packages/ai-isolate-quickjs-bun/package.json
  • packages/ai-isolate-quickjs-bun/src/error-normalizer.ts
  • packages/ai-isolate-quickjs-bun/src/index.ts
  • packages/ai-isolate-quickjs-bun/src/isolate-context.ts
  • packages/ai-isolate-quickjs-bun/src/isolate-driver.ts
  • packages/ai-isolate-quickjs-bun/tests/escape-attempts.test.ts
  • packages/ai-isolate-quickjs-bun/tests/isolate-driver.test.ts
  • packages/ai-isolate-quickjs-bun/tsconfig.json
  • packages/ai-isolate-quickjs-bun/vite.config.ts

Comment thread docs/code-mode/code-mode-isolates.md
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant