Custom Tools
Build custom tools to extend your AI agent's capabilities with your own logic.
Overview
Custom tools allow you to extend your AI agent with your own business logic, integrations, and functions. You can create tools using JavaScript/TypeScript or connect to external services via webhooks.
Tool Types
1. JavaScript Function
Execute custom JavaScript code:
json{ "name": "calculate_shipping", "type": "function", "description": "Calculate shipping cost based on weight and destination", "code": "async ({ weight, destination, expedited }) => { const baseRate = destination === 'international' ? 15 : 5; const weightRate = weight * 0.5; const expeditedFee = expedited ? 10 : 0; return { cost: baseRate + weightRate + expeditedFee, currency: 'USD', estimated_days: expedited ? 2 : 5 }; }", "parameters": { "type": "object", "properties": { "weight": { "type": "number", "description": "Package weight in pounds" }, "destination": { "type": "string", "enum": ["domestic", "international"], "description": "Shipping destination type" }, "expedited": { "type": "boolean", "default": false, "description": "Whether to use expedited shipping" } }, "required": ["weight", "destination"] } }
2. Webhook Tool
Call an external endpoint:
json{ "name": "verify_identity", "type": "webhook", "description": "Verify user identity through external service", "config": { "url": "https://your-service.com/api/verify", "method": "POST", "headers": { "Authorization": "Bearer {{credentials.verification_api_key}}" }, "body_template": { "name": "{{parameters.full_name}}", "dob": "{{parameters.date_of_birth}}", "ssn_last_4": "{{parameters.ssn_last_4}}" } }, "parameters": { "type": "object", "properties": { "full_name": { "type": "string" }, "date_of_birth": { "type": "string", "format": "date" }, "ssn_last_4": { "type": "string", "pattern": "^[0-9]{4}$" } }, "required": ["full_name", "date_of_birth", "ssn_last_4"] } }
3. Composite Tool
Combine multiple tools into one:
json{ "name": "process_order", "type": "composite", "description": "Process a complete order (validate, charge, fulfill)", "steps": [ { "tool": "validate_inventory", "params": { "product_id": "{{parameters.product_id}}" } }, { "tool": "charge_payment", "params": { "amount": "{{steps.0.price}}", "customer_id": "{{parameters.customer_id}}" }, "condition": "{{steps.0.in_stock === true}}" }, { "tool": "create_fulfillment", "params": { "order_id": "{{steps.1.order_id}}", "address": "{{parameters.shipping_address}}" } } ] }
Creating Custom Tools
Via UI
- Go to Agents → Select agent → Tools
- Click Create Tool
- Choose tool type (Function, Webhook, etc.)
- Configure the tool
- Test and save
Via API
bashcurl -X POST "https://api.arcanflows.com/api/v1/agents/{agent_id}/tools" \ -H "X-API-Key: your_api_key" \ -H "Content-Type: application/json" \ -d '{ "name": "my_custom_tool", "type": "function", "description": "Description for the AI agent", "code": "async (params) => { return { result: params.input * 2 }; }", "parameters": { "type": "object", "properties": { "input": { "type": "number" } }, "required": ["input"] } }'
JavaScript Function Tools
Basic Structure
javascriptasync (parameters, context) => { // parameters: The input from the AI agent // context: Additional context (user, tenant, etc.) // Your logic here const result = doSomething(parameters.input); // Return the result return { success: true, data: result }; }
Available Context
javascriptasync (params, context) => { // User information const userId = context.user.id; const userEmail = context.user.email; // Tenant information const tenantId = context.tenant.id; // Agent information const agentId = context.agent.id; // Credentials from vault const apiKey = context.credentials.my_api_key; // Current timestamp const now = context.timestamp; return { userId, tenantId }; }
Making HTTP Requests
javascriptasync (params, context) => { const response = await fetch('https://api.example.com/data', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${context.credentials.api_key}` }, body: JSON.stringify({ query: params.query }) }); if (!response.ok) { throw new Error(`API error: ${response.status}`); } return await response.json(); }
Error Handling
javascriptasync (params, context) => { try { // Validate input if (!params.email || !params.email.includes('@')) { return { success: false, error: 'Invalid email address' }; } // Process const result = await processEmail(params.email); return { success: true, data: result }; } catch (error) { // Log error (available in execution logs) console.error('Tool error:', error); return { success: false, error: error.message }; } }
Working with Dates
javascriptasync (params) => { const startDate = new Date(params.start_date); const endDate = new Date(params.end_date); const daysDiff = Math.ceil( (endDate - startDate) / (1000 * 60 * 60 * 24) ); return { start: startDate.toISOString(), end: endDate.toISOString(), duration_days: daysDiff }; }
Data Transformation
javascriptasync (params) => { const items = params.items || []; // Transform data const transformed = items.map(item => ({ id: item.id, fullName: `${item.firstName} ${item.lastName}`, totalValue: item.quantity * item.price })); // Aggregate const total = transformed.reduce( (sum, item) => sum + item.totalValue, 0 ); return { items: transformed, count: transformed.length, total: total }; }
Example Tools
Price Calculator
json{ "name": "calculate_price", "type": "function", "description": "Calculate total price with discounts and tax", "code": "async ({ items, discount_code, tax_rate }) => { let subtotal = items.reduce((sum, item) => sum + (item.price * item.quantity), 0); let discount = 0; if (discount_code === 'SAVE10') discount = subtotal * 0.10; if (discount_code === 'SAVE20') discount = subtotal * 0.20; const afterDiscount = subtotal - discount; const tax = afterDiscount * (tax_rate / 100); const total = afterDiscount + tax; return { subtotal: subtotal.toFixed(2), discount: discount.toFixed(2), tax: tax.toFixed(2), total: total.toFixed(2) }; }", "parameters": { "type": "object", "properties": { "items": { "type": "array", "items": { "type": "object", "properties": { "price": { "type": "number" }, "quantity": { "type": "integer" } } } }, "discount_code": { "type": "string" }, "tax_rate": { "type": "number", "default": 8.5 } }, "required": ["items"] } }
Appointment Scheduler
json{ "name": "find_available_slots", "type": "function", "description": "Find available appointment slots for a given date", "code": "async ({ date, duration_minutes }, context) => { const workStart = 9; const workEnd = 17; const slotDuration = duration_minutes || 30; const booked = await context.db.query('SELECT start_time, end_time FROM appointments WHERE date = $1', [date]); const slots = []; for (let hour = workStart; hour < workEnd; hour++) { for (let min = 0; min < 60; min += slotDuration) { const slotStart = `${hour.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}`; const isBooked = booked.some(apt => apt.start_time <= slotStart && apt.end_time > slotStart); if (!isBooked) slots.push(slotStart); } } return { date, available_slots: slots, slot_duration: slotDuration }; }" }
Email Validator
json{ "name": "validate_email", "type": "function", "description": "Validate email format and check if domain exists", "code": "async ({ email }) => { const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/; if (!emailRegex.test(email)) { return { valid: false, reason: 'Invalid email format' }; } const domain = email.split('@')[1]; try { const response = await fetch(`https://dns.google/resolve?name=${domain}&type=MX`); const data = await response.json(); const hasMX = data.Answer && data.Answer.length > 0; return { valid: hasMX, domain, has_mx_record: hasMX, reason: hasMX ? 'Valid email' : 'Domain has no MX records' }; } catch { return { valid: true, domain, has_mx_record: 'unknown', reason: 'Could not verify MX records' }; } }" }
Security Considerations
Sandboxed Execution
Custom functions run in a sandboxed environment:
- Limited execution time (30 seconds default)
- Memory limits enforced
- No file system access
- No process spawning
- Network requests allowed (with restrictions)
Input Validation
Always validate inputs:
javascriptasync (params) => { // Type checking if (typeof params.amount !== 'number') { throw new Error('Amount must be a number'); } // Range validation if (params.amount < 0 || params.amount > 10000) { throw new Error('Amount must be between 0 and 10000'); } // Continue with valid input return processAmount(params.amount); }
Sensitive Data
Never return sensitive data directly:
javascriptasync (params, context) => { const user = await getUser(params.user_id); // Good - return only necessary fields return { id: user.id, name: user.name, email: user.email }; // Bad - exposes sensitive data // return user; // includes password_hash, ssn, etc. }
Testing Custom Tools
Unit Testing
javascript// test-tool.js const toolCode = require('./my-tool'); describe('calculate_price', () => { it('calculates correctly without discount', async () => { const result = await toolCode({ items: [{ price: 10, quantity: 2 }], tax_rate: 10 }); expect(result.subtotal).toBe('20.00'); expect(result.tax).toBe('2.00'); expect(result.total).toBe('22.00'); }); });
Integration Testing
bashcurl -X POST "https://api.arcanflows.com/api/v1/agents/{agent_id}/tools/{tool_id}/test" \ -H "X-API-Key: your_api_key" \ -H "Content-Type: application/json" \ -d '{ "parameters": { "items": [{"price": 10, "quantity": 2}], "discount_code": "SAVE10", "tax_rate": 8 } }'
Debugging
Console Logs
Use console.log for debugging (appears in execution logs):
javascriptasync (params) => { console.log('Input:', JSON.stringify(params)); const result = processData(params); console.log('Result:', JSON.stringify(result)); return result; }
Error Inspection
View tool execution errors in:
- Agent → Logs → Filter by tool name
- API: GET /agents/{id}/tools/{tool_id}/executions
Best Practices
- Keep functions focused - One tool, one purpose
- Handle errors gracefully - Return meaningful error messages
- Validate inputs - Check types and ranges
- Document thoroughly - Clear descriptions help the AI use tools correctly
- Test extensively - Cover edge cases
- Monitor performance - Track execution times
- Use credentials vault - Never hardcode secrets