vani / docs
npm i @shvm/vani-clientimport { Vani } from "@shvm/vani-client/ui";
export function App() {
return <Vani defaultMode="full" />;
}import { useVoiceSession } from "@shvm/vani-client/headless";
export function VoiceWidget() {
const voice = useVoiceSession();
return (
<div>
<div>Status: {voice.status}</div>
<button onClick={voice.connect}>Connect</button>
<button onClick={voice.cancel}>Cancel</button>
</div>
);
}useVoiceSession and <Vani /> default to https://shvm.in. Override with serverUrl if you’re running your own server.
Vani is designed so the “expensive part” is the model usage, not your control plane. Running it on Cloudflare tends to mean you pay for Workers + Durable Objects + Workers AI usage. Exact cost depends on model choice, audio volume, and concurrency.
These aren’t 1:1 competitors — the right pick depends on whether you need a managed voice platform, a media SFU, or a small self-hostable voice-agent runtime.
npm cinpx wrangler loginnpm run deployMake sure Workers AI is enabled on your Cloudflare account. The worker binds it as env.AI via "ai": { "binding": "AI" }.
The voice websocket route is /ws/:sessionId and is backed by a Durable Object named VOICE_SESSIONS configured in wrangler.jsonc.
// wrangler.jsonc (excerpt)
{
"ai": { "binding": "AI" },
"durable_objects": {
"bindings": [
{ "name": "VOICE_SESSIONS", "class_name": "VoiceSessionDO" }
]
},
"migrations": [
{ "tag": "v2", "new_sqlite_classes": ["VoiceSessionDO"] }
]
}The worker routes websocket upgrades in src/server.ts and forwards them to the per-session DO.
// src/server.ts (excerpt)
// Voice session websocket route
const wsMatch = url.pathname.match(/^\/ws\/([^/]+)$/);
if (wsMatch) {
const sessionId = wsMatch[1];
const id = env.VOICE_SESSIONS.idFromName(sessionId);
const stub = env.VOICE_SESSIONS.get(id);
return stub.fetch(request);
}After deploying, point your own domain at the worker (Cloudflare dashboard → Workers & Pages → your worker → Triggers → Custom Domains). Then set serverUrl in your client/UI to your domain.
If you only want the voice server portion, you can keep the same Durable Object and websocket route and integrate the client package into a separate UI app — the contract is in @shvm/vani-client/shared.