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}
82
83/// Transcription text from server.
84#[derive(Debug, Clone, Deserialize)]
85pub struct TranscriptionPayload {
86    /// The transcribed text.
87    #[serde(default)]
88    pub text: Option<String>,
89}
90
91/// Server tool call request message.
92#[derive(Debug, Clone, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct ToolCallMessage {
95    /// The tool call payload.
96    pub tool_call: ToolCallPayload,
97}
98
99/// Payload for tool call.
100#[derive(Debug, Clone, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct ToolCallPayload {
103    /// Function calls requested by the model.
104    pub function_calls: Vec<FunctionCall>,
105}
106
107/// Server tool call cancellation message.
108#[derive(Debug, Clone, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct ToolCallCancellationMessage {
111    /// The tool call cancellation payload.
112    pub tool_call_cancellation: ToolCallCancellationPayload,
113}
114
115/// Payload for tool call cancellation.
116#[derive(Debug, Clone, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct ToolCallCancellationPayload {
119    /// IDs of the cancelled tool calls.
120    pub ids: Vec<String>,
121}
122
123/// Server GoAway signal — requesting graceful disconnect.
124#[derive(Debug, Clone, Deserialize)]
125#[serde(rename_all = "camelCase")]
126pub struct GoAwayMessage {
127    /// The GoAway payload.
128    pub go_away: GoAwayPayload,
129}
130
131/// Payload for GoAway.
132#[derive(Debug, Clone, Deserialize)]
133#[serde(rename_all = "camelCase")]
134pub struct GoAwayPayload {
135    /// Time remaining before forced disconnect. The wire carries a protobuf
136    /// `Duration` string such as `"30s"` or `"2.5s"`; an unparsable value
137    /// decodes as `None` so the GoAway itself is never lost.
138    #[serde(default, deserialize_with = "proto_duration::deserialize_opt")]
139    pub time_left: Option<std::time::Duration>,
140}
141
142/// Protobuf JSON `Duration` (`"3.5s"`) decoding.
143mod proto_duration {
144    use serde::{Deserialize, Deserializer};
145    use std::time::Duration;
146
147    pub(super) fn parse(s: &str) -> Option<Duration> {
148        let secs: f64 = s.trim().strip_suffix('s')?.parse().ok()?;
149        (secs.is_finite() && secs >= 0.0).then(|| Duration::from_secs_f64(secs))
150    }
151
152    pub(super) fn deserialize_opt<'de, D: Deserializer<'de>>(
153        d: D,
154    ) -> Result<Option<Duration>, D::Error> {
155        Ok(Option::<String>::deserialize(d)?.as_deref().and_then(parse))
156    }
157
158    #[cfg(test)]
159    mod tests {
160        use super::*;
161
162        #[test]
163        fn parses_proto_duration_strings() {
164            assert_eq!(parse("30s"), Some(Duration::from_secs(30)));
165            assert_eq!(parse("2.5s"), Some(Duration::from_millis(2500)));
166            assert_eq!(parse("0s"), Some(Duration::ZERO));
167            assert_eq!(parse("30"), None);
168            assert_eq!(parse("-1s"), None);
169            assert_eq!(parse("soon"), None);
170        }
171    }
172}
173
174/// Session resumption update from server (sent during active session).
175#[derive(Debug, Clone, Deserialize)]
176#[serde(rename_all = "camelCase")]
177pub struct SessionResumptionUpdateMessage {
178    /// The session resumption update payload.
179    pub session_resumption_update: SessionResumptionUpdatePayload,
180}
181
182/// Payload for session resumption update.
183#[derive(Debug, Clone, Deserialize)]
184#[serde(rename_all = "camelCase")]
185pub struct SessionResumptionUpdatePayload {
186    /// New opaque handle for session resumption.
187    #[serde(default)]
188    pub new_handle: Option<String>,
189    /// Whether the session is currently resumable.
190    #[serde(default)]
191    pub resumable: Option<bool>,
192    /// Index of the last client message consumed by the server.
193    #[serde(default)]
194    pub last_consumed_client_message_index: Option<String>,
195}
196
197/// Server-side voice activity detection event.
198#[derive(Debug, Clone, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct VoiceActivityMessage {
201    /// The voice activity payload.
202    pub voice_activity: VoiceActivityPayload,
203}
204
205/// Payload for voice activity detection.
206#[derive(Debug, Clone, Serialize, Deserialize)]
207#[serde(rename_all = "camelCase")]
208pub struct VoiceActivityPayload {
209    /// The type of voice activity event.
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub voice_activity_type: Option<VoiceActivityType>,
212}
213
214/// Type of voice activity event from the server.
215#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
216pub enum VoiceActivityType {
217    /// Voice activity started (user began speaking).
218    #[serde(rename = "VOICE_ACTIVITY_START")]
219    VoiceActivityStart,
220    /// Voice activity ended (user stopped speaking).
221    #[serde(rename = "VOICE_ACTIVITY_END")]
222    VoiceActivityEnd,
223}
224
225/// Server message wrapper — includes optional usage metadata alongside the message.
226#[derive(Debug, Clone, Deserialize)]
227#[serde(rename_all = "camelCase")]
228pub struct ServerMessageWrapper {
229    /// Token usage metadata (present on most server messages).
230    #[serde(default)]
231    pub usage_metadata: Option<UsageMetadata>,
232}
233
234/// Unified server message enum — parsed from incoming WebSocket text frames.
235///
236/// We use manual dispatch instead of `#[serde(untagged)]` for performance:
237/// untagged tries every variant in order. String-contains + targeted parse
238/// is O(1) routing.
239#[derive(Debug, Clone)]
240pub enum ServerMessage {
241    /// Setup handshake completed successfully.
242    SetupComplete(SetupCompleteMessage),
243    /// Model output content (text, audio, transcription, etc.).
244    ServerContent(Box<ServerContentMessage>),
245    /// Model requested one or more tool/function calls.
246    ToolCall(ToolCallMessage),
247    /// Server cancelled previously requested tool calls.
248    ToolCallCancellation(ToolCallCancellationMessage),
249    /// Server requesting graceful disconnect.
250    GoAway(GoAwayMessage),
251    /// Updated session resumption handle.
252    SessionResumptionUpdate(SessionResumptionUpdateMessage),
253    /// Server-side voice activity detection event.
254    VoiceActivity(VoiceActivityMessage),
255    /// Unrecognized message type (forward compatibility).
256    Unknown(serde_json::Value),
257}
258
259/// Single-pass deserialization target: every known server message is
260/// discriminated by exactly one top-level key, so one serde pass over the
261/// frame replaces the previous string-contains scan + targeted re-parse
262/// (which cost an O(n) `memchr` sweep per candidate key before parsing).
263#[derive(Deserialize)]
264#[serde(rename_all = "camelCase")]
265struct RawServerMessage {
266    setup_complete: Option<SetupCompletePayload>,
267    server_content: Option<ServerContentPayload>,
268    usage_metadata: Option<UsageMetadata>,
269    tool_call: Option<ToolCallPayload>,
270    tool_call_cancellation: Option<ToolCallCancellationPayload>,
271    go_away: Option<GoAwayPayload>,
272    session_resumption_update: Option<SessionResumptionUpdatePayload>,
273    voice_activity: Option<VoiceActivityPayload>,
274}
275
276impl ServerMessage {
277    /// Parse a server message from a JSON text frame.
278    ///
279    /// One serde pass: known messages are discriminated by their single
280    /// top-level key; frames with no known key fall back to a raw-value parse
281    /// and surface as [`ServerMessage::Unknown`] (forward compatibility).
282    pub fn parse(text: &str) -> Result<Self, serde_json::Error> {
283        let raw: RawServerMessage = match serde_json::from_str(text) {
284            Ok(raw) => raw,
285            // A known key with an unexpected payload shape should surface as
286            // a parse error (matching the previous targeted-parse behavior),
287            // but a frame that isn't an object at all falls through to Unknown.
288            Err(e) => {
289                return if text.trim_start().starts_with('{') {
290                    Err(e)
291                } else {
292                    serde_json::from_str::<serde_json::Value>(text).map(ServerMessage::Unknown)
293                };
294            }
295        };
296
297        if let Some(setup_complete) = raw.setup_complete {
298            Ok(ServerMessage::SetupComplete(SetupCompleteMessage {
299                setup_complete,
300            }))
301        } else if let Some(tool_call_cancellation) = raw.tool_call_cancellation {
302            Ok(ServerMessage::ToolCallCancellation(
303                ToolCallCancellationMessage {
304                    tool_call_cancellation,
305                },
306            ))
307        } else if let Some(tool_call) = raw.tool_call {
308            Ok(ServerMessage::ToolCall(ToolCallMessage { tool_call }))
309        } else if let Some(server_content) = raw.server_content {
310            Ok(ServerMessage::ServerContent(Box::new(
311                ServerContentMessage {
312                    server_content,
313                    usage_metadata: raw.usage_metadata,
314                },
315            )))
316        } else if let Some(go_away) = raw.go_away {
317            Ok(ServerMessage::GoAway(GoAwayMessage { go_away }))
318        } else if let Some(session_resumption_update) = raw.session_resumption_update {
319            Ok(ServerMessage::SessionResumptionUpdate(
320                SessionResumptionUpdateMessage {
321                    session_resumption_update,
322                },
323            ))
324        } else if let Some(voice_activity) = raw.voice_activity {
325            Ok(ServerMessage::VoiceActivity(VoiceActivityMessage {
326                voice_activity,
327            }))
328        } else {
329            // No known key: unknown message type (forward compatibility).
330            serde_json::from_str::<serde_json::Value>(text).map(ServerMessage::Unknown)
331        }
332    }
333}