# LuaTool

The `LuaTool` interface defines the structure for creating custom tools that your AI agent can use. Tools are functions with validated inputs and well-defined outputs.

## Import

```typescript
import { LuaTool } from 'lua-cli/skill';
```

## Overview

Tools are the building blocks of your agent's capabilities. Each tool:
- Has a unique name
- Describes what it does
- Validates its inputs with Zod schemas
- Executes custom logic
- Returns structured data

## Interface Definition

```typescript
interface LuaTool<TInput extends ZodType = ZodType> {
  /** 
   * Unique tool name (lowercase, underscores only)
   * Example: 'get_weather', 'create_order'
   */
  name: string;
  
  /**
   * Human-readable description of what the tool does
   * The AI uses this to understand when to call the tool
   */
  description: string;
  
  /**
   * Zod schema defining the input structure
   * Validates and type-checks all inputs
   */
  inputSchema: TInput;
  
  /**
   * Optional Zod schema defining the output structure
   * Used for documentation and validation
   */
  outputSchema?: ZodType;
  
  /**
   * The function that executes when the tool is called
   * @param input - Validated input matching inputSchema
   * @returns Promise resolving to the tool's output
   */
  execute: (input: z.infer<TInput>) => Promise<any>;
}
```

## Creating a Tool

### Basic Example

```typescript
import { LuaTool } from 'lua-cli/skill';
import { z } from 'zod';

export default class GreetingTool implements LuaTool {
  name = 'greet_user';
  description = 'Generate a personalized greeting';
  
  inputSchema = z.object({
    name: z.string().describe('User name'),
    language: z.enum(['en', 'es', 'fr']).optional().describe('Language code')
  });
  
  async execute(input: z.infer<typeof this.inputSchema>) {
    const greetings = {
      en: `Hello, ${input.name}!`,
      es: `¡Hola, ${input.name}!`,
      fr: `Bonjour, ${input.name}!`
    };
    
    const lang = input.language || 'en';
    return { greeting: greetings[lang] };
  }
}
```

### With Output Schema

```typescript
import { LuaTool } from 'lua-cli/skill';
import { z } from 'zod';

export default class CalculatorTool implements LuaTool {
  name = 'calculator';
  description = 'Perform mathematical calculations';
  
  inputSchema = z.object({
    operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
    a: z.number(),
    b: z.number()
  });
  
  outputSchema = z.object({
    result: z.number(),
    operation: z.string()
  });
  
  async execute(input: z.infer<typeof this.inputSchema>) {
    let result: number;
    
    switch (input.operation) {
      case 'add':
        result = input.a + input.b;
        break;
      case 'subtract':
        result = input.a - input.b;
        break;
      case 'multiply':
        result = input.a * input.b;
        break;
      case 'divide':
        if (input.b === 0) {
          throw new Error('Division by zero');
        }
        result = input.a / input.b;
        break;
    }
    
    return {
      result,
      operation: input.operation
    };
  }
}
```

## Using Runtime APIs

Tools can access Lua platform APIs:

```typescript
import { LuaTool, User, Products, Data } from 'lua-cli/skill';
import { z } from 'zod';

export default class UserPreferencesTool implements LuaTool {
  name = 'save_user_preferences';
  description = 'Save user preferences for personalization';
  
  inputSchema = z.object({
    theme: z.enum(['light', 'dark']).optional(),
    language: z.string().optional(),
    notifications: z.boolean().optional()
  });
  
  async execute(input: z.infer<typeof this.inputSchema>) {
    // Get current user
    const user = await User.get();
    
    // Update user data
    await user.update({
      preferences: {
        theme: input.theme,
        language: input.language,
        notifications: input.notifications
      }
    });
    
    return {
      success: true,
      message: 'Preferences saved successfully',
      preferences: input
    };
  }
}
```

## External API Integration

```typescript
import { LuaTool } from 'lua-cli/skill';
import { z } from 'zod';
import axios from 'axios';
import { env } from 'lua-cli/skill';

export default class WeatherTool implements LuaTool {
  name = 'get_weather';
  description = 'Get current weather for any city';
  
  inputSchema = z.object({
    city: z.string().describe('City name'),
    units: z.enum(['metric', 'imperial']).default('metric')
  });
  
  outputSchema = z.object({
    temperature: z.number(),
    condition: z.string(),
    humidity: z.number(),
    city: z.string()
  });
  
  async execute(input: z.infer<typeof this.inputSchema>) {
    const apiKey = env('WEATHER_API_KEY');
    
    if (!apiKey) {
      throw new Error('WEATHER_API_KEY not configured');
    }
    
    try {
      const response = await axios.get('https://api.openweathermap.org/data/2.5/weather', {
        params: {
          q: input.city,
          appid: apiKey,
          units: input.units
        }
      });
      
      return {
        temperature: response.data.main.temp,
        condition: response.data.weather[0].main,
        humidity: response.data.main.humidity,
        city: input.city
      };
    } catch (error) {
      if (axios.isAxiosError(error)) {
        throw new Error(`Weather API error: ${error.message}`);
      }
      throw error;
    }
  }
}
```

## Error Handling

```typescript
import { LuaTool, Products } from 'lua-cli/skill';
import { z } from 'zod';

export default class GetProductTool implements LuaTool {
  name = 'get_product';
  description = 'Retrieve a product by ID';
  
  inputSchema = z.object({
    productId: z.string()
  });
  
  async execute(input: z.infer<typeof this.inputSchema>) {
    try {
      const product = await Products.getById(input.productId);
      
      if (!product) {
        return {
          success: false,
          error: 'Product not found',
          productId: input.productId
        };
      }
      
      return {
        success: true,
        product: {
          id: product.id,
          name: product.name,
          price: product.price,
          stock: product.stock
        }
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : 'Unknown error'
      };
    }
  }
}
```

