gemini_genai_rs/protocol/messages/
mod.rs1pub mod client;
4pub mod server;
5
6pub use client::*;
7pub use server::*;
8
9#[cfg(test)]
10mod tests {
11 use super::*;
12 use crate::protocol::types::*;
13
14 #[test]
15 fn setup_message_serialization() {
16 let config = SessionConfig::new("test-key")
17 .model(ModelId::from_static("models/gemini-2.0-flash-live-001"))
18 .voice(Voice::Kore)
19 .system_instruction("You are a helpful assistant.");
20
21 let json = config.to_setup_json();
22 assert!(json.contains("\"setup\""));
23 assert!(json.contains("\"generationConfig\""));
24 assert!(json.contains("\"Kore\""));
25 assert!(json.contains("\"systemInstruction\""));
26 }
27
28 #[test]
29 fn parse_setup_complete() {
30 let json = r#"{"setupComplete":{"sessionResumption":{"handle":"abc123"}}}"#;
31 let msg = ServerMessage::parse(json).unwrap();
32 match msg {
33 ServerMessage::SetupComplete(sc) => {
34 let handle = sc.setup_complete.session_resumption.unwrap().handle;
35 assert_eq!(handle, Some("abc123".to_string()));
36 }
37 _ => panic!("Expected SetupComplete"),
38 }
39 }
40
41 #[test]
42 fn parse_server_content_text() {
43 let json = r#"{
44 "serverContent": {
45 "modelTurn": {
46 "parts": [{"text": "Hello! How can I help?"}]
47 },
48 "turnComplete": true
49 }
50 }"#;
51 let msg = ServerMessage::parse(json).unwrap();
52 match msg {
53 ServerMessage::ServerContent(sc) => {
54 assert!(sc.server_content.turn_complete.unwrap_or(false));
55 let turn = sc.server_content.model_turn.unwrap();
56 assert_eq!(turn.parts.len(), 1);
57 match &turn.parts[0] {
58 Part::Text { text } => assert_eq!(text, "Hello! How can I help?"),
59 _ => panic!("Expected text part"),
60 }
61 }
62 _ => panic!("Expected ServerContent"),
63 }
64 }
65
66 #[test]
67 fn parse_server_content_audio() {
68 let json = r#"{
69 "serverContent": {
70 "modelTurn": {
71 "parts": [{"inlineData": {"mimeType": "audio/pcm", "data": "AAAA"}}]
72 }
73 }
74 }"#;
75 let msg = ServerMessage::parse(json).unwrap();
76 match msg {
77 ServerMessage::ServerContent(sc) => {
78 let turn = sc.server_content.model_turn.unwrap();
79 match &turn.parts[0] {
80 Part::InlineData { inline_data } => {
81 assert_eq!(inline_data.mime_type, "audio/pcm");
82 }
83 _ => panic!("Expected inline data part"),
84 }
85 }
86 _ => panic!("Expected ServerContent"),
87 }
88 }
89
90 #[test]
91 fn parse_tool_call() {
92 let json = r#"{
93 "toolCall": {
94 "functionCalls": [
95 {"name": "get_weather", "args": {"city": "London"}, "id": "call-1"}
96 ]
97 }
98 }"#;
99 let msg = ServerMessage::parse(json).unwrap();
100 match msg {
101 ServerMessage::ToolCall(tc) => {
102 assert_eq!(tc.tool_call.function_calls.len(), 1);
103 assert_eq!(tc.tool_call.function_calls[0].name, "get_weather");
104 }
105 _ => panic!("Expected ToolCall"),
106 }
107 }
108
109 #[test]
110 fn parse_tool_call_cancellation() {
111 let json = r#"{"toolCallCancellation": {"ids": ["call-1", "call-2"]}}"#;
112 let msg = ServerMessage::parse(json).unwrap();
113 match msg {
114 ServerMessage::ToolCallCancellation(tc) => {
115 assert_eq!(tc.tool_call_cancellation.ids, vec!["call-1", "call-2"]);
116 }
117 _ => panic!("Expected ToolCallCancellation"),
118 }
119 }
120
121 #[test]
122 fn parse_go_away() {
123 let json = r#"{"goAway": {"timeLeft": "30s"}}"#;
124 let msg = ServerMessage::parse(json).unwrap();
125 match msg {
126 ServerMessage::GoAway(ga) => {
127 assert_eq!(
128 ga.go_away.time_left,
129 Some(std::time::Duration::from_secs(30))
130 );
131 }
132 _ => panic!("Expected GoAway"),
133 }
134 }
135
136 #[test]
137 fn parse_interrupted() {
138 let json = r#"{"serverContent": {"interrupted": true}}"#;
139 let msg = ServerMessage::parse(json).unwrap();
140 match msg {
141 ServerMessage::ServerContent(sc) => {
142 assert!(sc.server_content.interrupted.unwrap_or(false));
143 }
144 _ => panic!("Expected ServerContent"),
145 }
146 }
147
148 #[test]
149 fn parse_usage_only_message() {
150 let json = r#"{"usageMetadata": {"promptTokenCount": 12, "totalTokenCount": 30}}"#;
151 let msg = ServerMessage::parse(json).unwrap();
152 match msg {
153 ServerMessage::ServerContent(sc) => {
154 let usage = sc.usage_metadata.expect("usage kept");
155 assert_eq!(usage.prompt_token_count, Some(12));
156 assert_eq!(usage.total_token_count, Some(30));
157 assert!(sc.server_content.model_turn.is_none());
158 assert!(sc.server_content.turn_complete.is_none());
159 }
160 other => panic!("Expected ServerContent, got {other:?}"),
161 }
162 }
163
164 #[test]
165 fn parse_unknown_message() {
166 let json = r#"{"newFeature": {"value": 42}}"#;
167 let msg = ServerMessage::parse(json).unwrap();
168 assert!(matches!(msg, ServerMessage::Unknown(_)));
169 }
170
171 #[test]
172 fn realtime_input_serialization_audio() {
173 let msg = RealtimeInputMessage {
174 realtime_input: RealtimeInputPayload {
175 media_chunks: Vec::new(),
176 audio: Some(Blob {
177 mime_type: "audio/pcm".to_string(),
178 data: "AQIDBA==".to_string(),
179 }),
180 video: None,
181 audio_stream_end: None,
182 text: None,
183 },
184 };
185 let json = serde_json::to_string(&msg).unwrap();
186 assert!(json.contains("\"realtimeInput\""));
187 assert!(json.contains("\"audio\""));
188 assert!(json.contains("\"mimeType\""));
189 assert!(!json.contains("\"mediaChunks\""));
191 }
192
193 #[test]
194 fn realtime_input_serialization_legacy() {
195 let msg = RealtimeInputMessage {
196 realtime_input: RealtimeInputPayload {
197 media_chunks: vec![MediaChunk {
198 mime_type: "audio/pcm".to_string(),
199 data: "AQIDBA==".to_string(),
200 }],
201 audio: None,
202 video: None,
203 audio_stream_end: None,
204 text: None,
205 },
206 };
207 let json = serde_json::to_string(&msg).unwrap();
208 assert!(json.contains("\"mediaChunks\""));
209 }
210
211 #[test]
212 fn parse_session_resumption_update() {
213 let json = r#"{"sessionResumptionUpdate": {"newHandle": "handle-xyz", "resumable": true}}"#;
214 let msg = ServerMessage::parse(json).unwrap();
215 match msg {
216 ServerMessage::SessionResumptionUpdate(sru) => {
217 assert_eq!(
218 sru.session_resumption_update.new_handle,
219 Some("handle-xyz".to_string())
220 );
221 assert_eq!(sru.session_resumption_update.resumable, Some(true));
222 }
223 _ => panic!("Expected SessionResumptionUpdate"),
224 }
225 }
226
227 #[test]
228 fn tool_response_serialization() {
229 let msg = ToolResponseMessage {
230 tool_response: ToolResponsePayload {
231 function_responses: vec![FunctionResponse {
232 name: "get_weather".to_string(),
233 response: serde_json::json!({"temp": 22}),
234 id: Some("call-1".to_string()),
235 scheduling: None,
236 }],
237 },
238 };
239 let json = serde_json::to_string(&msg).unwrap();
240 assert!(json.contains("\"toolResponse\""));
241 assert!(json.contains("\"functionResponses\""));
242 }
243
244 #[test]
245 fn client_content_serialization() {
246 let msg = ClientContentMessage {
247 client_content: ClientContentPayload {
248 turns: vec![Content::user("Hello")],
249 turn_complete: Some(true),
250 },
251 };
252 let json = serde_json::to_string(&msg).unwrap();
253 assert!(json.contains("\"clientContent\""));
254 assert!(json.contains("\"turnComplete\""));
255 }
256
257 #[test]
258 fn activity_signal_serialization() {
259 let msg = ActivitySignalMessage {
260 realtime_input: ActivitySignalPayload {
261 activity_start: Some(ActivityStart {}),
262 activity_end: None,
263 },
264 };
265 let json = serde_json::to_string(&msg).unwrap();
266 assert!(json.contains("\"activityStart\""));
267 }
268
269 #[test]
270 fn voice_activity_type_serialization() {
271 let start = VoiceActivityType::VoiceActivityStart;
272 let json = serde_json::to_string(&start).unwrap();
273 assert_eq!(json, "\"ACTIVITY_START\"");
274 let parsed: VoiceActivityType = serde_json::from_str(&json).unwrap();
275 assert_eq!(parsed, start);
276
277 let end = VoiceActivityType::VoiceActivityEnd;
278 let json = serde_json::to_string(&end).unwrap();
279 assert_eq!(json, "\"ACTIVITY_END\"");
280 let parsed: VoiceActivityType = serde_json::from_str(&json).unwrap();
281 assert_eq!(parsed, end);
282 }
283
284 #[test]
285 fn parse_voice_activity_message() {
286 let json = r#"{"voiceActivity":{"voiceActivityType":"VOICE_ACTIVITY_START"}}"#;
287 let msg = ServerMessage::parse(json).unwrap();
288 match msg {
289 ServerMessage::VoiceActivity(va) => {
290 assert_eq!(
291 va.voice_activity.voice_activity_type,
292 Some(VoiceActivityType::VoiceActivityStart)
293 );
294 }
295 _ => panic!("Expected VoiceActivity"),
296 }
297
298 let json = r#"{"voiceActivity":{"voiceActivityType":"VOICE_ACTIVITY_END"}}"#;
299 let msg = ServerMessage::parse(json).unwrap();
300 match msg {
301 ServerMessage::VoiceActivity(va) => {
302 assert_eq!(
303 va.voice_activity.voice_activity_type,
304 Some(VoiceActivityType::VoiceActivityEnd)
305 );
306 }
307 _ => panic!("Expected VoiceActivity"),
308 }
309 }
310
311 #[test]
312 fn parse_input_transcription() {
313 let json = r#"{
314 "serverContent": {
315 "inputTranscription": {"text": "Hello world"}
316 }
317 }"#;
318 let msg = ServerMessage::parse(json).unwrap();
319 match msg {
320 ServerMessage::ServerContent(sc) => {
321 let text = sc.server_content.input_transcription.unwrap().text.unwrap();
322 assert_eq!(text, "Hello world");
323 }
324 _ => panic!("Expected ServerContent"),
325 }
326 }
327}