Skip to main content
Convert TypeScript/JavaScript functions into AI-accessible tools with automatic schema generation and type safety. This is the most straightforward way to create custom tools.

Simple Functions

Basic Function Tool

Create a tool from a simple function:
import { tool } from '@ag-kit/tools';
import { z } from 'zod';

const addNumbersTool = tool(
  async ({ a, b }) => {
    return ToolResult({
      success: true,
      data: { result: a + b }
    });
  },
  {
    name: 'add_numbers',
    description: 'Add two numbers together',
    schema: z.object({
      a: z.number().describe('First number'),
      b: z.number().describe('Second number')
    })
  }
);

String Processing Tool

Create tools for text processing:
const textProcessTool = tool(
  async ({ text, operation, options = {} }) => {
    let processedText = text;

    if (options.trim) {
      processedText = processedText.trim();
    }

    if (options.removeSpaces) {
      processedText = processedText.replace(/\s+/g, '');
    }

    let result;
    switch (operation) {
      case 'uppercase':
        result = processedText.toUpperCase();
        break;
      case 'lowercase':
        result = processedText.toLowerCase();
        break;
      case 'reverse':
        result = processedText.split('').reverse().join('');
        break;
      case 'word_count':
        result = processedText.split(/\s+/).length;
        break;
    }

    return ToolResult({
      success: true,
      data: {
        original: text,
        processed: result,
        operation
      }
    });
  },
  {
    name: 'process_text',
    description: 'Process text with various operations',
    schema: z.object({
      text: z.string().describe('Input text'),
      operation: z.enum(['uppercase', 'lowercase', 'reverse', 'word_count']),
      options: z.object({
        trim: z.boolean().default(true),
        removeSpaces: z.boolean().default(false)
      }).optional()
    })
  }
);

Async Operations

HTTP Request Tool

Create tools that make HTTP requests:
const httpRequestTool = tool({
  name: 'http_request',
  description: 'Make HTTP requests to external APIs',
  schema: z.object({
    url: z.string().url(),
    method: z.enum(['GET', 'POST', 'PUT', 'DELETE']).default('GET'),
    headers: z.record(z.string()).optional(),
    body: z.any().optional(),
    timeout: z.number().default(30000)
  }),
  invoke: async ({ url, method, headers, body, timeout }) => {
    try {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), timeout);

      const response = await fetch(url, {
        method,
        headers: {
          'Content-Type': 'application/json',
          ...headers
        },
        body: body ? JSON.stringify(body) : undefined,
        signal: controller.signal
      });

      clearTimeout(timeoutId);

      if (!response.ok) {
        return ToolResult({
          success: false,
          error: `HTTP ${response.status}: ${response.statusText}`,
          error_type: 'network'
        });
      }

      const data = await response.json();

      return ToolResult({
        success: true,
        data: {
          status: response.status,
          headers: Object.fromEntries(response.headers.entries()),
          body: data
        }
      });
    } catch (error) {
      if (error.name === 'AbortError') {
        return ToolResult({
          success: false,
          error: 'Request timeout',
          error_type: 'network'
        });
      }

      return ToolResult({
        success: false,
        error: error.message,
        error_type: 'execution'
      });
    }
  }
});

Complex Parameters

Validation and Transformation

Create tools with complex validation:
const userManagementTool = tool({
  name: 'manage_user',
  description: 'Manage user accounts with validation',
  schema: z.object({
    action: z.enum(['create', 'update', 'delete', 'get']),
    userId: z.string().uuid().optional(),
    userData: z.object({
      email: z.string().email(),
      name: z.string().min(2).max(50),
      age: z.number().min(13).max(120),
      roles: z.array(z.enum(['admin', 'user', 'moderator'])).default(['user']),
      preferences: z.object({
        theme: z.enum(['light', 'dark']).default('light'),
        notifications: z.boolean().default(true),
        language: z.string().length(2).default('en')
      }).optional()
    }).optional()
  }).refine(
    (data) => {
      if (data.action === 'create' && !data.userData) {
        return false;
      }
      if (['update', 'delete', 'get'].includes(data.action) && !data.userId) {
        return false;
      }
      return true;
    },
    {
      message: 'userData required for create, userId required for update/delete/get'
    }
  ),
  invoke: async ({ action, userId, userData }) => {
    try {
      switch (action) {
        case 'create':
          const newUser = {
            id: crypto.randomUUID(),
            ...userData,
            createdAt: new Date().toISOString()
          };
          
          // Simulate database save
          await saveUser(newUser);
          
          return ToolResult({
            success: true,
            data: { user: newUser, message: 'User created successfully' }
          });
          
        case 'update':
          const existingUser = await getUser(userId);
          if (!existingUser) {
            return ToolResult({
              success: false,
              error: 'User not found',
              error_type: 'execution'
            });
          }
          
          const updatedUser = {
            ...existingUser,
            ...userData,
            updatedAt: new Date().toISOString()
          };
          
          await saveUser(updatedUser);
          
          return ToolResult({
            success: true,
            data: { user: updatedUser, message: 'User updated successfully' }
          });
          
        case 'delete':
          await deleteUser(userId);
          return ToolResult({
            success: true,
            data: { message: 'User deleted successfully' }
          });
          
        case 'get':
          const user = await getUser(userId);
          if (!user) {
            return ToolResult({
              success: false,
              error: 'User not found',
              error_type: 'execution'
            });
          }
          
          return ToolResult({
            success: true,
            data: { user }
          });
          
        default:
          return ToolResult({
            success: false,
            error: 'Invalid action',
            error_type: 'validation'
          });
      }
    } catch (error) {
      return ToolResult({
        success: false,
        error: error.message,
        error_type: 'execution'
      });
    }
  }
});