## Complex Tool with State

```typescript
import { LuaTool, Data } from 'lua-cli/skill';
import { z } from 'zod';

export default class GameTool implements LuaTool {
  name = 'play_game';
  description = 'Play a number guessing game';
  
  inputSchema = z.object({
    action: z.enum(['start', 'guess', 'status']),
    guess: z.number().min(1).max(100).optional()
  });
  
  async execute(input: z.infer<typeof this.inputSchema>) {
    // Use Data API to persist game state
    const games = await Data.get('games');
    let gameState = games.length > 0 ? games[0].data : null;
    
    if (input.action === 'start') {
      // Start new game
      const secretNumber = Math.floor(Math.random() * 100) + 1;
      gameState = {
        secretNumber,
        attempts: 0,
        started: new Date()
      };
      
      await Data.create('games', gameState);
      
      return {
        message: 'Game started! Guess a number between 1 and 100',
        attempts: 0
      };
    }
    
    if (input.action === 'guess') {
      if (!gameState) {
        return { error: 'No active game. Use action: "start"' };
      }
      
      gameState.attempts++;
      
      if (input.guess === gameState.secretNumber) {
        await Data.delete('games', gameState.id);
        return {
          message: '🎉 Correct! You won!',
          attempts: gameState.attempts
        };
      }
      
      const hint = input.guess! < gameState.secretNumber ? 'higher' : 'lower';
      await Data.update('games', gameState.id, gameState);
      
      return {
        message: `Try ${hint}`,
        attempts: gameState.attempts
      };
    }
    
    // Status
    if (!gameState) {
      return { message: 'No active game' };
    }
    
    return {
      message: 'Game in progress',
      attempts: gameState.attempts
    };
  }
}
```

## Tool Composition

Tools can call other tools or create jobs:

```typescript
import { LuaTool, Jobs, JobInstance } from 'lua-cli/skill';
import { z } from 'zod';

export default class ReminderTool implements LuaTool {
  name = 'set_reminder';
  description = 'Set a reminder to notify user later';
  
  inputSchema = z.object({
    message: z.string(),
    minutes: z.number().min(1).max(1440)
  });
  
  async execute(input: z.infer<typeof this.inputSchema>) {
    // Create a job that will notify the user later
    const job = await Jobs.create({
      name: `reminder-${Date.now()}`,
      description: 'User reminder',
      metadata: {
        message: input.message
      },
      schedule: {
        type: 'once',
        executeAt: new Date(Date.now() + input.minutes * 60000)
      },
      execute: async (jobInstance: JobInstance) => {
        const user = await jobInstance.user();
        await user.send([{
          type: 'text',
          text: `⏰ Reminder: ${jobInstance.metadata.message}`
        }]);
        return { success: true };
      }
    });
    
    return {
      success: true,
      message: `Reminder set for ${input.minutes} minutes from now`,
      jobId: job.id
    };
  }
}
```

## Naming Conventions

### ✅ Good Names
- `get_weather` - Action + noun, clear purpose
- `create_order` - Verb + noun, describes action
- `search_products` - Clear, specific
- `calculate_tax` - Action verb, specific

### ❌ Bad Names
- `weather` - Too vague, unclear if getting or setting
- `doIt` - Meaningless
- `tool1` - Not descriptive
- `get-weather` - Use underscores, not hyphens

## Best Practices

### 1. Single Responsibility
Each tool should do one thing well:

```typescript
// ✅ Good - Focused purpose
class GetWeatherTool { }
class GetForecastTool { }

// ❌ Bad - Too many responsibilities
class WeatherAndNewsAndStocksTool { }
```

### 2. Descriptive Schemas

```typescript
// ✅ Good - With descriptions
inputSchema = z.object({
  email: z.string().email().describe('User email address'),
  sendCopy: z.boolean().optional().describe('Send copy to user')
});

// ❌ Bad - No context
inputSchema = z.object({
  email: z.string(),
  sendCopy: z.boolean()
});
```

### 3. Structured Returns

```typescript
// ✅ Good - Structured object
return {
  success: true,
  orderId: '123',
  total: 99.99,
  items: [...]
};

// ❌ Bad - Just a string
return 'Order 123 created for $99.99';
```

### 4. Error Handling

```typescript
// ✅ Good - Graceful error handling
async execute(input) {
  try {
    const result = await externalAPI.call(input);
    return { success: true, data: result };
  } catch (error) {
    return {
      success: false,
      error: error.message,
      suggestion: 'Please try again later'
    };
  }
}

// ❌ Bad - Uncaught errors
async execute(input) {
  const result = await externalAPI.call(input); // May throw!
  return result;
}
```

## Testing

Test your tool before adding to agent:

```bash
# Interactive testing
lua test

# Or create a test file
```

```typescript
// test-tool.ts
import MyTool from './src/tools/MyTool';

async function test() {
  const tool = new MyTool();
  const result = await tool.execute({ param: 'value' });
  console.log(result);
}

test();
```

## Related Documentation

- [LuaSkill](./LuaSkill.md) - Organizing tools into skills
- [LuaAgent](./LuaAgent.md) - Agent configuration
- [User API](./User.md) - Accessing user data
- [Products API](./Products.md) - Product operations
- [Data API](./Data.md) - Custom data storage

## Examples

- [Weather Tool](../../template/src/skills/tools/GetWeatherTool.ts)
- [Product Tools](../../template/src/skills/tools/ProductsTool.ts)
- [Basket Tools](../../template/src/skills/tools/BasketTool.ts)
- [Game Tracker](../../template/src/skills/tools/GameScoreTrackerTool.ts)

