Client tools allow your AI persona to trigger events in your client application. When the LLM invokes a client tool, the Anam SDK emits an event that your application can listen for, enabling the persona to:
Navigate to specific pages or sections
Open modals, dialogs, or overlays
Update UI state based on conversation
Trigger animations or visual effects
Control media playback
Submit forms or initiate actions
This creates a seamless voice-driven or chat-driven user experience where the AI can guide users through your application.
Beta Feature: Tool calling is currently in beta. You may encounter some issues as we continue to improve the feature. Please report any feedback or issues to help us make it better.
If awaitResult is true, the return value from your handler is sent back to the LLM. The persona uses the result to continue naturally: “I’ve opened the pricing page for you!”If awaitResult is false, the tool call is fire-and-forget — the LLM continues without waiting for a result.
The user experiences a seamless interaction between voice/chat and UI.
When true, the return value from your onStart handler is sent back to the LLM as the tool result, allowing the persona to incorporate it into its response. When false (default), the tool call is fire-and-forget.
How long the engine waits for the client to return a tool result before timing out. Only applies when awaitResult is true. Range: 1–600 seconds. Default: 10 seconds.
Register handlers for specific tools by name. This will automatically emit completed or failed events when the handler completes. For client tools with awaitResult set to true, the return value from onStart is sent back to the LLM as the tool result:
import { AnamClient } from "@anam-ai/js-sdk";const client = new AnamClient({ sessionToken: "YOUR_SESSION_TOKEN",});// Register handlers for each toolclient.registerToolCallHandler("navigate_to_page", { onStart: async (payload) => { window.location.href = `/${payload.arguments.page}`; return `Navigated to ${payload.arguments.page}`; }, onComplete: async (payload) => { console.log(`Navigation completed in ${payload.executionTime}ms`); }, onFail: async (payload) => { console.error(`Navigation failed: ${payload.errorMessage}`); },});client.registerToolCallHandler("open_modal", { onStart: async (payload) => { openModal(payload.arguments.modalType, payload.arguments.data); return `Opened ${payload.arguments.modalType} modal`; },});// Start the sessionawait client.streamToVideoElement("video-element-id");
Each call to registerToolCallHandler returns a cancel function you can use for cleanup:
const cancelNavHandler = client.registerToolCallHandler("navigate_to_page", { onStart: async (payload) => { // handle navigation },});// Later, when the handler is no longer needed:cancelNavHandler();
The description helps the LLM decide when to use the tool:
// ✅ Good - Specific about when to use{ name: 'open_checkout', description: 'Open the checkout page when user explicitly says they want to purchase, buy, checkout, or complete their order'}// ❌ Bad - Too vague{ name: 'open_checkout', description: 'Opens checkout'}
Validate arguments in your handler. Errors thrown in onStart are automatically caught and routed to the onFail callback. If awaitResult is true, the error message is also sent back to the LLM so it can respond appropriately (e.g., “Sorry, I couldn’t find that product”):
client.registerToolCallHandler("show_product", { onStart: async (payload) => { const { productId } = payload.arguments; if (!productId) { throw new Error("Missing productId"); } if (!productExists(productId)) { throw new Error("Product not found"); } router.push(`/products/${productId}`); return `Showing product ${productId}`; }, onFail: async (payload) => { console.error("Tool error:", payload.errorMessage); showNotification("Something went wrong"); },});