Frontend Tools
Build client-side tools that run in the browser
Create tools that run in the browser with access to DOM, state, and UI.
Why Frontend Tools?
- Access browser APIs - DOM, localStorage, navigation
- Use React state - Read/write app state directly
- Show rich UI - Render components as results
- Real-time updates - Stream results as they happen
Basic Tool
import { useTools } from '@yourgpt/copilot-sdk/react';
import { z } from 'zod';
function MyTools() {
useTools({
search_products: {
description: 'Search the product catalog',
parameters: z.object({
query: z.string().describe('Search query'),
limit: z.number().optional().default(10),
}),
handler: async ({ query, limit }) => {
const results = await fetch(`/api/search?q=${query}&limit=${limit}`);
return results.json();
},
},
});
return null;
}Tool Structure
| Field | Required | Description |
|---|---|---|
description | Yes | What the tool does (AI reads this) |
parameters | Yes | Zod schema for inputs |
handler | Yes | Async function that runs |
requiresApproval | No | Ask user before running |
hidden | No | Hide tool from chat UI (still executes) |
Multiple Tools
useTools({
get_weather: {
description: 'Get weather for a city',
parameters: z.object({ city: z.string() }),
handler: async ({ city }) => fetchWeather(city),
},
add_to_cart: {
description: 'Add product to cart',
parameters: z.object({
productId: z.string(),
quantity: z.number().default(1),
}),
handler: async ({ productId, quantity }) => {
await cart.add(productId, quantity);
return { success: true };
},
},
navigate: {
description: 'Go to a page in the app',
parameters: z.object({ path: z.string() }),
handler: async ({ path }) => {
router.push(path);
return { navigated: path };
},
},
});With Approval
Require user confirmation before running:
useTools({
delete_account: {
description: 'Permanently delete user account',
parameters: z.object({}),
requiresApproval: true,
handler: async () => {
await deleteAccount();
return { deleted: true };
},
},
});Use requiresApproval: true for destructive or sensitive actions.
Hidden Tools
Hide tool execution from the chat UI while still executing normally:
useTools({
navigate: {
description: 'Navigate to a page in the app',
parameters: z.object({ path: z.string() }),
hidden: true, // Won't show in chat UI
handler: async ({ path }) => {
router.push(path);
return { navigated: path };
},
},
track_event: {
description: 'Track analytics event',
parameters: z.object({
event: z.string(),
properties: z.record(z.any()).optional(),
}),
hidden: true, // Silent background operation
handler: async ({ event, properties }) => {
analytics.track(event, properties);
return { tracked: true };
},
},
});Hidden tools are useful for navigation, analytics, or background operations that shouldn't clutter the chat UI.
AI Response Control
Control how the AI responds after tool execution:
handler: async ({ query }) => {
const results = await search(query);
return {
data: results,
_aiResponseMode: 'brief', // 'verbose' | 'brief' | 'silent'
_aiContext: `Found ${results.length} results`,
_aiContent: 'Here are the results:',
};
}| Mode | Behavior |
|---|---|
verbose | AI explains results in detail |
brief | AI gives short summary |
silent | No AI response, just show tool result |
Error Handling
handler: async ({ query }) => {
try {
const results = await search(query);
return { success: true, data: results };
} catch (error) {
return {
success: false,
error: error.message,
};
}
}Best Practices
- Clear descriptions - AI uses this to decide when to call the tool
- Validate with Zod - Type safety and clear parameter docs
- Return structured data - AI understands JSON better than strings
- Handle errors - Return error info instead of throwing
- Use approval for destructive actions - Delete, purchase, send
Next Steps
- Backend Tools - Server-side tools
- Generative UI - Custom tool renderers
- Agentic Loop - Multi-step execution