@facemode/agents-plugin-facemode package connects a LiveKit Agents JS
AgentSession to FaceMode. The plugin creates the FaceMode session, streams
your TTS audio to FaceMode over the canonical WebSocket protocol, and routes
the avatar video track into your LiveKit room.
Install
Quickstart
1
Create the AvatarSession
Construct
AvatarSession with apiKey and avatarId.2
Connect and start the avatar
Call
await ctx.connect(), then avatar.start(session, ctx.room, { room }).
The room option is the credential object sent to FaceMode; it is separate
from the already-connected ctx.room the agent uses for media. Call
avatar.start() before session.start().3
Start the agent with audio output disabled
Start the
AgentSession with outputOptions: { audioEnabled: false }. The
plugin replaces the session’s audio output so TTS frames go to FaceMode
instead of being published directly.4
Wait for the avatar to join
await avatar.waitForJoin() resolves when the avatar signals audio_ready
or its avatar video track is subscribed. It rejects with FaceModeProtocolError
after the timeout (30000 ms by default; pass { timeout: null } to wait
indefinitely).AvatarSession options
Useful properties:
avatar.sessionId returns the active FaceMode session ID
and avatar.avatarIdentity returns the resolved participant identity.
start() signature
Room token grants
Theroom.token value is used by FaceMode to join your room. It must
grant the avatar participant join, publish, and subscribe permission on the
exact room name (room_join, can_publish, can_subscribe). If the token
fixes a participant identity, set avatarParticipantIdentity to match. Keep the
token on your server; never expose it to browser clients or log it.
inputProvider
SetinputProvider to pin the upstream speech input provider for the session.
Supported values: 'deepgram', 'gemini', 'gnani', 'elevenlabs',
'openai', 'cartesia', 'sarvam', 'custom'. When omitted, the field is
not sent and the backend default applies.
Audio format
The plugin negotiates the canonical protocol using your TTS output format and locks it for the session:- Encoding is
pcm_s16lesent as binary WebSocket frames. - Sample rate must be an integer between 8000 and 48000 Hz. Preferred rates are 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, and 48000; other in-range rates are accepted and logged once.
- Channels must be 1 or 2.
- The declared rate must match the actual PCM data. A format change
mid-session raises
FaceModeProtocolError.
Auto-reconnect
If the WebSocket transport drops afterstarted, the plugin reconnects in the
background:
- It posts to
/api/sessions/{id}/reconnect, receives a fresh one-timewsToken(tokens are never reused), and opens a new socket with thefacemode.<newToken>subprotocol. - It resends the identical
startmessage and waits forstartedbefore resuming. - Session identity and outgoing sequence numbers are preserved. Sends made while the transport is down wait for the barrier and are delivered once on the new socket; nothing already sent is replayed.
stop(), after FaceMode reports
ended, or after a fatal protocol error. If all attempts fail, pending and
future sends raise FaceModeProtocolError.
Lifecycle and shutdown
Callawait avatar.stop() (or aclose()) to shut down. On a negotiated
session the plugin sends end_session and waits up to 3 seconds for the
canonical ended acknowledgement before closing the socket. Interruptions send
cancel_utterance; a normal end of speech sends end_utterance.
Error types
All errors derive fromFaceModeError:
The JS package exports
FaceModeApiError (lowercase “pi”), while the Python
package exports FaceModeAPIError.Timeouts
The plugin disables WebSocket compression and uses an application-level
ping
every 15 seconds after negotiation instead of native keepalives.
API URL override
The default API URL ishttps://api.facemode.io/api. Set apiUrl (or the
FACEMODE_API_URL environment variable in your own config) for a self-hosted
or local backend.