@aikaara/chat-sdk
v1.0.7
Published
Aikaara Chat SDK — embeddable chat widget and headless client
Readme
@aikaara/chat-sdk
Embeddable chat widget and headless client for the Aikaara AI agent platform.
Installation
npm install @aikaara/chat-sdkThree Entry Points
| Import Path | Use Case |
|-------------|----------|
| @aikaara/chat-sdk | Full bundle: widget + headless + mount()/unmount() |
| @aikaara/chat-sdk/headless | Headless only: no DOM dependency. For React Native, custom UI, server-side |
| @aikaara/chat-sdk/ui | Web Components only |
Quick Start — Chat Widget (CDN)
<script src="https://cdn.aikaara.com/chat-sdk/latest/aikaara-chat.iife.js"></script>
<script>
AikaaraChat.mount({
baseUrl: 'https://api.aikaara.com',
userToken: 'ut_...',
title: 'Support',
primaryColor: '#6366f1',
});
</script>Quick Start — Headless Client
import { AikaaraChatClient } from '@aikaara/chat-sdk/headless';
const client = new AikaaraChatClient({
baseUrl: 'https://api.aikaara.com',
userToken: 'ut_...',
apiKey: 'ak_...',
});
await client.connect();
client.on('message:received', (msg) => console.log(msg.content));
client.on('stream:update', ({ content }) => updateUI(content));
await client.sendMessage('Hello!');Quick Start — Headless with JWT Auth (Dashboard / Internal Apps)
import { AikaaraChatClient } from '@aikaara/chat-sdk/headless';
const client = new AikaaraChatClient({
baseUrl: 'https://api.aikaara.com',
userToken: 'ut_...',
authToken: jwtToken, // Bearer token for REST calls
channel: 'sidekick',
});
await client.connect();App Context — Connecting the Agent to Your App
The SDK doesn't know about your app's routes, pages, or entities. You provide that context at runtime via setContext(), and the agent uses it to navigate, edit forms, and stay aware of what the user is doing.
Why This Matters
Without context, the agent doesn't know:
- What page the user is on (
/products/42?/orders?) - What routes exist in your app (so it can navigate)
- What entity the user is viewing (so it can edit the right form)
Providing Context on Route Changes
Call setContext() whenever the user navigates in your app:
import { AikaaraChatClient } from '@aikaara/chat-sdk/headless';
const client = new AikaaraChatClient({
baseUrl: 'https://api.aikaara.com',
userToken: 'ut_...',
authToken: jwt,
channel: 'sidekick',
});
await client.connect();
// Call on every route change (e.g., in a React useEffect, Vue watch, etc.)
client.setContext({
currentPage: '/products/42',
entityType: 'product',
entityId: '42',
availableRoutes: {
'Product list': '/products',
'Order list': '/orders',
'Customer list': '/customers',
'Settings': '/settings',
'Analytics': '/analytics',
},
custom: {
formFields: ['name', 'price', 'description', 'category'],
userName: 'Jane',
},
});What Happens Under the Hood
setContext()sends a PATCH to the backend, storing the context in conversation metadata- The backend interpolates
{{current_page}},{{available_routes}}, and{{custom_context}}into the agent's system prompt - The agent now knows your app's routes and can call
navigate_towith them - The agent knows the current entity and can call
edit_current_entityfor it
AppContext Interface
interface AppContext {
/** Current page/route path in your app (e.g., '/products/42') */
currentPage: string;
/** Entity type on the current page (e.g., 'product', 'order') */
entityType?: string;
/** Entity ID on the current page */
entityId?: string | number;
/** Project/workspace ID if applicable */
projectId?: string | number;
/** Routes the agent can navigate to — map of label to path */
availableRoutes?: Record<string, string>;
/** Any additional context for the agent (form fields, user info, etc.) */
custom?: Record<string, unknown>;
}Handling Navigation
The agent doesn't control your router — it returns a path, and you handle it:
client.on('action:navigate', ({ navigate_to }) => {
// Your router (React Router, Vue Router, Next.js, etc.)
router.push(navigate_to);
});The navigate_to value will be one of the paths from your availableRoutes, or a path the agent constructs from context (e.g., /products/42).
Framework Examples
React (with React Router)
import { useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router';
function useSidekickContext(client: AikaaraChatClient) {
const location = useLocation();
const navigate = useNavigate();
// Update context on route change
useEffect(() => {
const match = location.pathname.match(/^\/(products|orders|customers)\/(\d+)/);
client.setContext({
currentPage: location.pathname,
entityType: match?.[1],
entityId: match?.[2],
availableRoutes: {
'Products': '/products',
'Orders': '/orders',
'Customers': '/customers',
'Analytics': '/analytics',
},
});
}, [location.pathname]);
// Handle navigation from agent
useEffect(() => {
const off = client.on('action:navigate', ({ navigate_to }) => {
navigate(navigate_to);
});
return off;
}, [navigate]);
}Vue 3 (with Vue Router)
import { watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
function useSidekickContext(client: AikaaraChatClient) {
const route = useRoute();
const router = useRouter();
watch(() => route.fullPath, (path) => {
client.setContext({
currentPage: path,
entityType: route.params.type as string,
entityId: route.params.id as string,
availableRoutes: { Products: '/products', Orders: '/orders' },
});
}, { immediate: true });
client.on('action:navigate', ({ navigate_to }) => {
router.push(navigate_to);
});
}Vanilla JS / Any Framework
// On route change (however your app detects it)
window.addEventListener('popstate', () => {
client.setContext({ currentPage: window.location.pathname });
});
// On navigation action from agent
client.on('action:navigate', ({ navigate_to }) => {
window.history.pushState(null, '', navigate_to);
// Trigger your app's route handler
});FormBridge — AI-Driven Form Editing
The FormBridge lets AI agents visually edit forms in your application. When the agent calls tools like edit_current_entity, the bridge pushes field updates to your registered form — the user sees changes live and can confirm or reject them.
How It Works
- Your agent has tools like
edit_current_entity,save_current_entity, andtest_tool_by_id - When the agent calls these tools, the result flows through the WebSocket as a
tool_execution_endevent - The SDK's
AikaaraChatClientparses the result and emits typed action events FormBridgelistens for these events and pushes updates to your registered form
Integration Steps
1. Import FormBridge from the SDK
import { AikaaraChatClient, FormBridge } from '@aikaara/chat-sdk/headless';2. Pass it your AikaaraChatClient
const client = new AikaaraChatClient({
baseUrl: 'https://api.aikaara.com',
userToken: 'ut_...',
authToken: jwtToken,
channel: 'sidekick',
});
const bridge = new FormBridge(client);
await client.connect();3. Register your forms with registerForm()
Call this when your form component mounts. Only one form can be active at a time (the current page).
bridge.registerForm({
entityType: 'product', // matches what the agent sends
entityId: 42, // the entity being edited
onFieldUpdate: (fields) => {
// Update your form UI — fields is FieldUpdate[]
for (const { field, value } of fields) {
formState[field] = value;
rerenderField(field); // your UI framework's update mechanism
}
},
onSave: async () => {
// Called when the agent triggers save_current_entity
await api.updateProduct(42, formState);
},
onTest: async (params) => {
// Optional: called when the agent triggers test_tool_by_id
await api.testProduct(42, params);
},
getCurrentValues: () => ({ ...formState }),
});
// Unregister when the form unmounts
bridge.unregisterForm('product', 42);4. The bridge automatically handles tool results
When the agent calls these backend tools, the bridge takes action automatically:
| Agent Tool | Bridge Action |
|-----------|---------------|
| edit_current_entity | Calls onFieldUpdate() with the field changes |
| save_current_entity | Calls onSave() on the registered form |
| test_tool_by_id | Calls onTest() with parameters |
If no form is registered when an edit_current_entity result arrives, the bridge queues it. When a matching form later registers, the queued edits are applied automatically.
5. Build your own confirmation UI using bridge events
bridge.on('edit:applied', ({ entityType, entityId, fields }) => {
// Show a confirmation banner: "AI edited: name, description"
showBanner({
message: `AI edited: ${fields.map(f => f.field).join(', ')}`,
onConfirm: () => dismissBanner(),
onReject: () => {
// Revert fields using previousValue
revertFields(fields);
dismissBanner();
},
onSave: async () => {
await bridge.requestSave();
dismissBanner();
},
});
});
bridge.on('edit:pending', ({ entityType, entityId, fields }) => {
// Edits queued — no matching form registered yet
// Navigate the user to the right page, then the form will pick them up
});
bridge.on('save:success', () => toast.success('Saved by AI'));
bridge.on('save:error', ({ error }) => toast.error(`Save failed: ${error}`));
bridge.on('test:triggered', ({ toolId, parameters }) => {
// Show test UI if needed
});Lower-Level: Action Events on AikaaraChatClient
If you don't need FormBridge and want to handle actions yourself:
client.on('action:edit_entity', (action) => {
// action: { entity_type, entity_id, fields: FieldUpdate[] }
});
client.on('action:save_entity', () => {
// Trigger save
});
client.on('action:navigate', ({ navigate_to }) => {
// Route change: e.g., router.push(navigate_to)
});
client.on('action:test_tool', ({ tool_id, parameters }) => {
// Run a tool test
});
// Raw tool lifecycle events
client.on('tool:start', ({ toolName, args }) => showSpinner(toolName));
client.on('tool:end', ({ toolName, result, isError }) => hideSpinner(toolName));React Hook Example
import { useEffect, useRef } from 'react';
import type { FormBridge, FieldUpdate } from '@aikaara/chat-sdk/headless';
function useFormBridge(
bridge: FormBridge,
options: {
entityType: string;
entityId: string | number | undefined;
onFieldUpdate: (fields: FieldUpdate[]) => void;
onSave: () => Promise<void>;
onTest?: (params?: Record<string, unknown>) => Promise<void>;
getCurrentValues: () => Record<string, unknown>;
}
) {
const refs = useRef(options);
refs.current = options;
useEffect(() => {
if (!options.entityId) return;
bridge.registerForm({
entityType: options.entityType,
entityId: options.entityId,
onFieldUpdate: (f) => refs.current.onFieldUpdate(f),
onSave: () => refs.current.onSave(),
onTest: refs.current.onTest ? (p) => refs.current.onTest!(p) : undefined,
getCurrentValues: () => refs.current.getCurrentValues(),
});
return () => bridge.unregisterForm(options.entityType, options.entityId!);
}, [bridge, options.entityType, options.entityId]);
}Agent Events
Events broadcast over ActionCable from the Aikaara Rails backend:
| Event | Description |
|-------|-------------|
| status | Processing state (processing/completed) |
| message_start/update/end | Streaming response lifecycle |
| message_queued | User message acknowledged |
| tool_execution_start/update/end | Tool call lifecycle |
| agent_start/end | Agent session lifecycle |
| turn_start/end | Conversation turn lifecycle |
| auto_retry_start/end | Auto-retry on failure |
| cancelled | Processing cancelled |
TypeScript Types
All types are exported from @aikaara/chat-sdk/headless:
import type {
// Connection
ConnectionConfig, ConnectionState, ChatClientConfig,
// Messages
Message, ToolCall, ToolCallResult,
// Events
AgentEvent, AgentEventType, ChatEvents,
// App Context
AppContext,
// Form Bridge
FieldUpdate, FormRegistration, FormBridgeEvents,
// Actions
EditEntityAction, SaveEntityAction, TestToolAction, NavigateAction, AgentAction,
} from '@aikaara/chat-sdk/headless';License
MIT
