From a329a949dfcd24330a322fc8a0cd1824c350fe5b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 28 Oct 2025 18:34:12 +0000 Subject: [PATCH 1/3] Add client-side Web SDK tools guide Explain handling tool-calls in-browser without server URL, clarify that client tools cannot return results to the model, and show how to inject context via addMessage. Includes complete React example and next steps to server-based tools. --- fern/tools/client-side-websdk.mdx | 296 ++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 fern/tools/client-side-websdk.mdx diff --git a/fern/tools/client-side-websdk.mdx b/fern/tools/client-side-websdk.mdx new file mode 100644 index 000000000..253f6901b --- /dev/null +++ b/fern/tools/client-side-websdk.mdx @@ -0,0 +1,296 @@ +--- +title: Client-side Tools (Web SDK) +subtitle: Handle tool-calls in the browser without a server URL +slug: tools/client-side-websdk +--- + +## Overview + +Use the Web SDK to handle tool-calls entirely on the client. This lets your assistant trigger UI-side effects (like showing notifications or changing state) directly in the browser. + +**In this guide, you'll learn to:** +- Define a client-side tool with the Web SDK +- Receive and handle `tool-calls` events on the client +- Inject extra context during a call with `addMessage` + + +Client-side tools cannot send a tool "result" back to the model. If the model must use the output of a tool to continue reasoning, implement a server-based tool instead. See: Server-based Custom Tools. + + + +To make a tool client-side, simply do not provide a server URL. The tool specification is delivered to the browser, and the Web SDK emits tool-calls messages that your frontend can handle. + + +## Quickstart + +1. Install the Web SDK: + +```bash +npm install @vapi-ai/web +``` + +2. Start a call with your tool defined in the model.tools array and subscribe to clientMessages: ['tool-calls']. +3. Listen for message.type === 'tool-calls' and perform the desired UI update. No response is sent back to the model. +4. (Optional) Inject context mid-call using vapi.addMessage(...). + +## Complete example (React + Web SDK) + +```tsx +import Vapi from '@vapi-ai/web'; +import { useCallback, useState } from 'react'; + +const vapi = new Vapi('26c8fc62-b329-459b-b290-d6d43651ae10'); + +function App() { + const [notification, setNotification] = useState(null); + + const handleUIUpdate = useCallback((message?: string) => { + setNotification(message || 'UI Update Triggered!'); + setTimeout(() => setNotification(null), 3000); + }, []); + + // 1) Listen for client tool-calls and update the UI + vapi.on('message', (message) => { + console.log('Message:', message); + + if (message.type === 'tool-calls') { + const toolCalls = message.toolCallList; + + toolCalls.forEach((toolCall) => { + const functionName = toolCall.function?.name; + let parameters: Record = {}; + + try { + const args = toolCall.function?.arguments; + if (typeof args === 'string') { + parameters = JSON.parse(args || '{}'); + } else if (typeof args === 'object' && args !== null) { + parameters = args as Record; + } else { + parameters = {}; + } + } catch (err) { + console.error('Failed to parse toolCall arguments:', err); + return; + } + + if (functionName === 'updateUI') { + handleUIUpdate((parameters as any).message); + } + }); + } + }); + + // 2) Start the call with a client-side tool (no server URL) + const startCall = useCallback(() => { + vapi.start({ + model: { + provider: 'openai', + model: 'gpt-4.1', + messages: [ + { + role: 'system', + content: + "You are an attentive assistant who can interact with the application's user interface by calling available tools. Whenever the user asks to update, refresh, change, or otherwise modify the UI, or hints that some UI update should occur, always use the 'updateUI' tool call with the requested action and relevant data. Use tool calls proactively if you determine that a UI update would be helpful.", + }, + ], + tools: [ + { + type: 'function', + async: true, + function: { + name: 'updateUI', + description: + 'Call this function to initiate any UI update whenever the user requests or implies they want the user interface to change (for example: show a message, highlight something, trigger an animation, etc). Provide an \'action\' describing the update and an optional \'data\' object with specifics.', + parameters: { + type: 'object', + properties: { + message: { + description: + 'Feel free to start with any brief introduction message in 10 words.', + type: 'string', + default: '', + }, + }, + required: ['message'], + }, + }, + messages: [ + { + type: 'request-start', + content: 'Updating UI...', + blocking: false, + }, + ], + }, + ], + }, + voice: { provider: 'vapi', voiceId: 'Elliot' }, + transcriber: { provider: 'deepgram', model: 'nova-2', language: 'en' }, + name: 'Alex - Test', + firstMessage: 'Hello.', + voicemailMessage: "Please call back when you're available.", + endCallMessage: 'Goodbye.', + clientMessages: ['tool-calls'], // subscribe to client-side tool calls + }); + }, []); + + const stopCall = useCallback(() => { + vapi.stop(); + }, []); + + return ( +
+ {notification && ( +
+ {notification} +
+ )} + +
+

