Guides / Handling events
Handling events
The client is event-driven. Register handlers with the
@client.on(...) decorator or client.add_listener(...).
Handlers may be sync or async, and a handler that raises never
breaks the SDK — the error is routed to your ERROR handlers.
Registering handlers
from zeli import ZeliEvent
@client.on(ZeliEvent.SESSION_READY)
async def on_ready(info):
print("ready:", info.session_id)
# Equivalent, without the decorator:
def on_error(err):
print("error:", err)
client.add_listener(ZeliEvent.ERROR, on_error)
client.remove_listener(ZeliEvent.ERROR, on_error)The events
For the complete list of the 14 ZeliEvent members and their payload models, see
Events & models.
| Event | Payload | Fires when |
|---|---|---|
CONNECTION_ESTABLISHED | — | The media connection is up. |
SESSION_READY | SessionInfo | The control channel handshake completed. |
MESSAGE_RECEIVED | Message | A user or assistant message is finalized. |
MESSAGE_STREAM_EVENT_RECEIVED | MessageStreamEvent | An incremental transcript chunk arrives. |
MESSAGE_HISTORY_UPDATED | list[Message] | The transcript changed. |
AVATAR_SPEECH_STARTED | correlation_id | The avatar starts speaking. |
AVATAR_SPEECH_ENDED | correlation_id | The avatar stops speaking. |
TALK_STREAM_INTERRUPTED | — | A barge-in interrupted playback. |
EMOTION_DETECTED | EmotionEvent | The server classified an utterance's emotion. |
CONNECTION_CLOSED | code, reason | The session ended. |
SERVER_WARNING | message | The server sent a non-fatal warning. |
ERROR | error | A server-side or handler error occurred. |
USER_SPEECH_* are reserved
USER_SPEECH_STARTED / USER_SPEECH_ENDED are defined on
the enum for a future mic-input path but are
not currently emitted by the client.
Streaming a transcript
MESSAGE_STREAM_EVENT_RECEIVED fires per spoken clause as the reply is delivered,
so you can render captions in real time:
@client.on(ZeliEvent.MESSAGE_STREAM_EVENT_RECEIVED)
async def on_chunk(event):
print(event.content, end=" ", flush=True) # clause-by-clause
if event.end_of_speech:
print()
@client.on(ZeliEvent.MESSAGE_RECEIVED)
async def on_final(message):
print(f"[final] {message.role.value}: {message.content}")Reacting to speech state
@client.on(ZeliEvent.AVATAR_SPEECH_STARTED)
async def on_speaking(correlation_id):
ui.set_speaking(True)
@client.on(ZeliEvent.AVATAR_SPEECH_ENDED)
async def on_done(correlation_id):
ui.set_speaking(False)Message history
The client accumulates the transcript for you:
for msg in client.get_message_history():
print(f"{msg.role.value}: {msg.content}")get_message_history() returns a copy — mutating it won't affect the client.