1use std::time::Duration;
54
55use crate::{Content, InteractionResponse};
56use serde::{Deserialize, Serialize};
57
58#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
73#[non_exhaustive]
74pub struct PendingFunctionCall {
75 pub name: String,
77 pub call_id: String,
79 pub args: serde_json::Value,
81}
82
83impl PendingFunctionCall {
84 #[must_use]
86 pub fn new(
87 name: impl Into<String>,
88 call_id: impl Into<String>,
89 args: serde_json::Value,
90 ) -> Self {
91 Self {
92 name: name.into(),
93 call_id: call_id.into(),
94 args,
95 }
96 }
97}
98
99#[derive(Clone, Debug)]
117#[non_exhaustive]
118pub enum AutoFunctionStreamChunk {
119 Delta(Content),
121
122 ExecutingFunctions {
133 response: InteractionResponse,
135 pending_calls: Vec<PendingFunctionCall>,
137 },
138
139 FunctionResults(Vec<FunctionExecutionResult>),
144
145 Complete(InteractionResponse),
150
151 MaxLoopsReached(InteractionResponse),
164
165 Unknown {
174 chunk_type: String,
176 data: serde_json::Value,
178 },
179}
180
181impl AutoFunctionStreamChunk {
182 #[must_use]
184 pub const fn is_unknown(&self) -> bool {
185 matches!(self, Self::Unknown { .. })
186 }
187
188 #[must_use]
190 pub const fn is_delta(&self) -> bool {
191 matches!(self, Self::Delta(_))
192 }
193
194 #[must_use]
196 pub const fn is_complete(&self) -> bool {
197 matches!(self, Self::Complete(_))
198 }
199
200 #[must_use]
204 pub fn unknown_chunk_type(&self) -> Option<&str> {
205 match self {
206 Self::Unknown { chunk_type, .. } => Some(chunk_type),
207 _ => None,
208 }
209 }
210
211 #[must_use]
215 pub fn unknown_data(&self) -> Option<&serde_json::Value> {
216 match self {
217 Self::Unknown { data, .. } => Some(data),
218 _ => None,
219 }
220 }
221}
222
223impl Serialize for AutoFunctionStreamChunk {
224 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
225 where
226 S: serde::Serializer,
227 {
228 use serde::ser::SerializeMap;
229
230 match self {
231 Self::Delta(content) => {
232 let mut map = serializer.serialize_map(None)?;
233 map.serialize_entry("chunk_type", "delta")?;
234 map.serialize_entry("data", content)?;
235 map.end()
236 }
237 Self::ExecutingFunctions {
238 response,
239 pending_calls,
240 } => {
241 let mut map = serializer.serialize_map(None)?;
242 map.serialize_entry("chunk_type", "executing_functions")?;
243 let data = serde_json::json!({
245 "response": response,
246 "pending_calls": pending_calls,
247 });
248 map.serialize_entry("data", &data)?;
249 map.end()
250 }
251 Self::FunctionResults(results) => {
252 let mut map = serializer.serialize_map(None)?;
253 map.serialize_entry("chunk_type", "function_results")?;
254 map.serialize_entry("data", results)?;
255 map.end()
256 }
257 Self::Complete(response) => {
258 let mut map = serializer.serialize_map(None)?;
259 map.serialize_entry("chunk_type", "complete")?;
260 map.serialize_entry("data", response)?;
261 map.end()
262 }
263 Self::MaxLoopsReached(response) => {
264 let mut map = serializer.serialize_map(None)?;
265 map.serialize_entry("chunk_type", "max_loops_reached")?;
266 map.serialize_entry("data", response)?;
267 map.end()
268 }
269 Self::Unknown { chunk_type, data } => {
270 let mut map = serializer.serialize_map(None)?;
271 map.serialize_entry("chunk_type", chunk_type)?;
272 if !data.is_null() {
273 map.serialize_entry("data", data)?;
274 }
275 map.end()
276 }
277 }
278 }
279}
280
281impl<'de> Deserialize<'de> for AutoFunctionStreamChunk {
282 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
283 where
284 D: serde::Deserializer<'de>,
285 {
286 let value = serde_json::Value::deserialize(deserializer)?;
287
288 let chunk_type = match value.get("chunk_type") {
289 Some(serde_json::Value::String(s)) => s.as_str(),
290 Some(other) => {
291 tracing::warn!(
292 "AutoFunctionStreamChunk received non-string chunk_type: {}. \
293 This may indicate a malformed API response.",
294 other
295 );
296 "<non-string chunk_type>"
297 }
298 None => {
299 tracing::warn!(
300 "AutoFunctionStreamChunk is missing required chunk_type field. \
301 This may indicate a malformed API response."
302 );
303 "<missing chunk_type>"
304 }
305 };
306
307 match chunk_type {
308 "delta" => {
309 let data = match value.get("data").cloned() {
310 Some(d) => d,
311 None => {
312 tracing::warn!(
313 "AutoFunctionStreamChunk::Delta is missing the 'data' field. \
314 This may indicate a malformed API response."
315 );
316 serde_json::Value::Null
317 }
318 };
319 let content: Content = serde_json::from_value(data).map_err(|e| {
320 serde::de::Error::custom(format!(
321 "Failed to deserialize AutoFunctionStreamChunk::Delta data: {}",
322 e
323 ))
324 })?;
325 Ok(Self::Delta(content))
326 }
327 "executing_functions" => {
328 let data = match value.get("data").cloned() {
329 Some(d) => d,
330 None => {
331 tracing::warn!(
332 "AutoFunctionStreamChunk::ExecutingFunctions is missing the 'data' field. \
333 This may indicate a malformed API response."
334 );
335 serde_json::Value::Null
336 }
337 };
338
339 let response = serde_json::from_value(
340 data.get("response")
341 .cloned()
342 .unwrap_or(serde_json::Value::Null),
343 )
344 .map_err(|e| {
345 serde::de::Error::custom(format!(
346 "Failed to deserialize ExecutingFunctions response: {}",
347 e
348 ))
349 })?;
350 let pending_calls = serde_json::from_value(
351 data.get("pending_calls")
352 .cloned()
353 .unwrap_or(serde_json::json!([])),
354 )
355 .map_err(|e| {
356 serde::de::Error::custom(format!(
357 "Failed to deserialize ExecutingFunctions pending_calls: {}",
358 e
359 ))
360 })?;
361
362 Ok(Self::ExecutingFunctions {
363 response,
364 pending_calls,
365 })
366 }
367 "function_results" => {
368 let data = match value.get("data").cloned() {
369 Some(d) => d,
370 None => {
371 tracing::warn!(
372 "AutoFunctionStreamChunk::FunctionResults is missing the 'data' field. \
373 This may indicate a malformed API response."
374 );
375 serde_json::Value::Null
376 }
377 };
378 let results: Vec<FunctionExecutionResult> =
379 serde_json::from_value(data).map_err(|e| {
380 serde::de::Error::custom(format!(
381 "Failed to deserialize AutoFunctionStreamChunk::FunctionResults data: {}",
382 e
383 ))
384 })?;
385 Ok(Self::FunctionResults(results))
386 }
387 "complete" => {
388 let data = match value.get("data").cloned() {
389 Some(d) => d,
390 None => {
391 tracing::warn!(
392 "AutoFunctionStreamChunk::Complete is missing the 'data' field. \
393 This may indicate a malformed API response."
394 );
395 serde_json::Value::Null
396 }
397 };
398 let response: InteractionResponse = serde_json::from_value(data).map_err(|e| {
399 serde::de::Error::custom(format!(
400 "Failed to deserialize AutoFunctionStreamChunk::Complete data: {}",
401 e
402 ))
403 })?;
404 Ok(Self::Complete(response))
405 }
406 "max_loops_reached" => {
407 let data = match value.get("data").cloned() {
408 Some(d) => d,
409 None => {
410 tracing::warn!(
411 "AutoFunctionStreamChunk::MaxLoopsReached is missing the 'data' field. \
412 This may indicate a malformed API response."
413 );
414 serde_json::Value::Null
415 }
416 };
417 let response: InteractionResponse = serde_json::from_value(data).map_err(|e| {
418 serde::de::Error::custom(format!(
419 "Failed to deserialize AutoFunctionStreamChunk::MaxLoopsReached data: {}",
420 e
421 ))
422 })?;
423 Ok(Self::MaxLoopsReached(response))
424 }
425 other => {
426 tracing::warn!(
427 "Encountered unknown AutoFunctionStreamChunk type '{}'. \
428 This may indicate a new API feature. \
429 The chunk will be preserved in the Unknown variant.",
430 other
431 );
432 let data = value
433 .get("data")
434 .cloned()
435 .unwrap_or(serde_json::Value::Null);
436 Ok(Self::Unknown {
437 chunk_type: other.to_string(),
438 data,
439 })
440 }
441 }
442 }
443}
444
445#[derive(Clone, Debug)]
501#[non_exhaustive]
502pub struct AutoFunctionStreamEvent {
503 pub chunk: AutoFunctionStreamChunk,
505 pub event_id: Option<String>,
513}
514
515impl AutoFunctionStreamEvent {
516 #[must_use]
518 pub fn new(chunk: AutoFunctionStreamChunk, event_id: Option<String>) -> Self {
519 Self { chunk, event_id }
520 }
521
522 #[must_use]
524 pub const fn is_delta(&self) -> bool {
525 self.chunk.is_delta()
526 }
527
528 #[must_use]
530 pub const fn is_complete(&self) -> bool {
531 self.chunk.is_complete()
532 }
533
534 #[must_use]
536 pub const fn is_unknown(&self) -> bool {
537 self.chunk.is_unknown()
538 }
539
540 #[must_use]
542 pub fn unknown_chunk_type(&self) -> Option<&str> {
543 self.chunk.unknown_chunk_type()
544 }
545
546 #[must_use]
548 pub fn unknown_data(&self) -> Option<&serde_json::Value> {
549 self.chunk.unknown_data()
550 }
551}
552
553impl Serialize for AutoFunctionStreamEvent {
554 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
555 where
556 S: serde::Serializer,
557 {
558 use serde::ser::SerializeMap;
559
560 let mut map = serializer.serialize_map(None)?;
561 map.serialize_entry("chunk", &self.chunk)?;
562 if let Some(id) = &self.event_id {
563 map.serialize_entry("event_id", id)?;
564 }
565 map.end()
566 }
567}
568
569impl<'de> Deserialize<'de> for AutoFunctionStreamEvent {
570 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
571 where
572 D: serde::Deserializer<'de>,
573 {
574 let value = serde_json::Value::deserialize(deserializer)?;
575
576 let chunk = match value.get("chunk") {
577 Some(chunk_value) => {
578 serde_json::from_value(chunk_value.clone()).map_err(serde::de::Error::custom)?
579 }
580 None => {
581 return Err(serde::de::Error::missing_field("chunk"));
582 }
583 };
584
585 let event_id = value
586 .get("event_id")
587 .and_then(|v| v.as_str())
588 .map(String::from);
589
590 Ok(Self { chunk, event_id })
591 }
592}
593
594#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
611#[non_exhaustive]
612pub struct FunctionExecutionResult {
613 pub name: String,
615 pub call_id: String,
617 pub args: serde_json::Value,
619 pub result: serde_json::Value,
621 #[serde(with = "duration_millis")]
623 pub duration: Duration,
624}
625
626impl FunctionExecutionResult {
627 #[must_use]
629 pub fn new(
630 name: impl Into<String>,
631 call_id: impl Into<String>,
632 args: serde_json::Value,
633 result: serde_json::Value,
634 duration: Duration,
635 ) -> Self {
636 Self {
637 name: name.into(),
638 call_id: call_id.into(),
639 args,
640 result,
641 duration,
642 }
643 }
644
645 #[must_use]
666 pub fn is_error(&self) -> bool {
667 self.result.get("error").is_some()
668 }
669
670 #[must_use]
672 pub fn is_success(&self) -> bool {
673 !self.is_error()
674 }
675
676 #[must_use]
678 pub fn error_message(&self) -> Option<&str> {
679 self.result.get("error").and_then(|v| v.as_str())
680 }
681}
682
683mod duration_millis {
685 use serde::{Deserialize, Deserializer, Serialize, Serializer};
686 use std::time::Duration;
687
688 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
689 where
690 S: Serializer,
691 {
692 duration.as_millis().serialize(serializer)
693 }
694
695 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
696 where
697 D: Deserializer<'de>,
698 {
699 let millis = u64::deserialize(deserializer)?;
700 Ok(Duration::from_millis(millis))
701 }
702}
703
704#[derive(Clone, Debug, Serialize, Deserialize)]
755#[non_exhaustive]
756pub struct AutoFunctionResult {
757 pub response: InteractionResponse,
759 pub executions: Vec<FunctionExecutionResult>,
761 #[serde(default)]
772 pub reached_max_loops: bool,
773}
774
775impl AutoFunctionResult {
776 #[must_use]
796 pub fn all_executions_succeeded(&self) -> bool {
797 self.executions.iter().all(|e| e.is_success())
798 }
799
800 #[must_use]
802 pub fn failed_executions(&self) -> Vec<&FunctionExecutionResult> {
803 self.executions.iter().filter(|e| e.is_error()).collect()
804 }
805}
806
807#[derive(Clone, Debug, Default)]
853pub struct AutoFunctionResultAccumulator {
854 executions: Vec<FunctionExecutionResult>,
855}
856
857impl AutoFunctionResultAccumulator {
858 #[must_use]
860 pub fn new() -> Self {
861 Self::default()
862 }
863
864 #[must_use]
875 #[allow(unreachable_patterns)] pub fn push(&mut self, chunk: AutoFunctionStreamChunk) -> Option<AutoFunctionResult> {
877 match chunk {
878 AutoFunctionStreamChunk::FunctionResults(results) => {
879 self.executions.extend(results);
880 None
881 }
882 AutoFunctionStreamChunk::Complete(response) => Some(AutoFunctionResult {
883 response,
884 executions: std::mem::take(&mut self.executions),
885 reached_max_loops: false,
886 }),
887 AutoFunctionStreamChunk::MaxLoopsReached(response) => Some(AutoFunctionResult {
888 response,
889 executions: std::mem::take(&mut self.executions),
890 reached_max_loops: true,
891 }),
892 AutoFunctionStreamChunk::Delta(_)
893 | AutoFunctionStreamChunk::ExecutingFunctions { .. } => None,
894 _ => None,
896 }
897 }
898
899 #[must_use]
903 pub fn executions(&self) -> &[FunctionExecutionResult] {
904 &self.executions
905 }
906
907 pub fn reset(&mut self) {
909 self.executions.clear();
910 }
911}
912
913#[cfg(test)]
914mod tests {
915 use super::*;
916 use serde_json::json;
917
918 #[test]
919 fn test_function_execution_result() {
920 let result = FunctionExecutionResult::new(
921 "get_weather",
922 "call-123",
923 json!({"city": "Seattle"}),
924 json!({"temp": 20, "unit": "celsius"}),
925 Duration::from_millis(42),
926 );
927
928 assert_eq!(result.name, "get_weather");
929 assert_eq!(result.call_id, "call-123");
930 assert_eq!(result.args, json!({"city": "Seattle"}));
931 assert_eq!(result.result, json!({"temp": 20, "unit": "celsius"}));
932 assert_eq!(result.duration, Duration::from_millis(42));
933 }
934
935 #[test]
936 fn test_auto_function_stream_chunk_variants() {
937 let _delta = AutoFunctionStreamChunk::Delta(Content::Text {
939 text: Some("Hello".to_string()),
940 annotations: None,
941 });
942
943 let _results = AutoFunctionStreamChunk::FunctionResults(vec![FunctionExecutionResult {
944 name: "test".to_string(),
945 call_id: "1".to_string(),
946 args: json!({}),
947 result: json!({"ok": true}),
948 duration: Duration::from_millis(10),
949 }]);
950
951 }
953
954 #[test]
955 fn test_function_execution_result_serialization() {
956 let result = FunctionExecutionResult::new(
957 "get_weather",
958 "call-456",
959 json!({"city": "Miami"}),
960 json!({"temp": 22, "conditions": "sunny"}),
961 Duration::from_millis(150),
962 );
963
964 let json_str = serde_json::to_string(&result).expect("Serialization should succeed");
965
966 assert!(
968 json_str.contains("get_weather"),
969 "Should contain function name"
970 );
971 assert!(json_str.contains("call-456"), "Should contain call_id");
972 assert!(json_str.contains("sunny"), "Should contain result data");
973
974 let deserialized: FunctionExecutionResult =
976 serde_json::from_str(&json_str).expect("Deserialization should succeed");
977 assert_eq!(deserialized, result);
978 }
979
980 #[test]
981 fn test_auto_function_stream_chunk_serialization_roundtrip() {
982 let delta = AutoFunctionStreamChunk::Delta(Content::Text {
984 text: Some("Hello, world!".to_string()),
985 annotations: None,
986 });
987
988 let json_str = serde_json::to_string(&delta).expect("Serialization should succeed");
989 assert!(json_str.contains("chunk_type"), "Should contain tag field");
990 assert!(json_str.contains("delta"), "Should contain variant name");
991 assert!(json_str.contains("Hello, world!"), "Should contain text");
992
993 let deserialized: AutoFunctionStreamChunk =
994 serde_json::from_str(&json_str).expect("Deserialization should succeed");
995
996 match deserialized {
997 AutoFunctionStreamChunk::Delta(content) => {
998 assert_eq!(content.as_text(), Some("Hello, world!"));
999 }
1000 _ => panic!("Expected Delta variant"),
1001 }
1002
1003 let results = AutoFunctionStreamChunk::FunctionResults(vec![
1005 FunctionExecutionResult::new(
1006 "get_weather",
1007 "call-1",
1008 json!({"city": "Tokyo"}),
1009 json!({"temp": 20}),
1010 Duration::from_millis(50),
1011 ),
1012 FunctionExecutionResult::new(
1013 "get_time",
1014 "call-2",
1015 json!({"timezone": "UTC"}),
1016 json!({"time": "14:30"}),
1017 Duration::from_millis(30),
1018 ),
1019 ]);
1020
1021 let json_str = serde_json::to_string(&results).expect("Serialization should succeed");
1022 let deserialized: AutoFunctionStreamChunk =
1023 serde_json::from_str(&json_str).expect("Deserialization should succeed");
1024
1025 match deserialized {
1026 AutoFunctionStreamChunk::FunctionResults(execs) => {
1027 assert_eq!(execs.len(), 2);
1028 assert_eq!(execs[0].name, "get_weather");
1029 assert_eq!(execs[1].name, "get_time");
1030 }
1031 _ => panic!("Expected FunctionResults variant"),
1032 }
1033
1034 let unknown_json = r#"{"chunk_type": "future_event_type", "data": {"key": "value"}}"#;
1036 let deserialized: AutoFunctionStreamChunk =
1037 serde_json::from_str(unknown_json).expect("Should deserialize unknown variant");
1038
1039 assert!(deserialized.is_unknown());
1041 assert_eq!(deserialized.unknown_chunk_type(), Some("future_event_type"));
1042 let data = deserialized.unknown_data().expect("Should have data");
1043 assert_eq!(data["key"], "value");
1044
1045 let reserialized = serde_json::to_string(&deserialized).expect("Should serialize");
1047 assert!(reserialized.contains("future_event_type"));
1048 assert!(reserialized.contains("value"));
1049 }
1050
1051 #[test]
1052 fn test_auto_function_stream_chunk_unknown_without_data() {
1053 let unknown_json = r#"{"chunk_type": "no_data_chunk"}"#;
1055 let deserialized: AutoFunctionStreamChunk =
1056 serde_json::from_str(unknown_json).expect("Should deserialize unknown variant");
1057
1058 assert!(deserialized.is_unknown());
1059 assert_eq!(deserialized.unknown_chunk_type(), Some("no_data_chunk"));
1060
1061 let data = deserialized.unknown_data().expect("Should have data field");
1063 assert!(data.is_null());
1064 }
1065
1066 #[test]
1067 fn test_auto_function_result_roundtrip() {
1068 use crate::InteractionStatus;
1069
1070 let result = AutoFunctionResult {
1072 response: crate::InteractionResponse {
1073 id: Some("interaction-abc123".to_string()),
1074 model: Some("gemini-3-flash-preview".to_string()),
1075 agent: None,
1076 input: vec![Content::Text {
1077 text: Some("What's the weather in Paris and London?".to_string()),
1078 annotations: None,
1079 }],
1080 outputs: vec![
1081 Content::Text {
1082 text: Some("Based on the weather data:".to_string()),
1083 annotations: None,
1084 },
1085 Content::Text {
1086 text: Some("Paris is 18°C and London is 15°C.".to_string()),
1087 annotations: None,
1088 },
1089 ],
1090 status: InteractionStatus::Completed,
1091 usage: Some(crate::UsageMetadata {
1092 total_input_tokens: Some(50),
1093 total_output_tokens: Some(30),
1094 total_tokens: Some(80),
1095 ..Default::default()
1096 }),
1097 tools: None,
1098 grounding_metadata: None,
1099 url_context_metadata: None,
1100 previous_interaction_id: Some("prev-interaction-xyz".to_string()),
1101 created: None,
1102 updated: None,
1103 },
1104 executions: vec![
1105 FunctionExecutionResult::new(
1106 "get_weather",
1107 "call-001",
1108 json!({"city": "Paris"}),
1109 json!({"city": "Paris", "temp": 18, "unit": "celsius"}),
1110 Duration::from_millis(120),
1111 ),
1112 FunctionExecutionResult::new(
1113 "get_weather",
1114 "call-002",
1115 json!({"city": "London"}),
1116 json!({"city": "London", "temp": 15, "unit": "celsius"}),
1117 Duration::from_millis(95),
1118 ),
1119 ],
1120 reached_max_loops: false,
1121 };
1122
1123 let json_str = serde_json::to_string(&result).expect("Serialization should succeed");
1125
1126 assert!(
1128 json_str.contains("interaction-abc123"),
1129 "Should contain interaction ID"
1130 );
1131 assert!(
1132 json_str.contains("gemini-3-flash-preview"),
1133 "Should contain model name"
1134 );
1135 assert!(
1136 json_str.contains("get_weather"),
1137 "Should contain function name"
1138 );
1139 assert!(
1140 json_str.contains("call-001"),
1141 "Should contain first call_id"
1142 );
1143 assert!(
1144 json_str.contains("call-002"),
1145 "Should contain second call_id"
1146 );
1147 assert!(json_str.contains("Paris"), "Should contain Paris");
1148 assert!(json_str.contains("London"), "Should contain London");
1149 assert!(
1150 json_str.contains("prev-interaction-xyz"),
1151 "Should contain previous interaction ID"
1152 );
1153
1154 let deserialized: AutoFunctionResult =
1156 serde_json::from_str(&json_str).expect("Deserialization should succeed");
1157
1158 assert_eq!(
1160 deserialized.response.id.as_deref(),
1161 Some("interaction-abc123")
1162 );
1163 assert_eq!(
1164 deserialized.response.model,
1165 Some("gemini-3-flash-preview".to_string())
1166 );
1167 assert_eq!(deserialized.response.status, InteractionStatus::Completed);
1168 assert_eq!(
1169 deserialized.response.previous_interaction_id,
1170 Some("prev-interaction-xyz".to_string())
1171 );
1172
1173 let usage = deserialized.response.usage.expect("Should have usage");
1175 assert_eq!(usage.total_input_tokens, Some(50));
1176 assert_eq!(usage.total_output_tokens, Some(30));
1177 assert_eq!(usage.total_tokens, Some(80));
1178
1179 assert_eq!(deserialized.executions.len(), 2);
1181 assert_eq!(deserialized.executions[0].name, "get_weather");
1182 assert_eq!(deserialized.executions[0].call_id, "call-001");
1183 assert_eq!(deserialized.executions[0].result["city"], "Paris");
1184 assert_eq!(deserialized.executions[1].name, "get_weather");
1185 assert_eq!(deserialized.executions[1].call_id, "call-002");
1186 assert_eq!(deserialized.executions[1].result["city"], "London");
1187
1188 assert!(!deserialized.reached_max_loops);
1190 }
1191
1192 #[test]
1193 fn test_auto_function_result_reached_max_loops() {
1194 use crate::InteractionStatus;
1195
1196 let result = AutoFunctionResult {
1198 response: crate::InteractionResponse {
1199 id: Some("interaction-stuck".to_string()),
1200 model: Some("gemini-3-flash-preview".to_string()),
1201 agent: None,
1202 input: vec![Content::Text {
1203 text: Some("What's the weather?".to_string()),
1204 annotations: None,
1205 }],
1206 outputs: vec![Content::FunctionCall {
1207 id: Some("call-stuck".to_string()),
1208 name: "get_weather".to_string(),
1209 args: json!({"city": "Tokyo"}),
1210 }],
1211 status: InteractionStatus::Completed,
1212 usage: None,
1213 tools: None,
1214 grounding_metadata: None,
1215 url_context_metadata: None,
1216 previous_interaction_id: None,
1217 created: None,
1218 updated: None,
1219 },
1220 executions: vec![FunctionExecutionResult::new(
1221 "get_weather",
1222 "call-1",
1223 json!({"city": "Berlin"}),
1224 json!({"temp": 25}),
1225 Duration::from_millis(50),
1226 )],
1227 reached_max_loops: true,
1228 };
1229
1230 let json_str = serde_json::to_string(&result).expect("Serialization should succeed");
1232 assert!(
1233 json_str.contains("reached_max_loops"),
1234 "Should contain reached_max_loops field"
1235 );
1236 assert!(json_str.contains("true"), "Should contain true value");
1237
1238 let deserialized: AutoFunctionResult =
1240 serde_json::from_str(&json_str).expect("Deserialization should succeed");
1241 assert!(deserialized.reached_max_loops);
1242 assert_eq!(deserialized.executions.len(), 1);
1243 }
1244
1245 #[test]
1246 fn test_auto_function_result_backwards_compatibility() {
1247 let legacy_json = r#"{
1249 "response": {
1250 "id": "interaction-old",
1251 "model": "gemini-3-flash-preview",
1252 "agent": null,
1253 "input": [],
1254 "outputs": [],
1255 "status": "COMPLETED",
1256 "usage": null,
1257 "tools": null,
1258 "grounding_metadata": null,
1259 "url_context_metadata": null,
1260 "previous_interaction_id": null
1261 },
1262 "executions": []
1263 }"#;
1264
1265 let deserialized: AutoFunctionResult =
1266 serde_json::from_str(legacy_json).expect("Should deserialize legacy JSON");
1267
1268 assert!(
1270 !deserialized.reached_max_loops,
1271 "Missing field should default to false"
1272 );
1273 }
1274
1275 #[test]
1276 fn test_max_loops_reached_chunk_roundtrip() {
1277 use crate::InteractionStatus;
1278
1279 let response = crate::InteractionResponse {
1281 id: Some("interaction-max-loops".to_string()),
1282 model: Some("gemini-3-flash-preview".to_string()),
1283 agent: None,
1284 input: vec![],
1285 outputs: vec![Content::FunctionCall {
1286 id: Some("call-pending".to_string()),
1287 name: "stuck_function".to_string(),
1288 args: json!({}),
1289 }],
1290 status: InteractionStatus::Completed,
1291 usage: None,
1292 tools: None,
1293 grounding_metadata: None,
1294 url_context_metadata: None,
1295 previous_interaction_id: None,
1296 created: None,
1297 updated: None,
1298 };
1299
1300 let chunk = AutoFunctionStreamChunk::MaxLoopsReached(response);
1301
1302 let json_str = serde_json::to_string(&chunk).expect("Serialization should succeed");
1304 assert!(
1305 json_str.contains("max_loops_reached"),
1306 "Should contain chunk_type"
1307 );
1308 assert!(
1309 json_str.contains("interaction-max-loops"),
1310 "Should contain response data"
1311 );
1312
1313 let deserialized: AutoFunctionStreamChunk =
1315 serde_json::from_str(&json_str).expect("Deserialization should succeed");
1316
1317 match deserialized {
1318 AutoFunctionStreamChunk::MaxLoopsReached(resp) => {
1319 assert_eq!(resp.id.as_deref(), Some("interaction-max-loops"));
1320 assert_eq!(resp.function_calls().len(), 1);
1321 assert_eq!(resp.function_calls()[0].name, "stuck_function");
1322 }
1323 other => panic!("Expected MaxLoopsReached, got {:?}", other),
1324 }
1325 }
1326
1327 #[test]
1328 fn test_accumulator_handles_max_loops_reached() {
1329 use crate::InteractionStatus;
1330
1331 let mut accumulator = AutoFunctionResultAccumulator::new();
1332
1333 let results = AutoFunctionStreamChunk::FunctionResults(vec![FunctionExecutionResult::new(
1335 "test_func",
1336 "call-1",
1337 json!({}),
1338 json!({"ok": true}),
1339 Duration::from_millis(10),
1340 )]);
1341
1342 assert!(
1343 accumulator.push(results).is_none(),
1344 "Should not complete yet"
1345 );
1346 assert_eq!(accumulator.executions().len(), 1);
1347
1348 let response = crate::InteractionResponse {
1350 id: Some("max-loops-response".to_string()),
1351 model: Some("gemini-3-flash-preview".to_string()),
1352 agent: None,
1353 input: vec![],
1354 outputs: vec![],
1355 status: InteractionStatus::Completed,
1356 usage: None,
1357 tools: None,
1358 grounding_metadata: None,
1359 url_context_metadata: None,
1360 previous_interaction_id: None,
1361 created: None,
1362 updated: None,
1363 };
1364
1365 let max_loops_chunk = AutoFunctionStreamChunk::MaxLoopsReached(response);
1366 let result = accumulator.push(max_loops_chunk);
1367
1368 assert!(result.is_some(), "Should complete on MaxLoopsReached");
1369 let result = result.unwrap();
1370 assert!(
1371 result.reached_max_loops,
1372 "Should have reached_max_loops: true"
1373 );
1374 assert_eq!(result.executions.len(), 1);
1375 assert_eq!(result.response.id.as_deref(), Some("max-loops-response"));
1376 }
1377
1378 #[test]
1379 fn test_auto_function_stream_event_with_event_id_roundtrip() {
1380 let event = AutoFunctionStreamEvent::new(
1381 AutoFunctionStreamChunk::Delta(Content::Text {
1382 text: Some("Hello from auto-function".to_string()),
1383 annotations: None,
1384 }),
1385 Some("evt_auto_abc123".to_string()),
1386 );
1387
1388 assert!(event.is_delta());
1390 assert!(!event.is_complete());
1391 assert!(!event.is_unknown());
1392
1393 let json = serde_json::to_string(&event).expect("Serialization should succeed");
1394 assert!(json.contains("evt_auto_abc123"), "Should have event_id");
1395 assert!(
1396 json.contains("Hello from auto-function"),
1397 "Should have content"
1398 );
1399
1400 let deserialized: AutoFunctionStreamEvent =
1401 serde_json::from_str(&json).expect("Deserialization should succeed");
1402 assert_eq!(deserialized.event_id.as_deref(), Some("evt_auto_abc123"));
1403 assert!(deserialized.is_delta());
1404 }
1405
1406 #[test]
1407 fn test_auto_function_stream_event_without_event_id() {
1408 let event = AutoFunctionStreamEvent::new(
1410 AutoFunctionStreamChunk::FunctionResults(vec![FunctionExecutionResult::new(
1411 "weather",
1412 "call-123",
1413 json!({"city": "Denver"}),
1414 json!({"temp": 72}),
1415 Duration::from_millis(50),
1416 )]),
1417 None,
1418 );
1419
1420 assert!(!event.is_delta());
1421 assert!(!event.is_complete());
1422 assert!(event.event_id.is_none());
1423
1424 let json = serde_json::to_string(&event).expect("Serialization should succeed");
1425 assert!(!json.contains("event_id"), "Should not have event_id field");
1426 assert!(json.contains("weather"), "Should have function name");
1427
1428 let deserialized: AutoFunctionStreamEvent =
1429 serde_json::from_str(&json).expect("Deserialization should succeed");
1430 assert!(deserialized.event_id.is_none());
1431 }
1432
1433 #[test]
1434 fn test_auto_function_stream_event_with_empty_event_id() {
1435 let event = AutoFunctionStreamEvent::new(
1437 AutoFunctionStreamChunk::Delta(Content::Text {
1438 text: Some("Test".to_string()),
1439 annotations: None,
1440 }),
1441 Some(String::new()),
1442 );
1443
1444 let json = serde_json::to_string(&event).expect("Serialization should succeed");
1445 assert!(
1446 json.contains(r#""event_id":"""#),
1447 "Should have empty event_id"
1448 );
1449
1450 let deserialized: AutoFunctionStreamEvent =
1451 serde_json::from_str(&json).expect("Deserialization should succeed");
1452 assert_eq!(deserialized.event_id.as_deref(), Some(""));
1453 }
1454
1455 #[test]
1456 fn test_pending_function_call() {
1457 let call = PendingFunctionCall::new("get_weather", "call-123", json!({"city": "Seattle"}));
1458
1459 assert_eq!(call.name, "get_weather");
1460 assert_eq!(call.call_id, "call-123");
1461 assert_eq!(call.args, json!({"city": "Seattle"}));
1462 }
1463
1464 #[test]
1465 fn test_pending_function_call_serialization_roundtrip() {
1466 let call = PendingFunctionCall::new("test_func", "id-456", json!({"key": "value"}));
1467
1468 let json_str = serde_json::to_string(&call).expect("Serialization should succeed");
1469 assert!(json_str.contains("test_func"));
1470 assert!(json_str.contains("id-456"));
1471
1472 let deserialized: PendingFunctionCall =
1473 serde_json::from_str(&json_str).expect("Deserialization should succeed");
1474 assert_eq!(deserialized, call);
1475 }
1476
1477 #[test]
1478 fn test_executing_functions_new_format_roundtrip() {
1479 use crate::InteractionStatus;
1480
1481 let chunk = AutoFunctionStreamChunk::ExecutingFunctions {
1482 response: crate::InteractionResponse {
1483 id: Some("interaction-new".to_string()),
1484 model: Some("gemini-3-flash-preview".to_string()),
1485 agent: None,
1486 input: vec![],
1487 outputs: vec![],
1488 status: InteractionStatus::Completed,
1489 usage: None,
1490 tools: None,
1491 grounding_metadata: None,
1492 url_context_metadata: None,
1493 previous_interaction_id: None,
1494 created: None,
1495 updated: None,
1496 },
1497 pending_calls: vec![
1498 PendingFunctionCall::new("func1", "call-1", json!({"a": 1})),
1499 PendingFunctionCall::new("func2", "call-2", json!({"b": 2})),
1500 ],
1501 };
1502
1503 let json_str = serde_json::to_string(&chunk).expect("Serialization should succeed");
1504 assert!(json_str.contains("pending_calls"));
1505 assert!(json_str.contains("func1"));
1506 assert!(json_str.contains("func2"));
1507
1508 let deserialized: AutoFunctionStreamChunk =
1509 serde_json::from_str(&json_str).expect("Deserialization should succeed");
1510
1511 match deserialized {
1512 AutoFunctionStreamChunk::ExecutingFunctions {
1513 response,
1514 pending_calls,
1515 } => {
1516 assert_eq!(response.id.as_deref(), Some("interaction-new"));
1517 assert_eq!(pending_calls.len(), 2);
1518 assert_eq!(pending_calls[0].name, "func1");
1519 assert_eq!(pending_calls[1].name, "func2");
1520 }
1521 _ => panic!("Expected ExecutingFunctions variant"),
1522 }
1523 }
1524}