@speak2web/ain-aic-sdk
v1.0.0
Published
Headless JS/TS SDK for the speak2web AI Navigator + AI Concierge bridge (/wp-json/s2w/v1). Core (zero-React) client.
Readme
@speak2web/ain-aic-sdk
Headless JS/TS SDK for the speak2web AI Navigator + AI Concierge bridge — the
typed client customer-site engineers use to embed AIN/AIC chat in a Next.js / React
/ vanilla-JS app, talking to the public /wp-json/s2w/v1/* REST surface.
This package is the core (zero-React) client (SPEAK-170). The React layer
(@speak2web/ain-aic-sdk/react — provider, hooks, drop-in component) lands in
SPEAK-65 on top of this core.
Install
npm install @speak2web/ain-aic-sdkQuick start
import { createClient } from '@speak2web/ain-aic-sdk';
const client = createClient({
apiBase: 'https://customer.com/wp-json/s2w/v1',
tokenEndpoint: 'https://customer.com/wp-json/s2w/v1/auth/jwt',
siteId: 'pub_abc123',
tokenStorage: 'localStorage', // 'memory' (default) | 'sessionStorage' | custom TokenStore
});
// Chat — async iterator of typed chunks
for await (const chunk of client.chat({ message: 'Where is my order?' })) {
switch (chunk.type) {
case 'token': process.stdout.write(chunk.text); break;
case 'tool_call': console.log('tool:', chunk.name); break;
case 'tool_result': console.log('tool done:', chunk.name, chunk.ok); break; // {name, ok} only
case 'nav': console.log('suggested page:', chunk.url, chunk.confidence); break;
case 'error': console.error(chunk.code, chunk.message); break;
case 'done': console.log('\n[session]', chunk.sessionId); break;
}
}
// Context (no LLM call)
const { matches } = await client.getContext({ message: 'pricing', limit: 3 });
// Escalation
const esc = await client.escalate({ sessionId: 's_abc', email: '[email protected]', name: 'You' });
// Voice (Pro-only on the backend; falls back to the browser speech APIs otherwise)
const { text } = await client.voice.transcribe(audioBlob, { language: 'en' });
const speech = await client.voice.speak('Hello there');
// Session continuity across renderers
const { messages } = await client.session.bridge('s_abc');React (@speak2web/ain-aic-sdk/react)
Opinionated provider + hook + drop-in component on top of the core client. react /
react-dom are optional peers.
'use client';
import { createClient } from '@speak2web/ain-aic-sdk';
import { AICChatProvider, AICChat, useAICChat } from '@speak2web/ain-aic-sdk/react';
const client = createClient({ apiBase, tokenEndpoint, siteId });
// Drop-in component
<AICChatProvider client={client} sessionId={restoredId /* auto-bridges history */}>
<AICChat
greeting="Hi! How can I help?"
placeholder="Ask me anything"
enableEscalation /* adds an opt-in "Talk to a human" lead-capture form */
/>
</AICChatProvider>
// …or build your own UI with the hook
function MyChat() {
const { messages, send, isStreaming, escalate } = useAICChat();
// render messages, call send(text) / escalate({ email })
}<AICChatProvider sessionId>auto-bridges the prior session on mount (cross-renderer continuity).<AICChat>ships no styles by default — theme via thes2w-aic-chat*classNames / CSS custom properties. For a presentable widget out of the box, opt into the bundled stylesheet and a preset:import '@speak2web/ain-aic-sdk/react/aic-chat.css'; // opt-in default styling <AICChat theme="dark" /> // 'light' (default) | 'dark', or override the --s2w-aic-* CSS varsEvery rule in that stylesheet is
:where()-wrapped (zero specificity), so your owns2w-aic-chat*/ CSS-var overrides always win, and not importing it leaves the component fully unstyled.<AICChat enableEscalation>adds a lead-capture flow (off by default): a "Talk to a human" button (enabled once a session exists) reveals an email/name form that callsescalate(), then shows a confirmation. Customize viaescalateLabel,escalateSubmitLabel,escalateDoneMessage. Thes2w-aic-chat__escalate*classNames theme it. Advanced consumers can driveescalate()fromuseAICChat()directly instead.onEscalation/onErrorcallbacks live on the provider.
Embed (no build step — Web Component)
For a non-React host (plain HTML, Astro, SvelteKit, …) or a non-technical site
owner, drop the chat in with a single <script> + a custom element — no bundler,
no React on the page. The script registers <s2w-aic-chat>, which runs the core
client inside a Shadow DOM, so its styles and scripts stay isolated from the
host page:
<script src="https://unpkg.com/@speak2web/ain-aic-sdk"></script>
<s2w-aic-chat
api-base="https://customer.com/wp-json/s2w/v1"
token-endpoint="https://customer.com/wp-json/s2w/v1/auth/jwt"
site-id="pub_abc123"
greeting="Hi! How can I help?"
theme="dark"></s2w-aic-chat>Attributes: api-base, token-endpoint, site-id (required); greeting,
placeholder, theme (light | dark) optional. The element auto-mints its
token and streams replies. Self-host dist/embed.js instead of the CDN if you
prefer. (Remember to allowlist the page's origin in AI-Gateway → Headless / JWT.)
Auth
The client mints a short-lived JWT from tokenEndpoint, caches it in the configured
tokenStorage, refreshes it in place before expiry, and re-mints once on a 401.
All calls carry Authorization: Bearer <jwt> and are CORS-gated server-side. See the
headless-bridge ADRs in s2w-docs/specs/headless-bridge/.
Errors
Non-OK responses throw a BridgeError with a stable code, the HTTP status, and
retryAfter (on 429/503). error.retryable is true for 5xx and 429.
Build / test
npm run typecheck # tsc --noEmit
npm run test # vitest
npm run build # tsup -> dual ESM + CJS + .d.ts in dist/