aimatey-middleware-andbox
Previously published as
[email protected].
aimatey middleware for code-based tool execution via the andbox sandbox.
LLMs that don’t support native tool calling can still use tools by writing code. This middleware intercepts LLM responses, extracts fenced code blocks, adapts common Python-isms to JavaScript, and executes them in a sandboxed environment with tool stubs injected as callable functions.
Install
Section titled “Install”npm install @johnhenry/aimatey-middleware-andboxPeer dependency: this package requires andbox to be installed separately:
npm install andboxandbox only accepts capabilities (the functions reachable from sandboxed code via host.call(name, ...)) at createSandbox({ capabilities }) time — there is no way to attach them later. So this middleware needs either the andbox createSandbox factory itself (and it will create the sandbox for you, wired to your tools), or an already-built sandbox that you created with the capabilities already set. The factory form is recommended:
import { createCodeExecutionMiddleware } from '@johnhenry/aimatey-middleware-andbox';import { createSandbox } from 'andbox';
const tools = [ { name: 'fetch_data', description: 'Fetch data from a URL', parameters: { url: { type: 'string' } } }, { name: 'save_file', description: 'Save content to a file', parameters: { path: { type: 'string' }, content: { type: 'string' } } },];
const middleware = createCodeExecutionMiddleware({ createSandbox, // andbox's factory -- the middleware calls this itself tools, executeToolFn: async (name, params) => { // Route to your actual tool implementations console.log(`Executing tool: ${name}`, params); return { success: true, output: 'done' }; }, maxResultLength: 4096, timeoutMs: 30000,});
// Use with aimatey// bridge.use(middleware);If you need full control over the sandbox (custom importMap, policy, onConsole, etc.), build it yourself with toolsToCapabilities() and pass the instance as sandbox instead — the middleware will use it as-is and will not be able to add capabilities to it later:
import { createCodeExecutionMiddleware, toolsToCapabilities } from '@johnhenry/aimatey-middleware-andbox';import { createSandbox } from 'andbox';
const executeToolFn = async (name, params) => ({ success: true });const sandbox = await createSandbox({ capabilities: toolsToCapabilities(tools, executeToolFn), policy: { limits: { maxCalls: 50 } },});
const middleware = createCodeExecutionMiddleware({ sandbox, tools, executeToolFn });createCodeExecutionMiddleware(options)
Section titled “createCodeExecutionMiddleware(options)”Creates an aimatey middleware object with an after hook.
| Option | Type | Default | Description |
|---|---|---|---|
createSandbox |
(opts) => Sandbox | Promise<Sandbox> |
one of createSandbox/sandbox required |
andbox’s createSandbox factory. The middleware creates (and caches) the sandbox itself, with capabilities wired from tools/executeToolFn. |
sandbox |
Sandbox |
one of createSandbox/sandbox required |
A pre-built andbox sandbox instance. Must already have been created with capabilities: toolsToCapabilities(tools, executeToolFn) — capabilities cannot be added after creation. |
sandboxOptions |
object |
{} |
Extra options merged into createSandbox() when using the createSandbox factory (e.g. importMap, policy, onConsole). Any capabilities here are merged with (and can override) the tool-derived ones. |
tools |
Array<{name, description?, parameters?}> |
required | Tool definitions |
executeToolFn |
(name, params) => Promise<any> |
required | Function to execute tools |
maxResultLength |
number |
4096 |
Max characters per result |
codeLanguages |
string[] |
['js','javascript','tool_code','python','py',''] |
Languages to execute |
timeoutMs |
number |
30000 |
Execution timeout in ms |
The middleware attaches the following properties to the response:
_codeResults— array of{code, output, error?}for each executed block_toolCalls— synthetic tool call entries_cleanText— response text with code blocks stripped_resultSummary— formatted summary string
Other exports
Section titled “Other exports”extractCodeBlocks(text)— extract fenced code blocks from text, returnsArray<{lang, code}>stripCodeBlocks(text)— remove all fenced code blocks from textadaptPythonisms(code)— convert Python patterns (True,False,None, f-strings) to JavaScript equivalentsautoAwait(code, asyncFnPatterns?)— insertawaitbefore common async calls (print(),browser_*(), and custom patterns)toolsToCapabilities(tools, executeToolFn)— convert tool definitions to an object of callable async functionstoolsToPreamble(tools)— generate JavaScript code that declares function stubs for each toolformatResults(results, maxResultLength?)— format execution results as a summary stringresultsToToolCalls(results)— convert results to synthetic tool call entries
Security model
Section titled “Security model”This middleware routes LLM-authored code through andbox’s Worker sandbox and gates which host functions (tools) that code can call via host.call(). That capability gate is not a security boundary against adversarial LLM output. andbox’s own Security model documents confirmed ways sandboxed code can act outside what capabilities appears to allow — in short:
- Worker-global APIs (
fetch,WebSocket,Worker,importScripts,indexedDB) are directly reachable from sandboxed code regardless of whichcapabilitiesyou supplied — omitting afetch-like tool does not block network access. - The capability gate can be bypassed via the prototype chain (
host.call('constructor', ...)resolves to the real globalObjectconstructor). sandboxImport()will load and execute an arbitrary remote URL.- A timeout stops message delivery to a killed Worker, not an in-flight host-side effect that capability call already triggered.
In other words: wiring executeToolFn into the sandbox (as this package does correctly) lets you organize which tools well-behaved LLM-generated code can call, and gives you timeouts/rate limits for code you already trust. It does not contain code that is deliberately trying to escape. If you’re executing output from an untrusted or adversarial model, pair this middleware with OS-level isolation (a separate process/container with its own network and filesystem restrictions) in addition to andbox’s Worker boundary — do not rely on the tool-capability gate alone.