// Mock database functions
async function saveUser(user: any) {
  // Implementation here
}

async function getUser(userId: string) {
  // Implementation here
}

async function deleteUser(userId: string) {
  // Implementation here
}

Error Handling

Comprehensive Error Handling

Implement robust error handling patterns:
const robustApiTool = tool({
  name: 'robust_api_call',
  description: 'Make API calls with comprehensive error handling',
  schema: z.object({
    endpoint: z.string().url(),
    retries: z.number().min(0).max(5).default(3),
    backoffMs: z.number().min(100).max(10000).default(1000)
  }),
  invoke: async ({ endpoint, retries, backoffMs }) => {
    let lastError: Error | null = null;
    
    for (let attempt = 0; attempt <= retries; attempt++) {
      try {
        const response = await fetch(endpoint, {
          timeout: 10000,
          headers: {
            'User-Agent': 'AG-Kit Tool/1.0'
          }
        });
        
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        
        const data = await response.json();
        
        return ToolResult({
          success: true,
          data: {
            result: data,
            attempts: attempt + 1,
            endpoint
          }
        });
        
      } catch (error) {
        lastError = error;
        
        // Don't retry on certain errors
        if (error.message.includes('404') || error.message.includes('401')) {
          break;
        }
        
        // Wait before retry (exponential backoff)
        if (attempt < retries) {
          const delay = backoffMs * Math.pow(2, attempt);
          await new Promise(resolve => setTimeout(resolve, delay));
        }
      }
    }
    
    return new ToolResult({
      success: false,
      error: `Failed after ${retries + 1} attempts: ${lastError?.message}`,
      error_type: 'network',
      executionTime: Date.now()
    });
  }
});

Input Sanitization

