gemini_genai_rs/protocol/messages/
server.rs

1//! Server → Client message types for the Gemini Live wire protocol.
2
3use serde::{Deserialize, Serialize};
4
5use crate::protocol::types::*;
6
7/// Server setup complete acknowledgment.
8#[derive(Debug, Clone, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct SetupCompleteMessage {
11    /// The setup complete payload.
12    pub setup_complete: SetupCompletePayload,
13}
14
15/// Payload for setup complete.
16#[derive(Debug, Clone, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct SetupCompletePayload {
19    /// Session resumption result, if resumption was requested.
20    #[serde(default)]
21    pub session_resumption: Option<SessionResumptionResult>,
22}
23
24/// Session resumption result from server.
25#[derive(Debug, Clone, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct SessionResumptionResult {
28    /// Opaque handle for future session resumption.
29    #[serde(default)]
30    pub handle: Option<String>,
31    /// Whether the session was successfully resumed.
32    #[serde(default)]
33    pub resumed: Option<bool>,
34}
35
36/// Server content message containing model output.
37#[derive(Debug, Clone, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct ServerContentMessage {
40    /// The server content payload.
41    pub server_content: ServerContentPayload,
42    /// Token usage metadata (present on most server messages).
43    #[serde(default)]
44    pub usage_metadata: Option<UsageMetadata>,
45}
46
47/// Payload for server content.
48#[derive(Debug, Clone, Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub struct ServerContentPayload {
51    /// Model output content for this turn.
52    #[serde(default)]
53    pub model_turn: Option<Content>,
54    /// Whether the model's turn is complete.
55    #[serde(default)]
56    pub turn_complete: Option<bool>,
57    /// Whether all generation (including tool use) is complete.
58    #[serde(default)]
59    pub generation_complete: Option<bool>,
60    /// Whether the model was interrupted by user barge-in.
61    #[serde(default)]
62    pub interrupted: Option<bool>,
63    /// Transcription of user audio input.
64    #[serde(default)]
65    pub input_transcription: Option<TranscriptionPayload>,
66    /// Transcription of model audio output.
67    #[serde(default)]
68    pub output_transcription: Option<TranscriptionPayload>,
69    /// Grounding metadata from search results.
70    #[serde(default)]
71    pub grounding_metadata: Option<GroundingMetadata>,
72    /// URL context metadata for content sourced from URLs.
73    #[serde(default)]
74    pub url_context_metadata: Option<UrlContextMetadata>,
75    /// Reason why the model's turn completed (e.g. "STOP", "MAX_TOKENS").
76    #[serde(default)]
77    pub turn_complete_reason: Option<String>,
78    /// Whether the server is waiting for user input.
79    #[serde(default)]
80    pub waiting_for_input: Option<bool>,
81    /// Progress of a longer interaction, e.g. `"IN_PROGRESS"` while Gemini
82    /// 3.8 Live Extended Thinking works on an answer it will give in a later
83    /// turn.
84    #[serde(default)]
85    pub interaction_status: Option<String>,
86}
87
88/// Transcription text from server.
89#[derive(Debug, Clone, Deserialize)]
90pub struct TranscriptionPayload {
91    /// The transcribed text.
92    #[serde(default)]
93    pub text: Option<String>,
94}
95
96/// Server tool call request message.
97#[derive(Debug, Clone, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct ToolCallMessage {
100    /// The tool call payload.
101    pub tool_call: ToolCallPayload,
102}
103
104/// Payload for tool call.
105#[derive(Debug, Clone, Deserialize)]
106#[serde(rename_all = "camelCase")]
107pub struct ToolCallPayload {
108    /// Function calls requested by the model.
109    pub function_calls: Vec<FunctionCall>,
110}
111
112/// Server tool call cancellation message.
113#[derive(Debug, Clone, Deserialize)]
114#[serde(rename_all = "camelCase")]
115pub struct ToolCallCancellationMessage {
116    /// The tool call cancellation payload.
117    pub tool_call_cancellation: ToolCallCancellationPayload,
118}
119
120/// Payload for tool call cancellation.
121#[derive(Debug, Clone, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct ToolCallCancellationPayload {
124    /// IDs of the cancelled tool calls.
125    pub ids: Vec<String>,
126}
127
128/// Server GoAway signal — requesting graceful disconnect.
129#[derive(Debug, Clone, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct GoAwayMessage {
132    /// The GoAway payload.
133    pub go_away: GoAwayPayload,
134}
135
136/// Payload for GoAway.
137#[derive(Debug, Clone, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct GoAwayPayload {
140    /// Time remaining before forced disconnect. The wire carries a protobuf
141    /// `Duration` string such as `"30s"` or `"2.5s"`; an unparsable value
142    /// decodes as `None` so the GoAway itself is never lost.
143    #[serde(default, deserialize_with = "proto_duration::deserialize_opt")]
144    pub time_left: Option<std::time::Duration>,
145}
146
147/// Protobuf JSON `Duration` (`"3.5s"`) decoding.
148mod proto_duration {
149    use serde::{Deserialize, Deserializer};
150    use std::time::Duration;
151
152    pub(super) fn parse(s: &str) -> Option<Duration> {
153        let secs: f64 = s.trim().strip_suffix('s')?.parse().ok()?;
154        (secs.is_finite() && secs >= 0.0).then(|| Duration::from_secs_f64(secs))
155    }
156
157    pub(super) fn deserialize_opt<'de, D: Deserializer<'de>>(
158        d: D,
159    ) -> Result<Option<Duration>, D::Error> {
160        Ok(Option::<String>::deserialize(d)?.as_deref().and_then(parse))
161    }
162
163    #[cfg(test)]
164    mod tests {
165        use super::*;
166
167        #[test]
168        fn parses_proto_duration_strings() {
169            assert_eq!(parse("30s"), Some(Duration::from_secs(30)));
170            assert_eq!(parse("2.5s"), Some(Duration::from_millis(2500)));
171            assert_eq!(parse("0s"), Some(Duration::ZERO));
172            assert_eq!(parse("30"), None);
173            assert_eq!(parse("-1s"), None);
174            assert_eq!(parse("soon"), None);
175        }
176    }
177}
178
179/// Session resumption update from server (sent during active session).
180#[derive(Debug, Clone, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct SessionResumptionUpdateMessage {
183    /// The session resumption update payload.
184    pub session_resumption_update: SessionResumptionUpdatePayload,
185}
186
187/// Payload for session resumption update.
188#[derive(Debug, Clone, Deserialize)]
189#[serde(rename_all = "camelCase")]
190pub struct SessionResumptionUpdatePayload {
191    /// New opaque handle for session resumption.
192    #[serde(default)]
193    pub new_handle: Option<String>,
194    /// Whether the session is currently resumable.
195    #[serde(default)]
196    pub resumable: Option<bool>,
197    /// Index of the last client message consumed by the server (transparent
198    /// resumption). An int64, which proto JSON sends as a string; a bare
199    /// number is accepted too.
200    #[serde(default, deserialize_with = "string_or_number")]
201    pub last_consumed_client_message_index: Option<String>,
202}
203
204/// An optional int64 field, as a string whether the frame carries `"7"` or `7`.
205fn string_or_number<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Option<String>, D::Error> {
206    Ok(match Option::<serde_json::Value>::deserialize(d)? {
207        Some(serde_json::Value::String(s)) => Some(s),
208        Some(serde_json::Value::Number(n)) => Some(n.to_string()),
209        _ => None,
210    })
211}
212
213/// Server-side voice activity detection event.
214#[derive(Debug, Clone, Serialize, Deserialize)]
215#[serde(rename_all = "camelCase")]
216pub struct VoiceActivityMessage {
217    /// The voice activity payload.
218    pub voice_activity: VoiceActivityPayload,
219}
220
221/// Payload for voice activity detection.
222#[derive(Debug, Clone, Serialize, Deserialize)]
223#[serde(rename_all = "camelCase")]
224pub struct VoiceActivityPayload {
225    /// The type of voice activity event.
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub voice_activity_type: Option<VoiceActivityType>,
228    /// When the activity was detected, in audio time from the start of the
229    /// stream (a proto duration such as `"1.250s"`).
230    #[serde(default, skip_serializing_if = "Option::is_none")]
231    pub audio_offset: Option<String>,
232}
233
234/// Type of voice activity event from the server.
235///
236/// The API sends `ACTIVITY_START` / `ACTIVITY_END`; the `VOICE_ACTIVITY_*`
237/// spellings earlier releases expected are still accepted, and any other
238/// value reads as [`Unspecified`](Self::Unspecified) instead of failing the
239/// frame.
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
241#[non_exhaustive]
242pub enum VoiceActivityType {
243    /// Voice activity started (user began speaking).
244    #[serde(rename = "ACTIVITY_START", alias = "VOICE_ACTIVITY_START")]
245    VoiceActivityStart,
246    /// Voice activity ended (user stopped speaking).
247    #[serde(rename = "ACTIVITY_END", alias = "VOICE_ACTIVITY_END")]
248    VoiceActivityEnd,
249    /// `TYPE_UNSPECIFIED`, or a value this release does not know.
250    #[serde(rename = "TYPE_UNSPECIFIED", other)]
251    Unspecified,
252}
253
254/// Server message wrapper — includes optional usage metadata alongside the message.
255#[derive(Debug, Clone, Deserialize)]
256#[serde(rename_all = "camelCase")]
257pub struct ServerMessageWrapper {
258    /// Token usage metadata (present on most server messages).
259    #[serde(default)]
260    pub usage_metadata: Option<UsageMetadata>,
261}
262
263/// Unified server message enum — parsed from incoming WebSocket text frames.
264///
265/// We use manual dispatch instead of `#[serde(untagged)]` for performance:
266/// untagged tries every variant in order. String-contains + targeted parse
267/// is O(1) routing.
268#[derive(Debug, Clone)]
269pub enum ServerMessage {
270    /// Setup handshake completed successfully.
271    SetupComplete(SetupCompleteMessage),
272    /// Model output content (text, audio, transcription, etc.).
273    ServerContent(Box<ServerContentMessage>),
274    /// Model requested one or more tool/function calls.
275    ToolCall(ToolCallMessage),
276    /// Server cancelled previously requested tool calls.
277    ToolCallCancellation(ToolCallCancellationMessage),
278    /// Server requesting graceful disconnect.
279    GoAway(GoAwayMessage),
280    /// Updated session resumption handle.
281    SessionResumptionUpdate(SessionResumptionUpdateMessage),
282    /// Server-side voice activity detection event.
283    VoiceActivity(VoiceActivityMessage),
284    /// Unrecognized message type (forward compatibility).
285    Unknown(serde_json::Value),
286}
287
288/// Single-pass deserialization target: every known server message is
289/// discriminated by exactly one top-level key, so one serde pass over the
290/// frame replaces the previous string-contains scan + targeted re-parse
291/// (which cost an O(n) `memchr` sweep per candidate key before parsing).
292#[derive(Deserialize)]
293#[serde(rename_all = "camelCase")]
294struct RawServerMessage {
295    setup_complete: Option<SetupCompletePayload>,
296    server_content: Option<ServerContentPayload>,
297    usage_metadata: Option<UsageMetadata>,
298    tool_call: Option<ToolCallPayload>,
299    tool_call_cancellation: Option<ToolCallCancellationPayload>,
300    go_away: Option<GoAwayPayload>,
301    session_resumption_update: Option<SessionResumptionUpdatePayload>,
302    voice_activity: Option<VoiceActivityPayload>,
303}
304
305impl ServerMessage {
306    /// Parse a server message from a JSON text frame.
307    ///
308    /// One serde pass: known messages are discriminated by their single
309    /// top-level key; frames with no known key fall back to a raw-value parse
310    /// and surface as [`ServerMessage::Unknown`] (forward compatibility).
311    pub fn parse(text: &str) -> Result<Self, serde_json::Error> {
312        let raw: RawServerMessage = match serde_json::from_str(text) {
313            Ok(raw) => raw,
314            // A known key with an unexpected payload shape should surface as
315            // a parse error (matching the previous targeted-parse behavior),
316            // but a frame that isn't an object at all falls through to Unknown.
317            Err(e) => {
318                return if text.trim_start().starts_with('{') {
319                    Err(e)
320                } else {
321                    serde_json::from_str::<serde_json::Value>(text).map(ServerMessage::Unknown)
322                };
323            }
324        };
325
326        if let Some(setup_complete) = raw.setup_complete {
327            Ok(ServerMessage::SetupComplete(SetupCompleteMessage {
328                setup_complete,
329            }))
330        } else if let Some(tool_call_cancellation) = raw.tool_call_cancellation {
331            Ok(ServerMessage::ToolCallCancellation(
332                ToolCallCancellationMessage {
333                    tool_call_cancellation,
334                },
335            ))
336        } else if let Some(tool_call) = raw.tool_call {
337            Ok(ServerMessage::ToolCall(ToolCallMessage { tool_call }))
338        } else if let Some(server_content) = raw.server_content {
339            Ok(ServerMessage::ServerContent(Box::new(
340                ServerContentMessage {
341                    server_content,
342                    usage_metadata: raw.usage_metadata,
343                },
344            )))
345        } else if let Some(go_away) = raw.go_away {
346            Ok(ServerMessage::GoAway(GoAwayMessage { go_away }))
347        } else if let Some(session_resumption_update) = raw.session_resumption_update {
348            Ok(ServerMessage::SessionResumptionUpdate(
349                SessionResumptionUpdateMessage {
350                    session_resumption_update,
351                },
352            ))
353        } else if let Some(voice_activity) = raw.voice_activity {
354            Ok(ServerMessage::VoiceActivity(VoiceActivityMessage {
355                voice_activity,
356            }))
357        } else {
358            // No known key: unknown message type (forward compatibility).
359            serde_json::from_str::<serde_json::Value>(text).map(ServerMessage::Unknown)
360        }
361    }
362}