Lend a tool
A tool is a capability beyond pressing an action: reading or changing what the page shows, or going elsewhere. It runs in the browser. Lend one with useAssistedTool(name, { description, parameters, execute }).
Lend it on a page
Section titled “Lend it on a page”Call useAssistedTool in a component rendered under the AssistedPage. The tool belongs to the page: the page’s note lists it, and it leaves with the page. From examples/app/app/orders/page.tsx:
useAssistedTool("filter_orders", { description: "Show only the orders with one status, or all of them.", parameters: { type: "object", properties: { status: { type: "string", enum: filters } }, required: ["status"], }, execute: ({ status }: { status: Filter }) => { setFilter(status); return `Showing ${status === "all" ? "all orders" : `the orders that are ${status}`}.`; },});Split the page in two
Section titled “Split the page in two”The tool’s hook must render under the AssistedPage, while content needs the same state. So a page with tools is two components: the state and the AssistedPage above, the tools below. From examples/app/app/orders/page.tsx:
export default function Orders() { const shop = useShop(); const [filter, setFilter] = useState<Filter>("all"); const money = moneyOf(shop.settings.currency); const rows = shop.orders .filter((order) => filter === "all" || order.status === filter) .map((order) => rowOf(order, money)); return ( <AssistedPage name="Orders" instructions="Every order of the shop, as filtered by status with filter_orders. Ship or cancel an order that is waiting to ship by its id. New order opens a dialog that introduces itself. Each order has a page of its own with its note and refund." content={() => ({ filter, orders: rows })} > <OrdersTable filter={filter} setFilter={setFilter} rows={rows} /> </AssistedPage> );}OrdersTable lends filter_orders, shown above.
Lend it everywhere
Section titled “Lend it everywhere”Call useAssistedTool outside any AssistedPage, under AssistedProvider. The tool is global: it works from every page and no note lists it. From examples/app/app/shell.tsx:
useAssistedTool("get_shop_summary", { description: "How many orders and customers the shop has. Works from any page.", parameters: { type: "object", properties: {} }, execute: () => ({ orders: shop.orders.length, customers: shop.customers.length }),});Describe the parameters
Section titled “Describe the parameters”parameters takes a JSON Schema object or a Standard Schema such as a zod schema. With JSON Schema, type the arguments of execute yourself, as above. With zod, they are inferred:
const refund = z.object({ id: z.string().describe("The order's id.") });
export function RefundTool({ refundOrder }: { refundOrder: (id: string) => Promise<void> }) { useAssistedTool("refund_order", { description: "Refund an order.", parameters: refund, execute: async ({ id }) => { await refundOrder(id); return `Refunded ${id}.`; }, }); return null;}Define the schema at module scope.
The tool is lent again when its description or its parameters’ JSON Schema changes. execute always runs the latest closure.
Let failures speak
Section titled “Let failures speak”Throw from execute when the tool cannot do its job. The model gets { error: "<name> failed: <message>" } and the turn goes on. Arguments the schema rejects answer with an { error } too.
Show a card for the call
Section titled “Show a card for the call”render takes a tool UI component for the tool’s calls in the thread. Define it at module scope, so the same component is passed on every render.
To ask the user before the tool runs, see Make an action consequential.