Tools vs Widgets
PancakeJS supports two types of capabilities: Tools and Widgets. Understanding when to use each is key to building great AI experiences.
Overview
| Aspect | Tools | Widgets |
|---|---|---|
| Purpose | Perform actions or fetch data | Interactive UI experiences |
| Output | Text, structured data, or both | React components |
| Interaction | Single request/response | Stateful, multi-step |
| Best for | Data fetching, calculations, API calls | Forms, visualizations, workflows |
Tools
Tools are functions that an AI assistant can call to perform actions or fetch data. They return text, structured data, or both.
Defining a Tool
import { z } from 'zod';
import { textResult, withData } from '@pancakeapps/server';
app.tool(
{
name: 'getWeather',
description: 'Get current weather for a location',
inputSchema: z.object({
city: z.string().describe('City name'),
units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
}),
},
async (input) => {
const weather = await fetchWeather(input.city, input.units);
return withData(
textResult(`It's ${weather.temp}° and ${weather.conditions} in ${input.city}`),
weather, // Structured data for further processing
);
},
);Tool Response Types
// Simple text response
return textResult('Operation complete');// Rich HTML content
return htmlResult('<strong>Success!</strong> Your file was uploaded.');// Text + structured data
return withData(
textResult('Found 10 items'),
{ items: [...], total: 10 }
);// Combine multiple response types
return result()
.text('Primary message')
.html('<em>Rich content</em>')
.data({ key: 'value' })
.build();Widgets
Widgets are tools that render a custom UI. They're perfect for interactive experiences, data visualization, or complex user flows.
Defining a Widget
import { renderWidget } from '@pancakeapps/server';
app.widget(
{
name: 'flightSearch',
description: 'Search and book flights',
inputSchema: z.object({
origin: z.string(),
destination: z.string(),
date: z.string(),
}),
ui: {
entry: 'src/widgets/flight-search.tsx',
csp: {
connectDomains: ['api.flights.com'], // Allow API calls
imgDomains: ['images.airline.com'], // Allow images
},
},
},
async (input) => {
// Fetch initial data
const flights = await searchFlights(input);
return renderWidget('flightSearch', {
content: [{ type: 'text', text: `Found ${flights.length} flights` }],
data: { flights, searchParams: input },
});
},
);Widget UI
Widgets are React components that use special hooks to interact with the host:
import {
UniversalAppProvider,
useToolInvocation,
useCallTool,
useWidgetState,
useHost,
} from '@pancakeapps/react';
function FlightSearchWidget() {
// Get initial data from tool invocation
const { data } = useToolInvocation();
// Persist state across renders
const [selectedFlight, setSelectedFlight] = useWidgetState('selected', null);
// Call other tools from the widget
const bookFlight = useCallTool('bookFlight');
// Access host capabilities
const host = useHost();
const handleBook = async (flight) => {
const result = await bookFlight({ flightId: flight.id });
// Handle booking result
};
return (
<div className={host.context.theme === 'dark' ? 'dark' : 'light'}>
{data.flights.map((flight) => (
<FlightCard
key={flight.id}
flight={flight}
onBook={() => handleBook(flight)}
/>
))}
</div>
);
}When to Use Each
| Use Case | Tool | Widget |
|---|---|---|
| Simple data fetch | ✅ | |
| Text-based response | ✅ | |
| Interactive UI | ✅ | |
| Data visualization | ✅ | |
| Multi-step workflow | ✅ | |
| Form input | ✅ | |
| Real-time updates | ✅ |
Hybrid Approach
Widgets can call tools, enabling powerful patterns where the widget handles UI while delegating actions to tools.
function DataExplorer() {
const { data: initialData } = useToolInvocation();
const [data, setData] = useWidgetState('data', initialData);
const fetchMore = useCallTool('fetchMoreData');
const handleLoadMore = async () => {
const more = await fetchMore({ page: data.page + 1 });
setData({
...data,
items: [...data.items, ...more.items],
page: more.page,
});
};
return (
<div>
<ItemList items={data.items} />
<button onClick={handleLoadMore}>Load More</button>
</div>
);
}This pattern:
- Initial load → Widget tool fetches data and renders UI
- User interaction → Widget calls other tools for actions
- Update UI → Widget state updates with new data
Best Practices
Tools
- Keep tools focused on a single task
- Always provide clear descriptions for AI understanding
- Use Zod schemas with
.describe()for better AI comprehension - Return structured data when it might be useful for follow-up actions
Widgets
- Wrap with
<UniversalAppProvider>at the root - Use
useWidgetStatefor any state that should persist - Check host capabilities before using advanced features
- Keep initial data fetching in the tool handler, not the widget
Next Steps
- Auto-Bridge — How platform detection works
- Capabilities — Handle feature differences
- Examples — See working implementations