# Okidoki Widget SDK > Okidoki is an AI chat widget for websites. Embed with one script tag, register custom tools to let the AI interact with your page, and control the widget programmatically. Important notes for LLM agents implementing features: - The widget loads via a script tag with `data-app-id` attribute - All methods are available on `window.OkidokiWidget` - Custom tools let the AI call your JavaScript functions - Tools run sequentially by default; use `parallel: true` for read-only tools - Use `reinitialize()` to switch apps/bots at runtime - Use `ask({ searchKnowledgeBase: true })` to query crawled website/documents ## Installation Add this script tag before ``: ```html ``` The widget auto-initializes. Get your App ID from the Okidoki dashboard. ## Custom Tools Register tools that the AI can call to interact with your page: ```javascript OkidokiWidget.registerTools([{ name: 'add_to_cart', description: 'Add a product to the shopping cart', input: { productId: 'The product ID to add', quantity: 'Number of items (optional, default: 1)' }, handler: async ({ productId, quantity = 1 }) => { await myStore.addToCart(productId, quantity); return { success: true, cartCount: myStore.getCartCount() }; } }]); ``` ### Input Schema Options ```javascript input: { // Simple string field (required) name: 'User name', // With type and options count: { description: 'Number of items', type: 'number', optional: true, default: 1 }, // Enum constraint priority: { description: 'Priority level', type: 'string', enum: ['low', 'medium', 'high'] } } ``` ## Execution Control Configure how tools execute: ```javascript OkidokiWidget.registerTools([{ name: 'translate_document', description: 'Translate the entire document', // Execution options timeout: 300000, // Max 5 min (default: 60s, max: 600000) lockGroup: 'doc_write', // Tools in same group can't run concurrently parallel: true, // Can run alongside any tool (for read-only) input: { targetLanguage: 'Target language code' }, handler: async ({ targetLanguage }) => { OkidokiWidget.setToolNotification('Translating...'); await translateDoc(targetLanguage); OkidokiWidget.setToolNotification(null); return { success: true }; } }]); ``` **Concurrency rules:** - Default: Tools run sequentially (one at a time) - `lockGroup`: Only blocks tools in the same group - `parallel: true`: Never blocks, never blocked (use for read-only) ## Widget API ### Initialization ```typescript // Switch to different app at runtime (multi-tenant, environment switching) OkidokiWidget.reinitialize(publicKey: string, apiUrl?: string): void // Example: Switch to different bot based on user context OkidokiWidget.reinitialize('OTHER_APP_PUBLIC_KEY'); ``` ### Language ```typescript OkidokiWidget.setLanguage(lang: 'en' | 'es'): void OkidokiWidget.getLanguage(): 'en' | 'es' ``` ### Actions ```typescript // Send form data to app owner's inbox OkidokiWidget.sendEmail(fields: Record): boolean // Open scheduling modal OkidokiWidget.scheduleMeeting(eventType?: string): void // Show/hide progress notification in chat OkidokiWidget.setToolNotification(message: string | null): void ``` ### Widget Control ```typescript // Get current widget state (null if not initialized) OkidokiWidget.getMode(): WidgetModeInfo | null interface WidgetModeInfo { active: 'text' | 'voice' | 'video'; // Current mode configured: string; // Backend config (e.g., 'CHAT_PLUS_VOICE') available: ('text' | 'voice' | 'video')[]; // Available modes isOpen: boolean; } // Open widget (optionally in specific mode) OkidokiWidget.openChat(options?: { mode?: 'text' | 'voice' | 'video' }): WidgetResult // Close widget OkidokiWidget.closeChat(): WidgetResult // Insert text into input (optionally auto-send) OkidokiWidget.insertMessage(text: string, options?: { send?: boolean }): WidgetResult // Clear input field OkidokiWidget.clearInput(): WidgetResult interface WidgetResult { success: boolean; error?: string; // Error message if success is false } ``` **Notes:** - `insertMessage` returns error in voice-only or video-only modes (no text input) - `openChat({ mode })` returns error if requested mode is not available - Use `getMode()` to check available modes before calling other methods ### Tool Management ```typescript OkidokiWidget.registerTools(tools: ToolDefinition[]): void OkidokiWidget.unregisterTools(names?: string[]): void // undefined = all OkidokiWidget.getRegisteredTools(): NormalizedTool[] OkidokiWidget.invokeTool(name: string, args?: Record): Promise ``` **Note:** `invokeTool` allows calling registered tools programmatically from your code, without going through the chat. Useful for reusing tool logic in buttons/UI elements. ## Programmatic AI (ask API) Call the LLM directly from your code with structured outputs: ```javascript const { array, object, string, number } = OkidokiWidget.helpers; const result = await OkidokiWidget.ask({ prompt: 'Find dates that need updating', context: documentContent, // Up to ~400k characters temperature: 0.2, // 0-1, lower = more deterministic output: { updates: array( object({ original: string('Original date text'), suggested: string('Updated date'), reason: string('Why update needed') }), 'List of date updates' ) } }); if (result.success) { // result.result is typed based on output schema for (const update of result.result.updates) { console.log(update.original, '->', update.suggested); } } ``` ### Knowledge Base Search (RAG) Query the app's crawled website and uploaded documents before answering: ```javascript const answer = await OkidokiWidget.ask({ prompt: 'What are your pricing plans?', searchKnowledgeBase: true // Enable RAG }); // answer.result = "Our plans start at $29/month..." // answer.sources = [{ title, url, type, relevance, snippet }] // Advanced options const result = await OkidokiWidget.ask({ prompt: 'Does this align with our policies?', context: documentText, searchKnowledgeBase: true, searchQuery: 'standard terms policies', // Custom search query searchSource: 'documents', // 'website' | 'documents' | 'all' searchLimit: 5 // Max results (1-5, default: 3) }); // Use relevance scores for confidence result.sources?.forEach(s => { const conf = s.relevance >= 0.85 ? 'high' : s.relevance >= 0.70 ? 'medium' : 'low'; console.log(`${s.title} (${conf} confidence)`); }); ``` **RAG Options:** - `searchKnowledgeBase`: boolean (default: false) - Enable knowledge base search - `searchQuery`: string (default: prompt) - Custom search query - `searchSource`: 'website' | 'documents' | 'all' (default: 'all') - `searchLimit`: number 1-5 (default: 3) - Max results to retrieve **Relevance Scores:** 0.85+ = high confidence, 0.70-0.85 = good, 0.55-0.70 = partial, <0.55 = low ### Schema Helpers ```javascript const { string, number, boolean, array, object, enum_ } = OkidokiWidget.helpers; // Simple types string('Description') number('Description') boolean('Description') // Enum enum_(['low', 'medium', 'high'], 'Priority level') // Nested object object({ name: string('Name'), age: number('Age') }, 'Person object') // Array of objects array( object({ id: string('ID'), price: number('Price in cents') }), 'List of items' ) ``` ### ask() Response ```typescript interface AskResponse { success: boolean; result?: T; // Parsed JSON if output schema provided messagesConsumed: number; error?: string; sources?: AskSource[]; // Only when searchKnowledgeBase=true } interface AskSource { title: string; // Document/page title url?: string; // URL if from website type: 'website' | 'document'; relevance: number; // 0-1 similarity score snippet: string; // ~200 char preview } ``` ## Optional - [Full Documentation](https://okidoki.chat/developers): Complete developer guide with examples - [Dashboard](https://okidoki.chat/admin): Get your App ID and configure your widget - [Landing Page](https://okidoki.chat): Product overview and pricing