Sanitize and validate inputs:
const sanitizedInputTool = tool({
  name: 'process_user_input',
  description: 'Process user input with sanitization',
  schema: z.object({
    userInput: z.string(),
    allowHtml: z.boolean().default(false),
    maxLength: z.number().default(1000)
  }),
  invoke: async ({ userInput, allowHtml, maxLength }) => {
    try {
      // Length validation
      if (userInput.length > maxLength) {
        return ToolResult({
          success: false,
          error: `Input too long (max ${maxLength} characters)`,
          error_type: 'validation'
        });
      }
      
      // Sanitize input
      let sanitized = userInput.trim();
      
      if (!allowHtml) {
        // Remove HTML tags
        sanitized = sanitized.replace(/<[^>]*>/g, '');
        
        // Escape special characters
        sanitized = sanitized
          .replace(/&/g, '&amp;')
          .replace(/</g, '&lt;')
          .replace(/>/g, '&gt;')
          .replace(/"/g, '&quot;')
          .replace(/'/g, '&#x27;');
      }
      
      // Check for suspicious patterns
      const suspiciousPatterns = [
        /javascript:/i,
        /data:text\/html/i,
        /vbscript:/i,
        /<script/i
      ];
      
      const hasSuspiciousContent = suspiciousPatterns.some(
        pattern => pattern.test(sanitized)
      );
      
      if (hasSuspiciousContent) {
        return ToolResult({
          success: false,
          error: 'Input contains suspicious content',
          error_type: 'validation'
        });
      }
      
      return ToolResult({
        success: true,
        data: {
          original: userInput,
          sanitized,
          length: sanitized.length,
          wasModified: userInput !== sanitized
        }
      });
      
    } catch (error) {
      return ToolResult({
        success: false,
        error: error.message,
        error_type: 'execution'
      });
    }
  }
});

Integration Patterns

Tool Composition

Combine multiple function tools:
// Create individual tools
const validateEmailTool = tool({
  name: 'validate_email',
  description: 'Validate email address format',
  schema: z.object({
    email: z.string()
  }),
  invoke: async ({ email }) => {
    const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
    return ToolResult({
      success: true,
      data: { email, isValid }
    });
  }
});

const sendEmailTool = tool({
  name: 'send_email',
  description: 'Send email message',
  schema: z.object({
    to: z.string().email(),
    subject: z.string(),
    body: z.string()
  }),
  invoke: async ({ to, subject, body }) => {
    // Email sending logic
    return ToolResult({
      success: true,
      data: { messageId: 'msg_123', sentAt: new Date().toISOString() }
    });
  }
});

// Composite tool that uses both
const emailWorkflowTool = tool({
  name: 'email_workflow',
  description: 'Validate and send email in one operation',
  schema: z.object({
    to: z.string(),
    subject: z.string(),
    body: z.string()
  }),
  invoke: async ({ to, subject, body }) => {
    // Validate email first
    const validationResult = await validateEmailTool.invoke({ email: to });
    
    if (!validationResult.success || !validationResult.data.isValid) {
      return ToolResult({
        success: false,
        error: 'Invalid email address',
        error_type: 'validation'
      });
    }
    
    // Send email
    const sendResult = await sendEmailTool.invoke({ to, subject, body });
    
    return ToolResult({
      success: sendResult.success,
      data: {
        validation: validationResult.data,
        email: sendResult.data
      },
      error: sendResult.error,
      error_type: sendResult.error_type
    });
  }
});

With Agents

Use function tools with AG-Kit agents:
import { Agent, OpenAIProvider } from '@ag-kit/agents';

const agent = new Agent({
  name: 'multi-tool-agent',
  model: new OpenAIProvider({
    apiKey: process.env.OPENAI_API_KEY!,
    defaultModel: 'gpt-4'
  }),
  tools: [
    addNumbersTool,
    textProcessTool,
    httpRequestTool,
    userManagementTool
  ],
  instructions: `You are a helpful assistant with access to various tools:
- Mathematical operations
- Text processing
- HTTP requests
- User management

Use these tools to help users accomplish their tasks.`
});

const response = await agent.run({
  input: 'Calculate 15 + 27, then convert the result to uppercase text'
});

Best Practices

Performance Optimization

Optimize function tools for performance:
// Use connection pooling for database tools
class DatabasePool {
  private connections: any[] = [];
  
  async getConnection() {
    if (this.connections.length > 0) {
      return this.connections.pop();
    }
    return await createNewConnection();
  }
  
  releaseConnection(conn: any) {
    if (this.connections.length < 10) {
      this.connections.push(conn);
    } else {
      conn.close();
    }
  }
}

const dbPool = new DatabasePool();

const optimizedDbTool = tool({
  name: 'optimized_db_query',
  description: 'Database query with connection pooling',
  schema: z.object({
    query: z.string(),
    params: z.array(z.any()).default([])
  }),
  invoke: async ({ query, params }) => {
    const conn = await dbPool.getConnection();
    try {
      const result = await conn.query(query, params);
      return ToolResult({ success: true, data: result });
    } finally {
      dbPool.releaseConnection(conn);
    }
  }
});

Caching

Implement caching for expensive operations:
const cache = new Map();

const cachedApiTool = tool({
  name: 'cached_api_call',
  description: 'API call with caching',
  schema: z.object({
    url: z.string().url(),
    cacheTtl: z.number().default(300000) // 5 minutes
  }),
  invoke: async ({ url, cacheTtl }) => {
    const cached = cache.get(url);
    
    if (cached && Date.now() - cached.timestamp < cacheTtl) {
      return ToolResult({
        success: true,
        data: { ...cached.data, fromCache: true }
      });
    }
    
    try {
      const response = await fetch(url);
      const data = await response.json();
      
      cache.set(url, {
        data,
        timestamp: Date.now()
      });
      
      return ToolResult({
        success: true,
        data: { ...data, fromCache: false }
      });
    } catch (error) {
      return ToolResult({
        success: false,
        error: error.message,
        error_type: 'network'
      });
    }
  }
});

Next Steps