+ Vapi Client Tool Calls +

+ +

+ Start a call and ask the assistant to trigger UI updates +

+ +
+ + + +
+
+
+ ); +} + +export default App; +``` + +## Inject data during the call + +Use addMessage to provide extra context mid-call. This does not return results for a tool; it adds messages the model can see. + +```ts +// Inject system-level context +vapi.addMessage({ + role: 'system', + content: 'Context: userId=123, plan=premium, theme=dark', +}); + +// Inject a user message +vapi.addMessage({ + role: 'user', + content: 'FYI: I switched to the settings tab.', +}); +``` + + +If you need the model to consume tool outputs (e.g., fetch data and continue reasoning with it), implement a server-based tool. See Custom Tools. + + +## Key points + +- **Client-only execution**: Omit the server URL to run tools on the client. +- **One-way side effects**: Client tools do not send results back to the model. +- **Subscribe to events**: Use clientMessages: ['tool-calls'] and handle message.type === 'tool-calls'. +- **Add context**: Use vapi.addMessage to inject data mid-call. + +## Next steps + +- **Server-based tools**: Learn how to return results back to the model with Custom Tools. +- **API reference**: See Tools API for full configuration options. From ce91c88fe65a58986cfef9b26e5ba7b3324a20bc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 28 Oct 2025 18:39:39 +0000 Subject: [PATCH 2/3] chore: replace hardcoded key with placeholder in client-side Web SDK doc Use to avoid leaking credentials in example. --- fern/tools/client-side-websdk.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fern/tools/client-side-websdk.mdx b/fern/tools/client-side-websdk.mdx index 253f6901b..cca52c8e5 100644 --- a/fern/tools/client-side-websdk.mdx +++ b/fern/tools/client-side-websdk.mdx @@ -39,7 +39,7 @@ npm install @vapi-ai/web import Vapi from '@vapi-ai/web'; import { useCallback, useState } from 'react'; -const vapi = new Vapi('26c8fc62-b329-459b-b290-d6d43651ae10'); +const vapi = new Vapi(''); function App() { const [notification, setNotification] = useState(null); From 9d737780bdae2ec1a9b595954e3a709683d4a357 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 28 Oct 2025 18:41:10 +0000 Subject: [PATCH 3/3] chore(nav): add Client-side tools (Web SDK) to Tools sidebar Expose new page at /tools/client-side-websdk under Tools section. --- fern/docs.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fern/docs.yml b/fern/docs.yml index ba3d04943..0a4f8a9a7 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -185,6 +185,9 @@ navigation: - page: Custom tools path: tools/custom-tools.mdx icon: fa-light fa-screwdriver-wrench + - page: Client-side tools (Web SDK) + path: tools/client-side-websdk.mdx + icon: fa-light fa-browser - page: Tool rejection plan path: tools/tool-rejection-plan.mdx icon: fa-light fa-shield-xmark