Actions
Actions are custom voice commands that you define for your application. They allow the AI to perform business logic specific to your app.
⚠️ CRITICAL: Action Registration Timing
All custom actions MUST be registered BEFORE starting the voice session!
// ✅ CORRECT - Register actions before starting session
vowel.registerAction('addToCart', definition, handler);
vowel.registerAction('searchProducts', definition, handler);
await vowel.startSession();
// ❌ WRONG - Registering actions after session starts has NO EFFECT
await vowel.startSession();
vowel.registerAction('addToCart', definition, handler); // Too late! Won't work!Why? Tool definitions are sent to the server during session initialization. Actions registered after startSession() are not sent to the AI and will never be called.
Best Practice: Always register all actions immediately after creating the Vowel client and before calling startSession().
Overview
When you register an action, the AI can:
- Understand when to call it based on user voice input
- Extract parameters from natural language
- Execute your handler function
- Return results to continue the conversation
Basic Action Registration
import { vowel } from './vowel.client';
vowel.registerAction('addToCart', {
description: 'Add product to shopping cart',
parameters: {
productId: {
type: 'string',
description: 'Product ID'
},
quantity: {
type: 'number',
description: 'Quantity to add',
default: 1
}
}
}, async ({ productId, quantity }) => {
// Your business logic
await addProductToCart(productId, quantity);
return {
success: true,
message: `Added ${quantity} item(s) to cart`
};
});Action Definition
VowelAction Interface
interface VowelAction {
description: string;
parameters: {
[key: string]: VowelActionParameter;
};
}
interface VowelActionParameter {
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
description: string;
required?: boolean;
default?: any;
enum?: any[];
}Parameter Types
Actions support multiple parameter types:
vowel.registerAction('createEvent', {
description: 'Create a calendar event',
parameters: {
// String parameter
title: {
type: 'string',
description: 'Event title',
required: true
},
// Number parameter
duration: {
type: 'number',
description: 'Duration in minutes',
default: 60
},
// Boolean parameter
allDay: {
type: 'boolean',
description: 'Is this an all-day event?',
default: false
},
// Enum parameter
priority: {
type: 'string',
description: 'Event priority',
enum: ['low', 'medium', 'high'],
default: 'medium'
},
// Array parameter
attendees: {
type: 'array',
description: 'List of attendee email addresses'
},
// Object parameter
location: {
type: 'object',
description: 'Event location details'
}
}
}, async (params) => {
await createCalendarEvent(params);
return { success: true };
});Action Handler
The action handler receives parameters and returns a result:
type ActionHandler<T = any> = (
params: T,
context?: ToolContext
) => Promise<VowelToolResult> | VowelToolResult;
interface VowelToolResult {
success: boolean;
message?: string;
data?: any;
error?: string;
}Basic Handler
vowel.registerAction('getWeather', {
description: 'Get current weather for a location',
parameters: {
city: {
type: 'string',
description: 'City name',
required: true
}
}
}, async ({ city }) => {
const weather = await fetchWeather(city);
return {
success: true,
message: `The weather in ${city} is ${weather.condition}`,
data: weather
};
});Error Handling
vowel.registerAction('processPayment', {
description: 'Process payment for order',
parameters: {
orderId: {
type: 'string',
description: 'Order ID',
required: true
}
}
}, async ({ orderId }) => {
try {
const result = await processPayment(orderId);
return {
success: true,
message: 'Payment processed successfully',
data: result
};
} catch (error) {
return {
success: false,
error: error.message,
message: 'Payment processing failed'
};
}
});Common Action Patterns
Search Action
vowel.registerAction('searchProducts', {
description: 'Search for products in the catalog',
parameters: {
query: {
type: 'string',
description: 'Search query',
required: true
},
category: {
type: 'string',
description: 'Product category filter'
},
maxResults: {
type: 'number',
description: 'Maximum number of results',
default: 10
}
}
}, async ({ query, category, maxResults }) => {
const results = await searchProducts({
query,
category,
limit: maxResults
});
return {
success: true,
message: `Found ${results.length} products`,
data: results
};
});CRUD Operations
// Create
vowel.registerAction('createTask', {
description: 'Create a new task',
parameters: {
title: { type: 'string', description: 'Task title', required: true },
description: { type: 'string', description: 'Task description' },
dueDate: { type: 'string', description: 'Due date (ISO format)' }
}
}, async (params) => {
const task = await createTask(params);
return { success: true, data: task };
});
// Read
vowel.registerAction('getTask', {
description: 'Get task details',
parameters: {
taskId: { type: 'string', description: 'Task ID', required: true }
}
}, async ({ taskId }) => {
const task = await getTask(taskId);
return { success: true, data: task };
});
// Update
vowel.registerAction('updateTask', {
description: 'Update task details',
parameters: {
taskId: { type: 'string', description: 'Task ID', required: true },
title: { type: 'string', description: 'New title' },
completed: { type: 'boolean', description: 'Mark as completed' }
}
}, async ({ taskId, ...updates }) => {
const task = await updateTask(taskId, updates);
return { success: true, data: task };
});
// Delete
vowel.registerAction('deleteTask', {
description: 'Delete a task',
parameters: {
taskId: { type: 'string', description: 'Task ID', required: true }
}
}, async ({ taskId }) => {
await deleteTask(taskId);
return { success: true, message: 'Task deleted' };
});Cart Management
vowel.registerAction('addToCart', {
description: 'Add product to shopping cart',
parameters: {
productId: { type: 'string', required: true },
quantity: { type: 'number', default: 1 }
}
}, async ({ productId, quantity }) => {
await cart.add(productId, quantity);
return { success: true, message: `Added ${quantity} item(s) to cart` };
});
vowel.registerAction('removeFromCart', {
description: 'Remove product from cart',
parameters: {
productId: { type: 'string', required: true }
}
}, async ({ productId }) => {
await cart.remove(productId);
return { success: true, message: 'Removed from cart' };
});
vowel.registerAction('viewCart', {
description: 'View shopping cart contents',
parameters: {}
}, async () => {
const items = await cart.getItems();
const total = await cart.getTotal();
return {
success: true,
message: `You have ${items.length} items totaling $${total}`,
data: { items, total }
};
});Form Submission
vowel.registerAction('submitContactForm', {
description: 'Submit contact form',
parameters: {
name: { type: 'string', required: true },
email: { type: 'string', required: true },
message: { type: 'string', required: true }
}
}, async ({ name, email, message }) => {
await submitContactForm({ name, email, message });
return {
success: true,
message: 'Thank you! Your message has been sent.'
};
});Built-in Actions
Vowel automatically registers actions when you provide adapters:
From NavigationAdapter
navigate_to_page- Navigate to any route
From AutomationAdapter
search_page_elements- Search for elementsget_page_snapshot- Get page structureclick_element- Click any elementtype_into_element- Type into inputsfocus_element- Focus an elementscroll_to_element- Scroll to elementpress_key- Press keyboard keys
You don't need to register these manually - they work automatically!
Action Context
Handlers receive an optional context parameter:
interface ToolContext {
sessionId: string;
timestamp: number;
// Additional context from the session
}
vowel.registerAction('logActivity', {
description: 'Log user activity',
parameters: {
activity: { type: 'string', required: true }
}
}, async ({ activity }, context) => {
console.log('Session:', context?.sessionId);
console.log('Timestamp:', context?.timestamp);
await logActivity(activity, context);
return { success: true };
});TypeScript Support
Use generics for type-safe parameters:
interface AddToCartParams {
productId: string;
quantity: number;
}
vowel.registerAction<AddToCartParams>('addToCart', {
description: 'Add product to cart',
parameters: {
productId: { type: 'string', required: true },
quantity: { type: 'number', default: 1 }
}
}, async (params) => {
// params is typed as AddToCartParams
const { productId, quantity } = params;
await addToCart(productId, quantity);
return { success: true };
});Best Practices
- Clear Descriptions - Write clear, concise descriptions for actions and parameters
- Validate Input - Always validate parameters in your handler
- Handle Errors - Return proper error messages for failures
- Return Useful Data - Include relevant data in the response
- Keep Handlers Fast - Avoid long-running operations
- Use TypeScript - Leverage type safety for parameters
- Test Actions - Test action handlers independently
- Document Parameters - Provide detailed parameter descriptions
Testing Actions
Test action handlers independently:
// Extract handler for testing
const addToCartHandler = async ({ productId, quantity }) => {
await cart.add(productId, quantity);
return { success: true };
};
// Test
describe('addToCart', () => {
it('should add product to cart', async () => {
const result = await addToCartHandler({
productId: 'prod-123',
quantity: 2
});
expect(result.success).toBe(true);
expect(cart.items).toContain('prod-123');
});
});
// Register with tested handler
vowel.registerAction('addToCart', definition, addToCartHandler);Related
- Vowel Client - Core client API
- Adapters - Built-in actions from adapters
- Custom Actions Recipe - Advanced patterns
- API Reference - Complete API