ai-code-tools
v1.0.2
Published
Wrap your AI SDK tools with code execution capabilities - discover and execute multiple tools in a single step
Maintainers
Readme
AI Code Tools
Supercharge your AI SDK tools with code execution capabilities. Discover and execute multiple tools in a single step through JavaScript code generation.
What is this?
AI Code Tools wraps your existing Vercel AI SDK tools and adds two powerful meta-tools:
discover_tools- Let the LLM search through available tools by name and schemaexecute_tools- Let the LLM write JavaScript code to orchestrate multiple tool calls in one step
This allows LLMs to be more efficient by composing multiple operations together instead of making sequential tool calls with round-trips.
Installation
npm install ai-code-toolsQuick Start
Simply wrap your existing AI SDK tools with createCodeTools():
import { createCodeTools } from 'ai-code-tools';
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { z } from 'zod';
// Your existing tools
const myTools = {
getCurrentWeather: {
description: 'Get the current weather in a location',
inputSchema: z.object({
location: z.string().describe('The city name'),
}),
execute: async ({ location }) => {
// Your weather API call
return { temperature: 72, condition: 'sunny' };
},
},
sendEmail: {
description: 'Send an email',
inputSchema: z.object({
to: z.string().describe('Email address'),
subject: z.string().describe('Email subject'),
body: z.string().describe('Email body'),
}),
execute: async ({ to, subject, body }) => {
// Your email sending logic
return { sent: true };
},
},
};
// Wrap your tools - that's it!
const tools = createCodeTools(myTools);
// Use with AI SDK as normal
const result = await generateText({
model: anthropic('claude-sonnet-4-5-20250929'),
prompt: 'Get the weather in San Francisco and email it to [email protected]',
tools,
});How It Works
Instead of the LLM making multiple sequential tool calls:
1. Call getCurrentWeather({ location: "San Francisco" })
2. Wait for response...
3. Call sendEmail({ to: "[email protected]", subject: "Weather", body: "72°F, sunny" })
4. Wait for response...The LLM can now discover available tools and execute them together:
// Generated by the LLM via execute_tools
const weather = await tools.getCurrentWeather({ location: "San Francisco" });
const emailResult = await tools.sendEmail({
to: "[email protected]",
subject: "Weather Update",
body: `The weather in San Francisco is ${weather.temperature}°F and ${weather.condition}`
});
return emailResult;All in a single step!
API
createCodeTools(tools)
Takes your tools object and returns a new tools object with two additional meta-tools:
type Tool = {
description?: string;
inputSchema: z.ZodType<any>;
execute: (params: any) => Promise<any> | any;
};
function createCodeTools(tools: Record<string, Tool>): Record<string, Tool>;Generated Meta-Tools
discover_tools
Allows the LLM to search for available tools by name and schema:
{
description: "Discover available tools by searching tool names and schemas with a query pattern",
inputSchema: z.object({
query: z.string().describe("The search query to find relevant tools")
})
}execute_tools
Allows the LLM to execute JavaScript code with access to your tools:
{
description: "Execute JavaScript code with access to tools via the 'tools' object. Return the result you want access to. IMPORTANT: Only use this tool to call the available tools and return their results.",
inputSchema: z.object({
code: z.string().describe("The JavaScript code to execute")
})
}Security
Security is a top priority. The generated JavaScript code goes through AST-based validation using Acorn before execution.
What's Protected
The code validation blocks all potentially dangerous operations:
- Module System:
require(),import, dynamic imports - Code Execution:
eval(),Functionconstructor,setTimeout,setInterval,setImmediate - Global Access:
process,global,globalThis,module,exports - File System:
__dirname,__filename - Constructor Escapes: Nested constructor chains like
{}.constructor.constructor
How It Works
Before executing any code, it's parsed into an Abstract Syntax Tree (AST) and validated:
// ❌ This will be blocked before execution
const code = `
const fs = require('fs');
return fs.readFileSync('/etc/passwd');
`;
// Error: "Code validation failed: Forbidden identifier: require"
// ✅ This is allowed
const code = `
const weather = await tools.getCurrentWeather({ location: "NYC" });
return weather.temperature;
`;
// Returns: 72Why It's Safe
- Instruction-Level Protection: The tool description explicitly tells the LLM not to attempt system access
- AST Validation: All code is parsed and validated before execution - dangerous patterns are caught statically
- Sandboxed Context: Code only has access to:
- Your provided tools
- Safe JavaScript globals (Math, JSON, Array, etc.)
- No access to Node.js APIs or system resources
Test Coverage
The library includes comprehensive security tests covering:
- Module system attacks (require, import)
- Code execution attacks (eval, Function constructor)
- Global object access (process, global, globalThis)
- Timer-based attacks (setTimeout, setInterval)
- Sneaky bypass attempts (constructor chains, indirect access)
All 19 security tests pass. See src/execute_tools.test.ts for details.
Benefits
For the LLM
- Efficiency: Compose multiple operations in one step instead of sequential round-trips
- Discovery: Search through available tools before using them
- Flexibility: Use standard JavaScript to orchestrate tool calls
For Developers
- Zero Configuration: Just wrap your existing tools
- No Changes Needed: Your tool definitions stay exactly the same
- Type Safe: Full TypeScript support with Zod schemas
- Secure by Default: AST-based validation protects against code injection
Use Cases
- Multi-step workflows: Get data, transform it, and send it somewhere
- Conditional logic: Execute tools based on results of previous calls
- Data aggregation: Combine results from multiple tools
- Complex orchestration: Let the LLM compose tools creatively
Example: Complex Workflow
const tools = createCodeTools({
searchProducts: {
description: 'Search for products',
inputSchema: z.object({
query: z.string(),
}),
execute: async ({ query }) => {
return [
{ id: 1, name: 'Laptop', price: 999 },
{ id: 2, name: 'Mouse', price: 29 },
];
},
},
getProductDetails: {
description: 'Get detailed product information',
inputSchema: z.object({
productId: z.number(),
}),
execute: async ({ productId }) => {
return { id: productId, stock: 10, rating: 4.5 };
},
},
addToCart: {
description: 'Add product to shopping cart',
inputSchema: z.object({
productId: z.number(),
quantity: z.number(),
}),
execute: async ({ productId, quantity }) => {
return { success: true, cartTotal: 999 };
},
},
});
const result = await generateText({
model: anthropic('claude-sonnet-4-5-20250929'),
prompt: 'Search for laptops, find the one with the best rating, and add it to my cart',
tools,
});
// The LLM can execute_tools with code like:
// const products = await tools.searchProducts({ query: "laptops" });
// const details = await Promise.all(
// products.map(p => tools.getProductDetails({ productId: p.id }))
// );
// const best = details.sort((a, b) => b.rating - a.rating)[0];
// return await tools.addToCart({ productId: best.id, quantity: 1 });Development
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Run unit tests with Vitest
npm run test:unitLicense
MIT
Contributing
Contributions are welcome! Please ensure all security tests pass before submitting PRs.
