Skip to content

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.

Terminal window
npm install @johnhenry/aimatey-middleware-andbox

Peer dependency: this package requires andbox to be installed separately:

Terminal window
npm install andbox

andbox 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 });

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
  • extractCodeBlocks(text) — extract fenced code blocks from text, returns Array<{lang, code}>
  • stripCodeBlocks(text) — remove all fenced code blocks from text
  • adaptPythonisms(code) — convert Python patterns (True, False, None, f-strings) to JavaScript equivalents
  • autoAwait(code, asyncFnPatterns?) — insert await before common async calls (print(), browser_*(), and custom patterns)
  • toolsToCapabilities(tools, executeToolFn) — convert tool definitions to an object of callable async functions
  • toolsToPreamble(tools) — generate JavaScript code that declares function stubs for each tool
  • formatResults(results, maxResultLength?) — format execution results as a summary string
  • resultsToToolCalls(results) — convert results to synthetic tool call entries

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 which capabilities you supplied — omitting a fetch-like tool does not block network access.
  • The capability gate can be bypassed via the prototype chain (host.call('constructor', ...) resolves to the real global Object constructor).
  • 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.

github.com/johnhenry/aimatey-middleware-andbox