Overview
LuaTool is the interface that all tools must implement. A tool is a single function that the AI can call to accomplish a specific task.
import { LuaTool } from 'lua-cli' ;
import { z } from 'zod' ;
export default class MyTool implements LuaTool {
name = "my_tool" ;
description = "What the tool does" ;
inputSchema = z . object ({ param: z . string () });
async execute ( input : any ) {
return { result: "success" };
}
}
Interface Definition
interface LuaTool < TInput extends ZodType = ZodType > {
name : string ;
description : string ;
inputSchema : TInput ;
execute : ( input : z . infer < TInput >) => Promise < any >;
condition ?: () => Promise < boolean >;
}
Required Properties
name
Unique identifier for the tool.
Tool name using only: a-z, A-Z, 0-9, -, _ Examples : "get_weather", "create-order", "sendEmail123"Invalid : "get weather", "tool.name", "send@email"
// ✅ Good
name = "get_weather" ;
name = "create-product" ;
name = "search_items" ;
// ❌ Bad
name = "get weather" ; // No spaces
name = "tool.name" ; // No dots
name = "send@email" ; // No special chars
description
Clear, concise description of what the tool does.
One sentence describing the tool’s purpose Helps the AI understand when to use this tool
// ✅ Good
description = "Get current weather conditions for any city worldwide" ;
description = "Create a new product in the catalog with price and details" ;
description = "Search for products by name, category, or description" ;
// ❌ Bad
description = "Gets data" ; // Too vague
description = "Does weather stuff" ; // Unclear
Zod schema that validates and types the input.
Zod schema defining valid inputs Provides runtime validation and TypeScript types
import { z } from 'zod' ;
// Simple
inputSchema = z . object ({
city: z . string ()
});
// With validation
inputSchema = z . object ({
email: z . string (). email (),
age: z . number (). min ( 0 ). max ( 120 )
});
// With descriptions
inputSchema = z . object ({
city: z . string (). describe ( "City name (e.g., 'London', 'Tokyo')" ),
units: z . enum ([ 'metric' , 'imperial' ]). describe ( "Temperature units" )
});
// With optional and default values
inputSchema = z . object ({
query: z . string (),
limit: z . number (). default ( 10 ),
offset: z . number (). optional ()
});
execute
Async function that implements the tool’s logic.
async execute ( input : z . infer < typeof this . inputSchema > ): Promise < any >
Input is automatically validated
Must return a JSON-serializable value
Can throw errors for failures
Optional Properties
condition
Async function that determines if the tool should be available to the AI.
async condition (): Promise < boolean >
Runs before the tool is offered to the AI
Return true to enable the tool, false to hide it
If the function throws an error, the tool is disabled (fail-closed)
Has access to all Platform APIs (User, Data, Products, etc.)
Use conditions to dynamically enable/disable tools based on:
User subscription status (premium features)
User verification or account status
Feature flags or A/B testing
Region-specific functionality
Time-based access
import { LuaTool , User } from 'lua-cli' ;
import { z } from 'zod' ;
export default class PremiumSearchTool implements LuaTool {
name = "premium_search" ;
description = "Advanced search with filters - premium users only" ;
inputSchema = z . object ({
query: z . string () ,
filters: z . object ({
minPrice: z . number (). optional (),
maxPrice: z . number (). optional ()
}). optional ()
});
// Only show this tool to premium users
condition = async () => {
const user = await User . get ();
return user . data ?. isPremium === true ;
} ;
async execute ( input : z . infer < typeof this . inputSchema >) {
// This only runs if condition returned true
return { results: [] };
}
}
Fail-closed behavior: If your condition function throws an error or times out (30s), the tool is automatically disabled. This ensures tools aren’t accidentally exposed when conditions can’t be evaluated.
Implementation Examples
import { LuaTool } from 'lua-cli' ;
import { z } from 'zod' ;
export default class GreetTool implements LuaTool {
name = "greet_user" ;
description = "Greet a user by name" ;
inputSchema = z . object ({
name: z . string ()
});
async execute ( input : z . infer < typeof this . inputSchema >) {
return {
message: `Hello, ${ input . name } !` ,
timestamp: new Date (). toISOString ()
};
}
}
import { LuaTool } from 'lua-cli' ;
import { z } from 'zod' ;
export default class GetWeatherTool implements LuaTool {
name = "get_weather" ;
description = "Get current weather for a city" ;
inputSchema = z . object ({
city: z . string (). describe ( "City name" ) ,
units: z . enum ([ 'metric' , 'imperial' ]). optional (). default ( 'metric' )
});
async execute ( input : z . infer < typeof this . inputSchema >) {
const { city , units } = input ;
// Call external API
const response = await fetch (
`https://api.weather.com/v1/weather?city= ${ city } &units= ${ units } `
);
if ( ! response . ok ) {
throw new Error ( `Weather API error: ${ response . statusText } ` );
}
const data = await response . json ();
return {
city: data . location ,
temperature: data . temp ,
condition: data . condition ,
humidity: data . humidity
};
}
}
import { LuaTool , Products } from 'lua-cli' ;
import { z } from 'zod' ;
export default class SearchProductsTool implements LuaTool {
name = "search_products" ;
description = "Search for products by name or description" ;
inputSchema = z . object ({
query: z . string (). describe ( "Search query" ) ,
limit: z . number (). min ( 1 ). max ( 100 ). default ( 10 )
});
async execute ( input : z . infer < typeof this . inputSchema >) {
const results = await Products . search ( input . query );
// Take only requested number of results
const products = results . data . slice ( 0 , input . limit );
return {
products: products . map ( p => ({
id: p . id ,
name: p . name ,
price: `$ ${ p . price . toFixed ( 2 ) } ` ,
inStock: p . inStock
})),
total: results . data . length ,
showing: products . length
};
}
}
import { LuaTool , env } from 'lua-cli' ;
import { z } from 'zod' ;
export default class SendEmailTool implements LuaTool {
name = "send_email" ;
description = "Send an email via SendGrid" ;
inputSchema = z . object ({
to: z . string (). email () ,
subject: z . string () ,
body: z . string ()
});
async execute ( input : z . infer < typeof this . inputSchema >) {
// Get API key from environment
const apiKey = env ( 'SENDGRID_API_KEY' );
if ( ! apiKey ) {
throw new Error ( 'SENDGRID_API_KEY not configured' );
}
// Send email
const response = await fetch ( 'https://api.sendgrid.com/v3/mail/send' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ apiKey } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
personalizations: [{ to: [{ email: input . to }] }],
from: { email: '[email protected] ' },
subject: input . subject ,
content: [{ type: 'text/plain' , value: input . body }]
})
});
if ( ! response . ok ) {
throw new Error ( `Email failed: ${ response . statusText } ` );
}
return {
success: true ,
message: `Email sent to ${ input . to } `
};
}
}
import { LuaTool , Products , Baskets } from 'lua-cli' ;
import { z } from 'zod' ;
export default class QuickCheckoutTool implements LuaTool {
name = "quick_checkout" ;
description = "Search, add to cart, and checkout in one step" ;
inputSchema = z . object ({
productName: z . string () ,
quantity: z . number (). min ( 1 ). default ( 1 ) ,
shippingAddress: z . object ({
street: z . string (),
city: z . string (),
zip: z . string ()
})
});
async execute ( input : z . infer < typeof this . inputSchema >) {
// Step 1: Search for product
const products = await Products . search ( input . productName );
if ( products . data . length === 0 ) {
throw new Error ( `Product not found: ${ input . productName } ` );
}
const product = products . data [ 0 ];
// Step 2: Create basket
const basket = await Baskets . create ({ currency: 'USD' });
// Step 3: Add product
await Baskets . addItem ( basket . id , {
id: product . id ,
price: product . price ,
quantity: input . quantity
});
// Step 4: Checkout
const order = await Baskets . placeOrder ({
shippingAddress: input . shippingAddress ,
paymentMethod: 'stripe'
}, basket . id );
return {
orderId: order . id ,
product: product . name ,
quantity: input . quantity ,
total: `$ ${ ( product . price * input . quantity ). toFixed ( 2 ) } ` ,
message: 'Order created successfully'
};
}
}
import { LuaTool , User , Products } from 'lua-cli' ;
import { z } from 'zod' ;
export default class PremiumAdvancedSearchTool implements LuaTool {
name = "premium_advanced_search" ;
description = "Advanced search with filters and sorting - premium users only" ;
inputSchema = z . object ({
query: z . string (). describe ( "Search query" ) ,
filters: z . object ({
category: z . string (). optional (),
minPrice: z . number (). optional (),
maxPrice: z . number (). optional ()
}). optional () ,
sortBy: z . enum ([ "relevance" , "price_asc" , "price_desc" , "newest" ]). optional ()
});
// Condition: Only show to premium users
condition = async () => {
const user = await User . get ();
// Check subscription status
const isPremium = user . data ?. subscription === "premium"
|| user . data ?. isPremium === true ;
return isPremium ;
} ;
async execute ( input : z . infer < typeof this . inputSchema >) {
const { query , filters , sortBy } = input ;
const searchResult = await Products . search ({ query , limit: 20 });
let products = searchResult . products ;
// Apply price filters
if ( filters ?. minPrice !== undefined || filters ?. maxPrice !== undefined ) {
products = products . filter ( p => {
if ( filters . minPrice && p . price < filters . minPrice ) return false ;
if ( filters . maxPrice && p . price > filters . maxPrice ) return false ;
return true ;
});
}
return {
query ,
totalResults: products . length ,
results: products . slice ( 0 , 10 ),
sortedBy: sortBy || "relevance"
};
}
}
Optional Fields
inputSchema = z . object ({
required: z . string (),
optional: z . string (). optional (),
withDefault: z . string (). default ( 'default value' )
});
Validation
inputSchema = z . object ({
email: z . string (). email (),
age: z . number (). min ( 18 ). max ( 120 ),
phone: z . string (). regex ( / ^ \+ ? [ 1-9 ] \d {1,14} $ / ),
url: z . string (). url (),
password: z . string (). min ( 8 )
});
Nested Objects
inputSchema = z . object ({
user: z . object ({
name: z . string (),
email: z . string (). email ()
}),
preferences: z . object ({
notifications: z . boolean (),
language: z . string ()
}). optional ()
});
Arrays
inputSchema = z . object ({
items: z . array ( z . object ({
id: z . string (),
quantity: z . number ()
})),
tags: z . array ( z . string ()). optional ()
});
Enums
inputSchema = z . object ({
status: z . enum ([ 'pending' , 'active' , 'completed' ]),
priority: z . enum ([ 'low' , 'medium' , 'high' ]). default ( 'medium' )
});
Error Handling
Throwing Errors
async execute ( input : any ) {
// Validate business logic
if ( input . amount <= 0 ) {
throw new Error ( "Amount must be positive" );
}
// API errors
const response = await fetch ( url );
if ( ! response . ok ) {
throw new Error ( `API error: ${ response . statusText } ` );
}
// Not found errors
const item = await findItem ( input . id );
if ( ! item ) {
throw new Error ( `Item not found: ${ input . id } ` );
}
return result ;
}
Try-Catch Pattern
async execute ( input : any ) {
try {
const result = await externalService . call ( input );
if ( ! result . success ) {
throw new Error ( result . error || 'Operation failed' );
}
return result . data ;
} catch ( error ) {
// Add context to errors
throw new Error ( `Failed to process request: ${ error . message } ` );
}
}
Return Value Patterns
Structured Data
// ✅ Good - Structured
return {
success: true ,
data: { id , name , price },
metadata: { timestamp , version }
};
// ❌ Bad - Unstructured string
return "Product created with ID 123" ;
Lists
return {
items: [ ... ],
total: 100 ,
page: 1 ,
hasMore: true
};
Status Updates
return {
status: 'completed' ,
message: 'Order shipped successfully' ,
trackingNumber: 'ABC123' ,
estimatedDelivery: '2025-10-10'
};
Best Practices
Always return objects, not strings: // ✅ Good
return { temperature: 72 , condition: "sunny" };
// ❌ Bad
return "The temperature is 72 and sunny" ;
Provide helpful error messages: if ( ! apiKey ) {
throw new Error ( 'API_KEY environment variable is required. Set it in .env file.' );
}
Next Steps