diff --git a/agents/deploy/protocol.mdx b/agents/deploy/protocol.mdx index 5808bbd..a4b8a59 100644 --- a/agents/deploy/protocol.mdx +++ b/agents/deploy/protocol.mdx @@ -55,13 +55,13 @@ The response is a discriminated union on `transport`. The `livekit` arm carries Once connected, a session uses these channels. The two `*-event` topics carry reliable [data packets](https://docs.livekit.io/home/client/data/messages/): one complete JSON object per packet. -| Channel | Direction | Carries | -| -------------------------------------- | -------------- | --------------------------------------------------------- | -| Audio tracks | both | Your microphone up, the agent's speech down | -| `agent-event` topic | agent → client | Control events: client tool calls, tool lifecycle, errors | -| `client-event` topic | client → agent | Text turns, activity, interrupts, hangup, tool results | -| `lk.transcription` text streams | agent → client | Streaming transcripts for both sides | -| `lk.agent.state` participant attribute | agent → client | Agent pipeline state (sticky) | +| Channel | Direction | Carries | +| -------------------------------------- | -------------- | ---------------------------------------------------------------------- | +| Audio tracks | both | Your microphone up, the agent's speech down | +| `agent-event` topic | agent → client | Control events: client tool calls, tool lifecycle, errors, session end | +| `client-event` topic | client → agent | Text turns, activity, interrupts, hangup, tool results | +| `lk.transcription` text streams | agent → client | Streaming transcripts for both sides | +| `lk.agent.state` participant attribute | agent → client | Agent pipeline state (sticky) | Data-channel message fields are camelCase; REST bodies are snake_case. @@ -166,6 +166,14 @@ These events are on by default and their payloads travel to the end user's clien `code` is a coarse category only: `provider_error` (an upstream model or voice provider failed) or `internal_error` (the runtime failed). The message deliberately carries no raw error detail. +### `session.ended` + +```json session.ended +{ "type": "session.ended", "reason": "conversation_timeout" } +``` + +Sent once, right before the server ends the call (before the agent leaves the room or the room is deleted), so a client learns the cause instead of inferring it from the disconnect. `reason` is `user_hangup` (the user hung up, or a backend ended the session through the API), `agent_hangup` (the agent hung up or the workflow reached an end node), `conversation_timeout` (the session reached its maximum duration), or `escalated` (the call was transferred to a human). The values match `end_reason` on the [session record](/agents/monitor/conversation-history#status-and-end-reason); treat the set as open, per the [compatibility rules](#compatibility-rules). A call that drops without this message has no announced reason: the SDKs report `connection_lost` once reconnection fails. + ## Client events: `client-event` Publish these on the `client-event` topic. The agent ignores malformed JSON and unknown types. diff --git a/agents/deploy/web-sdk.mdx b/agents/deploy/web-sdk.mdx index 275cfca..6962b02 100644 --- a/agents/deploy/web-sdk.mdx +++ b/agents/deploy/web-sdk.mdx @@ -105,11 +105,15 @@ connecting → connected ⇄ reconnecting → ended(reason) When the session ends, `disconnect` fires with a reason (also available as `session.endReason`): -| `EndReason` | Meaning | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `user_hangup` | You called `session.end()`, or the user left the page | -| `agent_hangup` | The server ended the session: the agent hung up (for example via the hang-up [system tool](/agents/build/system-tools)), the session hit its maximum duration, or it was force-ended; the protocol does not distinguish these | -| `connection_lost` | The connection dropped and could not be recovered, or the agent never joined | +| `EndReason` | Meaning | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `user_hangup` | You called `session.end()`, the user left the page, or your backend [ended the session](/agents/deploy/authenticated-sessions#ending-sessions-from-your-backend) | +| `agent_hangup` | The agent ended the call: the hang-up [system tool](/agents/build/system-tools), a workflow end node, or an idle hangup | +| `conversation_timeout` | The session reached the agent's [maximum duration](/agents/build/configuration#call-duration) | +| `escalated` | The call was transferred to a human | +| `connection_lost` | The connection dropped and could not be recovered, the agent never joined, or the call ended before the server could say why | + +Every reason except `connection_lost` comes from the server: it sends the protocol's [`session.ended` event](/agents/deploy/protocol#session-ended) just before tearing the call down, and the values match `end_reason` on the [session record](/agents/monitor/conversation-history#status-and-end-reason), so your client and your backend agree about how a session ended. This needs `@fishaudio/agent-client` 0.3.0 or later; older clients infer the reason from the disconnect and report every server-side end as `agent_hangup`. Brief network drops don't end the session: the SDK moves to `reconnecting` and back to `connected` automatically, reusing the same session. It never creates a new session, so you never need a new token mid-call.