Skip to content

Events & transcripts

Every incremental and final event carries a stable turnId. UI must upsert in place by turnId — never append each delta as a new bubble.

session.on('asr_partial', ({ turnId, text }) => {
upsert({ id: turnId, role: 'user', text, live: true });
});
session.on('asr_final', ({ turnId, text }) => {
upsert({ id: turnId, role: 'user', text, live: false });
});
session.on('assistant_text_delta', ({ turnId, text }) => {
upsert({ id: turnId, role: 'assistant', text, live: true });
});
session.on('assistant_text', ({ turnId, text }) => {
upsert({ id: turnId, role: 'assistant', text, live: false });
});
Event When UI
statechange State machine transition Controls / indicators
asr_partial Provisional ASR Update user row + live cursor
asr_final User turn confirmed Replace provisional text
user_audio_end VAD ends user speech Latency start marker
user_audio_final Recorder stopped and final bytes flushed Private upload / audit
assistant_text_delta Model text fragment Upsert assistant row (prefer accumulated text)
assistant_text Reply confirmed Commit final text
assistant_audio_start / _end Playback bounds First-audio latency, speaking state
assistant_audio Complete generated audio snapshot Private upload / replay
turn Turn committed History
usage Usage snapshot Metrics
finished Session ended Teardown UI
error Normalized error Toast / retry

assistant_text_delta.delta is the new fragment; render accumulated text. Finals may normalize punctuation. Audio and text events for the same speaker share a turnId.

asr_final does not have to come from a separate ASR. When AudioLLMProvider.transcribesInput is true, Core commits the authoritative input transcript from the same audio-turn response as asr_final, and providers.asr may be omitted. Otherwise caption ASR owns user subtitles.

idle → starting → listening → user_speaking → processing
↑ ↓
└── assistant_speaking ─┘
└── user_speaking (confirmed barge-in)
Any active state → paused / finished / error
paused → listening

The common audioLlmStartTiming: 'after_asr_final' order is:

Order Event Guarantee
1 user_audio_end VAD/manual boundary; recorder flush starts
2 user_audio_final Complete encoded user recording is now available
3 asr_final Authoritative caption (it may be empty)
4 assistant_text_delta Zero or more accumulated reply updates
5 assistant_text / assistant_audio Complete snapshots; buffered output emits these before playback, streamed output may finalize them after playback starts
6 assistant_audio_start Playback has started; it can precede step 5 for streaming PCM
7 assistant_audio_end Playback completed or was interrupted

With audioLlmStartTiming: 'after_audio', assistant deltas/audio may start before asr_final. Build UI and persistence around turnId, not global temporary variables.

Call / event Contract
start() Valid once from idle. Provider/runtime startup failures emit error; an illegal repeated call rejects with invalid_state.
finish() Graceful and idempotent. Cancels pending work, stops I/O, then emits usage and exactly one finished. Calling from idle is a no-op.
dispose() Idempotent hard teardown. Removes listeners and does not emit finished; call finish() first when product analytics need completion.
error fatal: true means the state becomes error. fatal: false is a configured recoverable Audio LLM turn failure and the session resumes listening.

Only safeMessage is intended for production logs/UI. message, cause, and raw can contain provider or user data.

Payload shapes: VoiceSessionEventMap in the API reference.