1use std::time::Duration;
54
55use crate::{InteractionResponse, StepDelta};
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(StepDelta),
123
124 ExecutingFunctions {
135 response: InteractionResponse,
137 pending_calls: Vec<PendingFunctionCall>,
139 },
140
141 FunctionResults(Vec<FunctionExecutionResult>),
146
147 Complete(InteractionResponse),
152
153 MaxLoopsReached(InteractionResponse),
166
167 Unknown {
176 chunk_type: String,
178 data: serde_json::Value,
180 },
181}
182
183impl AutoFunctionStreamChunk {
184 #[must_use]
186 pub const fn is_unknown(&self) -> bool {
187 matches!(self, Self::Unknown { .. })
188 }
189
190 #[must_use]
192 pub const fn is_delta(&self) -> bool {
193 matches!(self, Self::Delta(_))
194 }
195
196 #[must_use]
198 pub const fn is_complete(&self) -> bool {
199 matches!(self, Self::Complete(_))
200 }
201
202 #[must_use]
206 pub fn unknown_chunk_type(&self) -> Option<&str> {
207 match self {
208 Self::Unknown { chunk_type, .. } => Some(chunk_type),
209 _ => None,
210 }
211 }
212
213 #[must_use]
217 pub fn unknown_data(&self) -> Option<&serde_json::Value> {
218 match self {
219 Self::Unknown { data, .. } => Some(data),
220 _ => None,
221 }
222 }
223}
224
225impl Serialize for AutoFunctionStreamChunk {
226 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
227 where
228 S: serde::Serializer,
229 {
230 use serde::ser::SerializeMap;
231
232 match self {
233 Self::Delta(content) => {
234 let mut map = serializer.serialize_map(None)?;
235 map.serialize_entry("chunk_type", "delta")?;
236 map.serialize_entry("data", content)?;
237 map.end()
238 }
239 Self::ExecutingFunctions {
240 response,
241 pending_calls,
242 } => {
243 let mut map = serializer.serialize_map(None)?;
244 map.serialize_entry("chunk_type", "executing_functions")?;
245 let data = serde_json::json!({
247 "response": response,
248 "pending_calls": pending_calls,
249 });
250 map.serialize_entry("data", &data)?;
251 map.end()
252 }
253 Self::FunctionResults(results) => {
254 let mut map = serializer.serialize_map(None)?;
255 map.serialize_entry("chunk_type", "function_results")?;
256 map.serialize_entry("data", results)?;
257 map.end()
258 }
259 Self::Complete(response) => {
260 let mut map = serializer.serialize_map(None)?;
261 map.serialize_entry("chunk_type", "complete")?;
262 map.serialize_entry("data", response)?;
263 map.end()
264 }
265 Self::MaxLoopsReached(response) => {
266 let mut map = serializer.serialize_map(None)?;
267 map.serialize_entry("chunk_type", "max_loops_reached")?;
268 map.serialize_entry("data", response)?;
269 map.end()
270 }
271 Self::Unknown { chunk_type, data } => {
272 let mut map = serializer.serialize_map(None)?;
273 map.serialize_entry("chunk_type", chunk_type)?;
274 if !data.is_null() {
275 map.serialize_entry("data", data)?;
276 }
277 map.end()
278 }
279 }
280 }
281}
282
283impl<'de> Deserialize<'de> for AutoFunctionStreamChunk {
284 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
285 where
286 D: serde::Deserializer<'de>,
287 {
288 let value = serde_json::Value::deserialize(deserializer)?;
289
290 fn extract_data_field(value: &serde_json::Value, variant_name: &str) -> serde_json::Value {
292 match value.get("data").cloned() {
293 Some(d) => d,
294 None => {
295 tracing::warn!(
296 "AutoFunctionStreamChunk::{} is missing the 'data' field. \
297 This may indicate a malformed API response.",
298 variant_name
299 );
300 serde_json::Value::Null
301 }
302 }
303 }
304
305 let chunk_type = match value.get("chunk_type") {
306 Some(serde_json::Value::String(s)) => s.as_str(),
307 Some(other) => {
308 tracing::warn!(
309 "AutoFunctionStreamChunk received non-string chunk_type: {}. \
310 This may indicate a malformed API response.",
311 other
312 );
313 "<non-string chunk_type>"
314 }
315 None => {
316 tracing::warn!(
317 "AutoFunctionStreamChunk is missing required chunk_type field. \
318 This may indicate a malformed API response."
319 );
320 "<missing chunk_type>"
321 }
322 };
323
324 match chunk_type {
325 "delta" => {
326 let data = extract_data_field(&value, "Delta");
327 let delta: StepDelta = serde_json::from_value(data).map_err(|e| {
328 serde::de::Error::custom(format!(
329 "Failed to deserialize AutoFunctionStreamChunk::Delta data: {}",
330 e
331 ))
332 })?;
333 Ok(Self::Delta(delta))
334 }
335 "executing_functions" => {
336 let data = extract_data_field(&value, "ExecutingFunctions");
337
338 let response = serde_json::from_value(
339 data.get("response")
340 .cloned()
341 .unwrap_or(serde_json::Value::Null),
342 )
343 .map_err(|e| {
344 serde::de::Error::custom(format!(
345 "Failed to deserialize ExecutingFunctions response: {}",
346 e
347 ))
348 })?;
349 let pending_calls = serde_json::from_value(
350 data.get("pending_calls")
351 .cloned()
352 .unwrap_or(serde_json::json!([])),
353 )
354 .map_err(|e| {
355 serde::de::Error::custom(format!(
356 "Failed to deserialize ExecutingFunctions pending_calls: {}",
357 e
358 ))
359 })?;
360
361 Ok(Self::ExecutingFunctions {
362 response,
363 pending_calls,
364 })
365 }
366 "function_results" => {
367 let data = extract_data_field(&value, "FunctionResults");
368 let results: Vec<FunctionExecutionResult> =
369 serde_json::from_value(data).map_err(|e| {
370 serde::de::Error::custom(format!(
371 "Failed to deserialize AutoFunctionStreamChunk::FunctionResults data: {}",
372 e
373 ))
374 })?;
375 Ok(Self::FunctionResults(results))
376 }
377 "complete" => {
378 let data = extract_data_field(&value, "Complete");
379 let response: InteractionResponse = serde_json::from_value(data).map_err(|e| {
380 serde::de::Error::custom(format!(
381 "Failed to deserialize AutoFunctionStreamChunk::Complete data: {}",
382 e
383 ))
384 })?;
385 Ok(Self::Complete(response))
386 }
387 "max_loops_reached" => {
388 let data = extract_data_field(&value, "MaxLoopsReached");
389 let response: InteractionResponse = serde_json::from_value(data).map_err(|e| {
390 serde::de::Error::custom(format!(
391 "Failed to deserialize AutoFunctionStreamChunk::MaxLoopsReached data: {}",
392 e
393 ))
394 })?;
395 Ok(Self::MaxLoopsReached(response))
396 }
397 other => {
398 tracing::warn!(
399 "Encountered unknown AutoFunctionStreamChunk type '{}'. \
400 This may indicate a new API feature. \
401 The chunk will be preserved in the Unknown variant.",
402 other
403 );
404 let data = value
405 .get("data")
406 .cloned()
407 .unwrap_or(serde_json::Value::Null);
408 Ok(Self::Unknown {
409 chunk_type: other.to_string(),
410 data,
411 })
412 }
413 }
414 }
415}
416
417#[derive(Clone, Debug)]
473#[non_exhaustive]
474pub struct AutoFunctionStreamEvent {
475 pub chunk: AutoFunctionStreamChunk,
477 pub event_id: Option<String>,
485}
486
487impl AutoFunctionStreamEvent {
488 #[must_use]
490 pub fn new(chunk: AutoFunctionStreamChunk, event_id: Option<String>) -> Self {
491 Self { chunk, event_id }
492 }
493
494 #[must_use]
496 pub const fn is_delta(&self) -> bool {
497 self.chunk.is_delta()
498 }
499
500 #[must_use]
502 pub const fn is_complete(&self) -> bool {
503 self.chunk.is_complete()
504 }
505
506 #[must_use]
508 pub const fn is_unknown(&self) -> bool {
509 self.chunk.is_unknown()
510 }
511
512 #[must_use]
514 pub fn unknown_chunk_type(&self) -> Option<&str> {
515 self.chunk.unknown_chunk_type()
516 }
517
518 #[must_use]
520 pub fn unknown_data(&self) -> Option<&serde_json::Value> {
521 self.chunk.unknown_data()
522 }
523}
524
525impl Serialize for AutoFunctionStreamEvent {
526 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
527 where
528 S: serde::Serializer,
529 {
530 use serde::ser::SerializeMap;
531
532 let mut map = serializer.serialize_map(None)?;
533 map.serialize_entry("chunk", &self.chunk)?;
534 if let Some(id) = &self.event_id {
535 map.serialize_entry("event_id", id)?;
536 }
537 map.end()
538 }
539}
540
541impl<'de> Deserialize<'de> for AutoFunctionStreamEvent {
542 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
543 where
544 D: serde::Deserializer<'de>,
545 {
546 let value = serde_json::Value::deserialize(deserializer)?;
547
548 let chunk = match value.get("chunk") {
549 Some(chunk_value) => {
550 serde_json::from_value(chunk_value.clone()).map_err(serde::de::Error::custom)?
551 }
552 None => {
553 return Err(serde::de::Error::missing_field("chunk"));
554 }
555 };
556
557 let event_id = value
558 .get("event_id")
559 .and_then(|v| v.as_str())
560 .map(String::from);
561
562 Ok(Self { chunk, event_id })
563 }
564}
565
566#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
583#[non_exhaustive]
584pub struct FunctionExecutionResult {
585 pub name: String,
587 pub call_id: String,
589 pub args: serde_json::Value,
591 pub result: serde_json::Value,
593 #[serde(with = "duration_millis")]
595 pub duration: Duration,
596}
597
598impl FunctionExecutionResult {
599 #[must_use]
601 pub fn new(
602 name: impl Into<String>,
603 call_id: impl Into<String>,
604 args: serde_json::Value,
605 result: serde_json::Value,
606 duration: Duration,
607 ) -> Self {
608 Self {
609 name: name.into(),
610 call_id: call_id.into(),
611 args,
612 result,
613 duration,
614 }
615 }
616
617 #[must_use]
638 pub fn is_error(&self) -> bool {
639 self.result.get("error").is_some()
640 }
641
642 #[must_use]
644 pub fn is_success(&self) -> bool {
645 !self.is_error()
646 }
647
648 #[must_use]
650 pub fn error_message(&self) -> Option<&str> {
651 self.result.get("error").and_then(|v| v.as_str())
652 }
653}
654
655mod duration_millis {
657 use serde::{Deserialize, Deserializer, Serialize, Serializer};
658 use std::time::Duration;
659
660 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
661 where
662 S: Serializer,
663 {
664 duration.as_millis().serialize(serializer)
665 }
666
667 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
668 where
669 D: Deserializer<'de>,
670 {
671 let millis = u64::deserialize(deserializer)?;
672 Ok(Duration::from_millis(millis))
673 }
674}
675
676#[derive(Clone, Debug, Serialize, Deserialize)]
727#[non_exhaustive]
728pub struct AutoFunctionResult {
729 pub response: InteractionResponse,
731 pub executions: Vec<FunctionExecutionResult>,
733 #[serde(default)]
744 pub reached_max_loops: bool,
745}
746
747impl AutoFunctionResult {
748 #[must_use]
768 pub fn all_executions_succeeded(&self) -> bool {
769 self.executions.iter().all(|e| e.is_success())
770 }
771
772 #[must_use]
774 pub fn failed_executions(&self) -> Vec<&FunctionExecutionResult> {
775 self.executions.iter().filter(|e| e.is_error()).collect()
776 }
777}
778
779#[derive(Clone, Debug, Default)]
825pub struct AutoFunctionResultAccumulator {
826 executions: Vec<FunctionExecutionResult>,
827}
828
829impl AutoFunctionResultAccumulator {
830 #[must_use]
832 pub fn new() -> Self {
833 Self::default()
834 }
835
836 #[must_use]
847 #[allow(unreachable_patterns)] pub fn push(&mut self, chunk: AutoFunctionStreamChunk) -> Option<AutoFunctionResult> {
849 match chunk {
850 AutoFunctionStreamChunk::FunctionResults(results) => {
851 self.executions.extend(results);
852 None
853 }
854 AutoFunctionStreamChunk::Complete(response) => Some(AutoFunctionResult {
855 response,
856 executions: std::mem::take(&mut self.executions),
857 reached_max_loops: false,
858 }),
859 AutoFunctionStreamChunk::MaxLoopsReached(response) => Some(AutoFunctionResult {
860 response,
861 executions: std::mem::take(&mut self.executions),
862 reached_max_loops: true,
863 }),
864 AutoFunctionStreamChunk::Delta(_)
865 | AutoFunctionStreamChunk::ExecutingFunctions { .. } => None,
866 _ => None,
868 }
869 }
870
871 #[must_use]
875 pub fn executions(&self) -> &[FunctionExecutionResult] {
876 &self.executions
877 }
878
879 pub fn reset(&mut self) {
881 self.executions.clear();
882 }
883}
884
885#[cfg(test)]
886mod tests {
887 use super::*;
888 use serde_json::json;
889
890 #[test]
891 fn test_function_execution_result() {
892 let result = FunctionExecutionResult::new(
893 "get_weather",
894 "call-123",
895 json!({"city": "Seattle"}),
896 json!({"temp": 20, "unit": "celsius"}),
897 Duration::from_millis(42),
898 );
899
900 assert_eq!(result.name, "get_weather");
901 assert_eq!(result.call_id, "call-123");
902 assert_eq!(result.args, json!({"city": "Seattle"}));
903 assert_eq!(result.result, json!({"temp": 20, "unit": "celsius"}));
904 assert_eq!(result.duration, Duration::from_millis(42));
905 }
906
907 #[test]
908 fn test_auto_function_stream_chunk_variants() {
909 let _delta = AutoFunctionStreamChunk::Delta(StepDelta::Text {
911 text: "Hello".to_string(),
912 });
913
914 let _results = AutoFunctionStreamChunk::FunctionResults(vec![FunctionExecutionResult {
915 name: "test".to_string(),
916 call_id: "1".to_string(),
917 args: json!({}),
918 result: json!({"ok": true}),
919 duration: Duration::from_millis(10),
920 }]);
921
922 let _complete = AutoFunctionStreamChunk::Complete(InteractionResponse::default());
923 }
924
925 #[test]
926 fn test_function_execution_result_serialization() {
927 let result = FunctionExecutionResult::new(
928 "get_weather",
929 "call-456",
930 json!({"city": "Miami"}),
931 json!({"temp": 22, "conditions": "sunny"}),
932 Duration::from_millis(150),
933 );
934
935 let json_str = serde_json::to_string(&result).expect("Serialization should succeed");
936
937 assert!(
939 json_str.contains("get_weather"),
940 "Should contain function name"
941 );
942 assert!(json_str.contains("call-456"), "Should contain call_id");
943 assert!(json_str.contains("sunny"), "Should contain result data");
944
945 let deserialized: FunctionExecutionResult =
947 serde_json::from_str(&json_str).expect("Deserialization should succeed");
948 assert_eq!(deserialized, result);
949 }
950
951 #[test]
952 fn test_auto_function_stream_chunk_serialization_roundtrip() {
953 let delta = AutoFunctionStreamChunk::Delta(StepDelta::Text {
955 text: "Hello, world!".to_string(),
956 });
957
958 let json_str = serde_json::to_string(&delta).expect("Serialization should succeed");
959 assert!(json_str.contains("chunk_type"), "Should contain tag field");
960 assert!(json_str.contains("delta"), "Should contain variant name");
961 assert!(json_str.contains("Hello, world!"), "Should contain text");
962
963 let value: serde_json::Value = serde_json::from_str(&json_str).unwrap();
965 assert_eq!(value["data"]["type"], "text");
966 assert_eq!(value["data"]["text"], "Hello, world!");
967
968 let deserialized: AutoFunctionStreamChunk =
969 serde_json::from_str(&json_str).expect("Deserialization should succeed");
970
971 match deserialized {
972 AutoFunctionStreamChunk::Delta(delta) => {
973 assert_eq!(delta.as_text(), Some("Hello, world!"));
974 }
975 _ => panic!("Expected Delta variant"),
976 }
977
978 let results = AutoFunctionStreamChunk::FunctionResults(vec![
980 FunctionExecutionResult::new(
981 "get_weather",
982 "call-1",
983 json!({"city": "Tokyo"}),
984 json!({"temp": 20}),
985 Duration::from_millis(50),
986 ),
987 FunctionExecutionResult::new(
988 "get_time",
989 "call-2",
990 json!({"timezone": "UTC"}),
991 json!({"time": "14:30"}),
992 Duration::from_millis(30),
993 ),
994 ]);
995
996 let json_str = serde_json::to_string(&results).expect("Serialization should succeed");
997 let deserialized: AutoFunctionStreamChunk =
998 serde_json::from_str(&json_str).expect("Deserialization should succeed");
999
1000 match deserialized {
1001 AutoFunctionStreamChunk::FunctionResults(execs) => {
1002 assert_eq!(execs.len(), 2);
1003 assert_eq!(execs[0].name, "get_weather");
1004 assert_eq!(execs[1].name, "get_time");
1005 }
1006 _ => panic!("Expected FunctionResults variant"),
1007 }
1008
1009 let unknown_json = r#"{"chunk_type": "future_event_type", "data": {"key": "value"}}"#;
1011 let deserialized: AutoFunctionStreamChunk =
1012 serde_json::from_str(unknown_json).expect("Should deserialize unknown variant");
1013
1014 assert!(deserialized.is_unknown());
1016 assert_eq!(deserialized.unknown_chunk_type(), Some("future_event_type"));
1017 let data = deserialized.unknown_data().expect("Should have data");
1018 assert_eq!(data["key"], "value");
1019
1020 let reserialized = serde_json::to_string(&deserialized).expect("Should serialize");
1022 assert!(reserialized.contains("future_event_type"));
1023 assert!(reserialized.contains("value"));
1024 }
1025
1026 #[test]
1027 fn test_auto_function_stream_chunk_unknown_without_data() {
1028 let unknown_json = r#"{"chunk_type": "no_data_chunk"}"#;
1030 let deserialized: AutoFunctionStreamChunk =
1031 serde_json::from_str(unknown_json).expect("Should deserialize unknown variant");
1032
1033 assert!(deserialized.is_unknown());
1034 assert_eq!(deserialized.unknown_chunk_type(), Some("no_data_chunk"));
1035
1036 let data = deserialized.unknown_data().expect("Should have data field");
1038 assert!(data.is_null());
1039 }
1040
1041 #[test]
1042 fn test_auto_function_result_roundtrip() {
1043 use crate::InteractionStatus;
1044
1045 let result = AutoFunctionResult {
1047 response: crate::InteractionResponse {
1048 id: Some("interaction-abc123".to_string()),
1049 model: Some("gemini-3-flash-preview".to_string()),
1050 steps: vec![
1051 crate::Step::model_text("Based on the weather data:"),
1052 crate::Step::model_text("Paris is 18°C and London is 15°C."),
1053 ],
1054 status: InteractionStatus::Completed,
1055 usage: Some(crate::UsageMetadata {
1056 total_input_tokens: Some(50),
1057 total_output_tokens: Some(30),
1058 total_tokens: Some(80),
1059 ..Default::default()
1060 }),
1061 previous_interaction_id: Some("prev-interaction-xyz".to_string()),
1062 ..Default::default()
1063 },
1064 executions: vec![
1065 FunctionExecutionResult::new(
1066 "get_weather",
1067 "call-001",
1068 json!({"city": "Paris"}),
1069 json!({"city": "Paris", "temp": 18, "unit": "celsius"}),
1070 Duration::from_millis(120),
1071 ),
1072 FunctionExecutionResult::new(
1073 "get_weather",
1074 "call-002",
1075 json!({"city": "London"}),
1076 json!({"city": "London", "temp": 15, "unit": "celsius"}),
1077 Duration::from_millis(95),
1078 ),
1079 ],
1080 reached_max_loops: false,
1081 };
1082
1083 let json_str = serde_json::to_string(&result).expect("Serialization should succeed");
1085
1086 assert!(
1088 json_str.contains("interaction-abc123"),
1089 "Should contain interaction ID"
1090 );
1091 assert!(
1092 json_str.contains("gemini-3-flash-preview"),
1093 "Should contain model name"
1094 );
1095 assert!(
1096 json_str.contains("get_weather"),
1097 "Should contain function name"
1098 );
1099 assert!(
1100 json_str.contains("call-001"),
1101 "Should contain first call_id"
1102 );
1103 assert!(
1104 json_str.contains("call-002"),
1105 "Should contain second call_id"
1106 );
1107 assert!(json_str.contains("Paris"), "Should contain Paris");
1108 assert!(json_str.contains("London"), "Should contain London");
1109 assert!(
1110 json_str.contains("prev-interaction-xyz"),
1111 "Should contain previous interaction ID"
1112 );
1113
1114 let deserialized: AutoFunctionResult =
1116 serde_json::from_str(&json_str).expect("Deserialization should succeed");
1117
1118 assert_eq!(
1120 deserialized.response.id.as_deref(),
1121 Some("interaction-abc123")
1122 );
1123 assert_eq!(
1124 deserialized.response.model,
1125 Some("gemini-3-flash-preview".to_string())
1126 );
1127 assert_eq!(deserialized.response.status, InteractionStatus::Completed);
1128 assert_eq!(
1129 deserialized.response.previous_interaction_id,
1130 Some("prev-interaction-xyz".to_string())
1131 );
1132
1133 let usage = deserialized.response.usage.expect("Should have usage");
1135 assert_eq!(usage.total_input_tokens, Some(50));
1136 assert_eq!(usage.total_output_tokens, Some(30));
1137 assert_eq!(usage.total_tokens, Some(80));
1138
1139 assert_eq!(deserialized.executions.len(), 2);
1141 assert_eq!(deserialized.executions[0].name, "get_weather");
1142 assert_eq!(deserialized.executions[0].call_id, "call-001");
1143 assert_eq!(deserialized.executions[0].result["city"], "Paris");
1144 assert_eq!(deserialized.executions[1].name, "get_weather");
1145 assert_eq!(deserialized.executions[1].call_id, "call-002");
1146 assert_eq!(deserialized.executions[1].result["city"], "London");
1147
1148 assert!(!deserialized.reached_max_loops);
1150 }
1151
1152 #[test]
1153 fn test_auto_function_result_reached_max_loops() {
1154 use crate::InteractionStatus;
1155
1156 let result = AutoFunctionResult {
1158 response: crate::InteractionResponse {
1159 id: Some("interaction-stuck".to_string()),
1160 model: Some("gemini-3-flash-preview".to_string()),
1161 steps: vec![crate::Step::function_call(
1162 "call-stuck",
1163 "get_weather",
1164 json!({"city": "Tokyo"}),
1165 )],
1166 status: InteractionStatus::Completed,
1167 ..Default::default()
1168 },
1169 executions: vec![FunctionExecutionResult::new(
1170 "get_weather",
1171 "call-1",
1172 json!({"city": "Berlin"}),
1173 json!({"temp": 25}),
1174 Duration::from_millis(50),
1175 )],
1176 reached_max_loops: true,
1177 };
1178
1179 let json_str = serde_json::to_string(&result).expect("Serialization should succeed");
1181 assert!(
1182 json_str.contains("reached_max_loops"),
1183 "Should contain reached_max_loops field"
1184 );
1185 assert!(json_str.contains("true"), "Should contain true value");
1186
1187 let deserialized: AutoFunctionResult =
1189 serde_json::from_str(&json_str).expect("Deserialization should succeed");
1190 assert!(deserialized.reached_max_loops);
1191 assert_eq!(deserialized.executions.len(), 1);
1192 }
1193
1194 #[test]
1195 fn test_auto_function_result_backwards_compatibility() {
1196 let legacy_json = r#"{
1198 "response": {
1199 "id": "interaction-old",
1200 "model": "gemini-3-flash-preview",
1201 "steps": [],
1202 "status": "completed"
1203 },
1204 "executions": []
1205 }"#;
1206
1207 let deserialized: AutoFunctionResult =
1208 serde_json::from_str(legacy_json).expect("Should deserialize legacy JSON");
1209
1210 assert!(
1212 !deserialized.reached_max_loops,
1213 "Missing field should default to false"
1214 );
1215 }
1216
1217 #[test]
1218 fn test_max_loops_reached_chunk_roundtrip() {
1219 use crate::InteractionStatus;
1220
1221 let response = crate::InteractionResponse {
1223 id: Some("interaction-max-loops".to_string()),
1224 model: Some("gemini-3-flash-preview".to_string()),
1225 steps: vec![crate::Step::function_call(
1226 "call-pending",
1227 "stuck_function",
1228 json!({}),
1229 )],
1230 status: InteractionStatus::Completed,
1231 ..Default::default()
1232 };
1233
1234 let chunk = AutoFunctionStreamChunk::MaxLoopsReached(response);
1235
1236 let json_str = serde_json::to_string(&chunk).expect("Serialization should succeed");
1238 assert!(
1239 json_str.contains("max_loops_reached"),
1240 "Should contain chunk_type"
1241 );
1242 assert!(
1243 json_str.contains("interaction-max-loops"),
1244 "Should contain response data"
1245 );
1246
1247 let deserialized: AutoFunctionStreamChunk =
1249 serde_json::from_str(&json_str).expect("Deserialization should succeed");
1250
1251 match deserialized {
1252 AutoFunctionStreamChunk::MaxLoopsReached(resp) => {
1253 assert_eq!(resp.id.as_deref(), Some("interaction-max-loops"));
1254 assert_eq!(resp.function_calls().len(), 1);
1255 assert_eq!(resp.function_calls()[0].name, "stuck_function");
1256 }
1257 other => panic!("Expected MaxLoopsReached, got {:?}", other),
1258 }
1259 }
1260
1261 #[test]
1262 fn test_accumulator_handles_max_loops_reached() {
1263 use crate::InteractionStatus;
1264
1265 let mut accumulator = AutoFunctionResultAccumulator::new();
1266
1267 let results = AutoFunctionStreamChunk::FunctionResults(vec![FunctionExecutionResult::new(
1269 "test_func",
1270 "call-1",
1271 json!({}),
1272 json!({"ok": true}),
1273 Duration::from_millis(10),
1274 )]);
1275
1276 assert!(
1277 accumulator.push(results).is_none(),
1278 "Should not complete yet"
1279 );
1280 assert_eq!(accumulator.executions().len(), 1);
1281
1282 let response = crate::InteractionResponse {
1284 id: Some("max-loops-response".to_string()),
1285 model: Some("gemini-3-flash-preview".to_string()),
1286 status: InteractionStatus::Completed,
1287 ..Default::default()
1288 };
1289
1290 let max_loops_chunk = AutoFunctionStreamChunk::MaxLoopsReached(response);
1291 let result = accumulator.push(max_loops_chunk);
1292
1293 assert!(result.is_some(), "Should complete on MaxLoopsReached");
1294 let result = result.unwrap();
1295 assert!(
1296 result.reached_max_loops,
1297 "Should have reached_max_loops: true"
1298 );
1299 assert_eq!(result.executions.len(), 1);
1300 assert_eq!(result.response.id.as_deref(), Some("max-loops-response"));
1301 }
1302
1303 #[test]
1304 fn test_auto_function_stream_event_with_event_id_roundtrip() {
1305 let event = AutoFunctionStreamEvent::new(
1306 AutoFunctionStreamChunk::Delta(StepDelta::Text {
1307 text: "Hello from auto-function".to_string(),
1308 }),
1309 Some("evt_auto_abc123".to_string()),
1310 );
1311
1312 assert!(event.is_delta());
1314 assert!(!event.is_complete());
1315 assert!(!event.is_unknown());
1316
1317 let json = serde_json::to_string(&event).expect("Serialization should succeed");
1318 assert!(json.contains("evt_auto_abc123"), "Should have event_id");
1319 assert!(
1320 json.contains("Hello from auto-function"),
1321 "Should have content"
1322 );
1323
1324 let deserialized: AutoFunctionStreamEvent =
1325 serde_json::from_str(&json).expect("Deserialization should succeed");
1326 assert_eq!(deserialized.event_id.as_deref(), Some("evt_auto_abc123"));
1327 assert!(deserialized.is_delta());
1328 }
1329
1330 #[test]
1331 fn test_auto_function_stream_event_without_event_id() {
1332 let event = AutoFunctionStreamEvent::new(
1334 AutoFunctionStreamChunk::FunctionResults(vec![FunctionExecutionResult::new(
1335 "weather",
1336 "call-123",
1337 json!({"city": "Denver"}),
1338 json!({"temp": 72}),
1339 Duration::from_millis(50),
1340 )]),
1341 None,
1342 );
1343
1344 assert!(!event.is_delta());
1345 assert!(!event.is_complete());
1346 assert!(event.event_id.is_none());
1347
1348 let json = serde_json::to_string(&event).expect("Serialization should succeed");
1349 assert!(!json.contains("event_id"), "Should not have event_id field");
1350 assert!(json.contains("weather"), "Should have function name");
1351
1352 let deserialized: AutoFunctionStreamEvent =
1353 serde_json::from_str(&json).expect("Deserialization should succeed");
1354 assert!(deserialized.event_id.is_none());
1355 }
1356
1357 #[test]
1358 fn test_auto_function_stream_event_with_empty_event_id() {
1359 let event = AutoFunctionStreamEvent::new(
1361 AutoFunctionStreamChunk::Delta(StepDelta::Text {
1362 text: "Test".to_string(),
1363 }),
1364 Some(String::new()),
1365 );
1366
1367 let json = serde_json::to_string(&event).expect("Serialization should succeed");
1368 assert!(
1369 json.contains(r#""event_id":"""#),
1370 "Should have empty event_id"
1371 );
1372
1373 let deserialized: AutoFunctionStreamEvent =
1374 serde_json::from_str(&json).expect("Deserialization should succeed");
1375 assert_eq!(deserialized.event_id.as_deref(), Some(""));
1376 }
1377
1378 #[test]
1379 fn test_pending_function_call() {
1380 let call = PendingFunctionCall::new("get_weather", "call-123", json!({"city": "Seattle"}));
1381
1382 assert_eq!(call.name, "get_weather");
1383 assert_eq!(call.call_id, "call-123");
1384 assert_eq!(call.args, json!({"city": "Seattle"}));
1385 }
1386
1387 #[test]
1388 fn test_pending_function_call_serialization_roundtrip() {
1389 let call = PendingFunctionCall::new("test_func", "id-456", json!({"key": "value"}));
1390
1391 let json_str = serde_json::to_string(&call).expect("Serialization should succeed");
1392 assert!(json_str.contains("test_func"));
1393 assert!(json_str.contains("id-456"));
1394
1395 let deserialized: PendingFunctionCall =
1396 serde_json::from_str(&json_str).expect("Deserialization should succeed");
1397 assert_eq!(deserialized, call);
1398 }
1399
1400 #[test]
1401 fn test_executing_functions_new_format_roundtrip() {
1402 use crate::InteractionStatus;
1403
1404 let chunk = AutoFunctionStreamChunk::ExecutingFunctions {
1405 response: crate::InteractionResponse {
1406 id: Some("interaction-new".to_string()),
1407 model: Some("gemini-3-flash-preview".to_string()),
1408 status: InteractionStatus::Completed,
1409 ..Default::default()
1410 },
1411 pending_calls: vec![
1412 PendingFunctionCall::new("func1", "call-1", json!({"a": 1})),
1413 PendingFunctionCall::new("func2", "call-2", json!({"b": 2})),
1414 ],
1415 };
1416
1417 let json_str = serde_json::to_string(&chunk).expect("Serialization should succeed");
1418 assert!(json_str.contains("pending_calls"));
1419 assert!(json_str.contains("func1"));
1420 assert!(json_str.contains("func2"));
1421
1422 let deserialized: AutoFunctionStreamChunk =
1423 serde_json::from_str(&json_str).expect("Deserialization should succeed");
1424
1425 match deserialized {
1426 AutoFunctionStreamChunk::ExecutingFunctions {
1427 response,
1428 pending_calls,
1429 } => {
1430 assert_eq!(response.id.as_deref(), Some("interaction-new"));
1431 assert_eq!(pending_calls.len(), 2);
1432 assert_eq!(pending_calls[0].name, "func1");
1433 assert_eq!(pending_calls[1].name, "func2");
1434 }
1435 _ => panic!("Expected ExecutingFunctions variant"),
1436 }
1437 }
1438}