AvaClone

Browser SDK

A small TypeScript library that renders the avatar into any element and speaks the realtime protocol. The widget is built on it; use it directly when you want your own interface.

Install

npm install @avaclone/sdk

Or, without a bundler, load the same ES module from the CDN:

<script type="module">
  import { AvaClone, MicCapture, CameraCapture } from "https://cdn.avaclone.ai/v1/sdk.js";
</script>

Mount

Your server creates a session with the API and hands the browser its ws_url, token and idle_loop_url. The browser mounts:

import { AvaClone, MicCapture } from "@avaclone/sdk";

const s = await fetch("/my-server/session").then((r) => r.json());   // your server called POST /api/v1/sessions

const avatar = AvaClone.mount(document.querySelector("#stage")!, {
  wsUrl: s.ws_url,
  token: s.token,
  idleLoopUrl: s.idle_loop_url,
  packed: s.packed,          // frames carry alpha (transparent background)
  lazy: true,                // dial only when the visitor begins
});

avatar.on("transcript", ({ role, text }) => console.log(role, text));
avatar.on("speaking", (on) => button.disabled = on);

const mic = new MicCapture();
await mic.start((pcm) => avatar.sendAudio(pcm));   // 24 kHz mono PCM, chunked

avatar.sendText("What are your opening hours?");   // typed questions work too

Mount options

FieldTypeMeaning
wsUrlstringwss://rt.avaclone.ai/v1/sessions/<id>, from POST /sessions.
tokenstringThe session token. Sent as a query parameter on connect; it is swapped server-side and never reaches the engine.
idleLoopUrlstringThe avatar's idle loop (MP4). Shown at once and whenever the stream is not live. Costs no minutes.
packedbooleanFrames carry their alpha in a side-by-side pack and are composited (WebGL, or the CPU without it). Optional: the SDK reads it from the idle loop and the stream, and the stream's own word wins.
lazybooleanDo not connect until the first sendAudio or sendText. Use it for public visitors.
backoffMs[min, max]Reconnect back-off in milliseconds, for a worker's own socket in development. A session from POST /sessions never redials: its token works once (see ended).
audioContextAudioContextShare one AudioContext with the rest of your page (ducking, analysers).
audioDestinationAudioNodeWhere the avatar's voice is played: a node of that AudioContext (a gain you duck, an analyser, a MediaStreamDestination your echo gate listens to). Default: the speakers.
ownerstringA stable id for this viewer so a reconnect resumes the same session.

Methods

FieldTypeMeaning
sendAudio(pcm)Int16Array | ArrayBufferMicrophone audio, 24 kHz, 16-bit mono. The relay transcribes with server-side voice detection; you do not need to detect speech yourself.
endUtterance()Optional: mark the end of what the visitor said when you run your own push-to-talk.
sendText(text)stringA typed turn. Queued until the socket is open.
interrupt()Stop the avatar mid-sentence and clear buffered audio (barge-in). Speaking into the microphone does this automatically.
sendImage(image, text?)Blob | canvas | video | img | ImageBitmap | CameraCapture | data URLShow the agent something. Without text, the visitor's next question is answered while seeing it (a newer image replaces it; forgotten after a minute). With text, a turn of its own. Resized to 1,536 px, sent as JPEG, never stored; at most ten a minute. Agent sessions with vision on; the image event says whether it was taken.
look(image, text?)same sources → Promise<{ ok, description?, tokens?, reason? }>A stream session: your own model is the brain, so the relay describes what is in view and resolves with the text for your model, for look_tokens a look (GET /organization). text is what the person said. Refusals: image.rate, image.large, image.invalid, tokens.exhausted, look.failed, look.timeout.
setCamera(cam | null)CameraCaptureHand the avatar a CameraCapture so the agent can ask for a still when the visitor refers to something in view. null turns it off.
rest()End the session on purpose (the visitor walked away). The idle loop returns.
destroy()Tear everything down and release the element.
on(event, fn)Subscribe to an event below. Returns a function that unsubscribes.

Events

FieldTypeMeaning
ready{ avatar, packed, fps, chunk_s, in_sr, model }The stream is up; the first frame is about to show.
session{ id, kind, agent }The relay accepted the session.
transcript{ role, text, final, via }What the visitor said (role user, via voice or text) and what the agent is saying (role assistant; final is false while it streams).
speakingbooleanThe avatar started or stopped speaking.
frame(seq, kind)A frame was drawn; kind is idle or speech.
stats{ chunks, late, genMsP50, bytesPerS }Playback health, about once a second.
restingThe stream ended (quiet cut-off, rest(), or a cap) and the idle loop is showing.
closed(code, reason)The socket closed. Codes: 4001 quiet cut-off, 4002 the avatar is loading (retry shortly), 4003 a plan cap, 4009 all engines busy, 4401 bad token.
ended{ code, reason, why }The session is over for good (SDK 0.1.5): a session's token works once, so the SDK stops instead of redialling. why is quiet (the quiet cut-off), busy, limit, unavailable, expired or dropped. Show a notice and a Resume button that starts a new session (POST /sessions) and mounts a new avatar; the idle loop keeps playing meanwhile. The widget does exactly this.
errorErrorSomething failed; the SDK reconnects where it can.
image{ ok, id?, reason?, description?, tokens? }The relay's answer to sendImage or look: taken, or refused with vision.off, image.rate, image.large, image.invalid, tokens.exhausted or look.failed. A look carries the description and the tokens it drew.

MicCapture and CameraCapture

MicCapture opens the microphone with echo cancellation and noise suppression, resamples to 24 kHz and calls you with PCM chunks. CameraCapture opens the camera (user or environment), optionally previews into an element, and keeps the last three seconds of frames in memory; flip() switches cameras on phones; sharpestRecent() is the best of those frames as a JPEG data URL. Neither sends anything by itself: audio goes only through sendAudio, and a camera still goes only when the agent asks for one or you call sendImage.

import { CameraCapture } from "@avaclone/sdk";
const cam = new CameraCapture();
await cam.start("environment", document.querySelector("#preview")!);
avatar.setCamera(cam);      // the agent asks for a still when the visitor refers to something in view

// or show it things yourself, from any camera, file or canvas
showButton.onclick = () => avatar.sendImage(cam);                        // the next question is answered seeing this
fileInput.onchange = () => avatar.sendImage(fileInput.files![0], "Do you stock this?");   // a photo, as a turn
avatar.on("image", ({ ok, reason }) => { if (!ok) console.warn(reason); });   // vision.off, image.rate, …
// later: avatar.setCamera(null); cam.stop();

Streaming your own speech (stream sessions)

A session of kind stream has no agent: your own model, knowledge and logic run on your side and AvaClone supplies the face and the voice. Two ways in:

  • Text. sendText(line) each time your model has something to say. The line is read aloud in the session’s voice (the voice_id you gave POST /sessions, or the avatar’s own) and the face is lip-synced to it. Lines are spoken in the order sent; interrupt() cuts the current one and drops the rest. Symbols, money, units and markdown are spoken as words, and audio tags such as [chuckles] or [warmly] are performed on the Pro plan and above, never read aloud (the list).
  • Audio. Send 24 kHz PCM of speech you generated yourself (your own TTS, a human operator, a recording) with sendAudio and the avatar lip-syncs to it. Call endUtterance() when a passage ends.

Tokens are drawn while the stream is live, 30 a minute on Standard.

Notes

  • The element you mount into keeps its own positioning; the SDK only adds a canvas and video that fill it.
  • Browsers require a user gesture before audio plays. Mount on load, but call sendText or start the microphone from a click.
  • Frames arrive as JPEG at 25 fps with PCM audio in 0.96 s chunks; the SDK schedules both on one clock so lips and sound stay together.