genai_rs/
streaming.rs

1//! Streaming types for automatic function calling.
2//!
3//! This module contains types for streaming responses with automatic function execution.
4//! The [`AutoFunctionStreamChunk`] enum provides events for tracking the progress of
5//! an interaction that may involve multiple function call rounds. Events are wrapped
6//! in [`AutoFunctionStreamEvent`] to include `event_id` for stream resume support.
7//!
8//! # Example
9//!
10//! ```no_run
11//! use futures_util::StreamExt;
12//! use genai_rs::{Client, AutoFunctionStreamChunk};
13//!
14//! # async fn example() -> Result<(), genai_rs::GenaiError> {
15//! let client = Client::new("your-api-key".to_string());
16//!
17//! let mut stream = client
18//!     .interaction()
19//!     .with_model("gemini-3-flash-preview")
20//!     .with_text("What's the weather in London?")
21//!     .create_stream_with_auto_functions();
22//!
23//! let mut last_event_id = None;
24//! while let Some(result) = stream.next().await {
25//!     let event = result?;
26//!     // Track event_id for potential resume
27//!     if event.event_id.is_some() {
28//!         last_event_id = event.event_id.clone();
29//!     }
30//!
31//!     match &event.chunk {
32//!         AutoFunctionStreamChunk::Delta(content) => {
33//!             if let Some(t) = content.as_text() {
34//!                 print!("{}", t);
35//!             }
36//!         }
37//!         AutoFunctionStreamChunk::ExecutingFunctions { pending_calls, .. } => {
38//!             println!("[Executing: {:?}]", pending_calls.iter().map(|c| &c.name).collect::<Vec<_>>());
39//!         }
40//!         AutoFunctionStreamChunk::FunctionResults(results) => {
41//!             println!("[Got {} results]", results.len());
42//!         }
43//!         AutoFunctionStreamChunk::Complete(_response) => {
44//!             println!("[Done]");
45//!         }
46//!         _ => {} // Handle unknown future variants
47//!     }
48//! }
49//! # Ok(())
50//! # }
51//! ```
52
53use std::time::Duration;
54
55use crate::{Content, InteractionResponse};
56use serde::{Deserialize, Serialize};
57
58/// A function call that is about to be executed.
59///
60/// This represents a function call detected during streaming but not yet executed.
61/// It contains the call metadata (name, ID, args) but not the result, which will
62/// be available in [`FunctionExecutionResult`] after execution completes.
63///
64/// # Example
65///
66/// ```no_run
67/// # use genai_rs::PendingFunctionCall;
68/// # let call: PendingFunctionCall = todo!();
69/// println!("About to execute: {}({})", call.name, call.args);
70/// println!("  Call ID: {}", call.call_id);
71/// ```
72#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
73#[non_exhaustive]
74pub struct PendingFunctionCall {
75    /// Name of the function to be called
76    pub name: String,
77    /// The call_id from the API (used to match results)
78    pub call_id: String,
79    /// The arguments to pass to the function
80    pub args: serde_json::Value,
81}
82
83impl PendingFunctionCall {
84    /// Creates a new pending function call.
85    #[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/// A chunk from streaming with automatic function calling.
100///
101/// This enum represents the different events that can occur during a streaming
102/// interaction with automatic function execution. The stream yields deltas as
103/// content arrives, signals when functions are being executed, and completes
104/// when the model returns a response without function calls.
105///
106/// # Forward Compatibility
107///
108/// This enum uses `#[non_exhaustive]` to allow adding new event types in future
109/// versions without breaking existing code. Always include a wildcard arm in
110/// match statements. Unknown variants are preserved with their data for debugging.
111///
112/// # Serialization
113///
114/// This enum implements `Serialize` and `Deserialize` for logging, persistence,
115/// and replay of streaming events.
116#[derive(Clone, Debug)]
117#[non_exhaustive]
118pub enum AutoFunctionStreamChunk {
119    /// Incremental content from the model (text, thoughts, etc.)
120    Delta(Content),
121
122    /// Function calls detected, about to execute.
123    ///
124    /// This event is yielded when the model requests function calls and
125    /// before the functions are executed. The `pending_calls` field contains
126    /// the function calls that are about to be executed.
127    ///
128    /// **Note**: In streaming mode, function calls arrive incrementally via
129    /// `Delta` chunks. The `pending_calls` list is built from accumulated deltas
130    /// and will always be populated, even though `response.function_calls()`
131    /// may be empty.
132    ExecutingFunctions {
133        /// The response from the API (may have empty `function_calls()` in streaming mode)
134        response: InteractionResponse,
135        /// The function calls that are about to be executed (always populated)
136        pending_calls: Vec<PendingFunctionCall>,
137    },
138
139    /// Function execution completed with results.
140    ///
141    /// This event is yielded after all functions in a batch have been executed,
142    /// before sending results back to the model for the next iteration.
143    FunctionResults(Vec<FunctionExecutionResult>),
144
145    /// Final complete response (no more function calls).
146    ///
147    /// This is the last event in the stream, yielded when the model returns
148    /// a response that doesn't request any function calls.
149    Complete(InteractionResponse),
150
151    /// Maximum function call loops reached.
152    ///
153    /// This event is yielded when the auto-function loop has reached the maximum
154    /// number of iterations (set via `with_max_function_call_loops()`) without
155    /// the model returning a response without function calls.
156    ///
157    /// The response contains the last response from the model, which likely still
158    /// contains pending function calls. Use [`AutoFunctionResultAccumulator`] to
159    /// collect all function execution results from prior `FunctionResults` chunks.
160    ///
161    /// This allows debugging why the model is stuck in a loop while preserving
162    /// all partial results.
163    MaxLoopsReached(InteractionResponse),
164
165    /// Unknown event type (for forward compatibility).
166    ///
167    /// This variant is used when deserializing JSON that contains an unrecognized
168    /// `chunk_type`. This allows the library to gracefully handle new event types
169    /// added by the API in future versions without failing deserialization.
170    ///
171    /// The `chunk_type` field contains the unrecognized type string, and `data`
172    /// contains the full JSON data for inspection or debugging.
173    Unknown {
174        /// The unrecognized chunk type from the API
175        chunk_type: String,
176        /// The raw JSON data, preserved for debugging and roundtrip serialization
177        data: serde_json::Value,
178    },
179}
180
181impl AutoFunctionStreamChunk {
182    /// Check if this is an unknown chunk type.
183    #[must_use]
184    pub const fn is_unknown(&self) -> bool {
185        matches!(self, Self::Unknown { .. })
186    }
187
188    /// Check if this chunk is a Delta variant.
189    #[must_use]
190    pub const fn is_delta(&self) -> bool {
191        matches!(self, Self::Delta(_))
192    }
193
194    /// Check if this chunk is a Complete variant.
195    #[must_use]
196    pub const fn is_complete(&self) -> bool {
197        matches!(self, Self::Complete(_))
198    }
199
200    /// Returns the chunk type name if this is an unknown chunk type.
201    ///
202    /// Returns `None` for known chunk types.
203    #[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    /// Returns the raw JSON data if this is an unknown chunk type.
212    ///
213    /// Returns `None` for known chunk types.
214    #[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                // Serialize as nested object with both fields
244                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/// Streaming event with position metadata for auto-function stream resumption.
446///
447/// This wrapper pairs an [`AutoFunctionStreamChunk`] with its `event_id`,
448/// enabling stream resumption after network interruptions or reconnects.
449///
450/// # Stream Resumption
451///
452/// Save the `event_id` from each event. If the connection drops, you can resume
453/// the stream from the last received event by calling `get_interaction_stream()`
454/// with the saved `event_id`.
455///
456/// **Note**: The auto-function streaming loop is client-side. If interrupted during
457/// function execution, you may need to restart the full loop rather than resuming.
458/// However, the underlying API stream can be resumed.
459///
460/// # Example
461///
462/// ```no_run
463/// use futures_util::StreamExt;
464/// use genai_rs::{Client, AutoFunctionStreamEvent, AutoFunctionStreamChunk};
465///
466/// # async fn example() -> Result<(), genai_rs::GenaiError> {
467/// let client = Client::new("your-api-key".to_string());
468///
469/// let mut stream = client
470///     .interaction()
471///     .with_model("gemini-3-flash-preview")
472///     .with_text("What's the weather in London?")
473///     .create_stream_with_auto_functions();
474///
475/// let mut last_event_id: Option<String> = None;
476///
477/// while let Some(event) = stream.next().await {
478///     let event = event?;
479///
480///     // Save event_id for potential resume
481///     if let Some(id) = &event.event_id {
482///         last_event_id = Some(id.clone());
483///     }
484///
485///     match &event.chunk {
486///         AutoFunctionStreamChunk::Delta(content) => {
487///             if let Some(text) = content.as_text() {
488///                 print!("{}", text);
489///             }
490///         }
491///         AutoFunctionStreamChunk::Complete(_) => {
492///             println!("\n[Done]");
493///         }
494///         _ => {}
495///     }
496/// }
497/// # Ok(())
498/// # }
499/// ```
500#[derive(Clone, Debug)]
501#[non_exhaustive]
502pub struct AutoFunctionStreamEvent {
503    /// The auto-function stream chunk content
504    pub chunk: AutoFunctionStreamChunk,
505    /// Event ID for stream resumption.
506    ///
507    /// Pass this to `last_event_id` when resuming a stream to continue from
508    /// this position. This is the `event_id` from the underlying API stream.
509    ///
510    /// May be `None` for client-generated events (like `ExecutingFunctions`
511    /// and `FunctionResults`) that don't come from the API stream.
512    pub event_id: Option<String>,
513}
514
515impl AutoFunctionStreamEvent {
516    /// Creates a new auto-function stream event.
517    #[must_use]
518    pub fn new(chunk: AutoFunctionStreamChunk, event_id: Option<String>) -> Self {
519        Self { chunk, event_id }
520    }
521
522    /// Check if the inner chunk is a Delta variant.
523    #[must_use]
524    pub const fn is_delta(&self) -> bool {
525        self.chunk.is_delta()
526    }
527
528    /// Check if the inner chunk is a Complete variant.
529    #[must_use]
530    pub const fn is_complete(&self) -> bool {
531        self.chunk.is_complete()
532    }
533
534    /// Check if the inner chunk is an Unknown variant.
535    #[must_use]
536    pub const fn is_unknown(&self) -> bool {
537        self.chunk.is_unknown()
538    }
539
540    /// Returns the unrecognized chunk type if this is an Unknown variant.
541    #[must_use]
542    pub fn unknown_chunk_type(&self) -> Option<&str> {
543        self.chunk.unknown_chunk_type()
544    }
545
546    /// Returns the preserved JSON data if this is an Unknown variant.
547    #[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/// Result of executing a function locally.
595///
596/// This represents the output from a function that was executed by the library
597/// during automatic function calling. It contains the function name, the call ID
598/// (used to match with the original request), the arguments that were passed,
599/// and the result value.
600///
601/// # Example
602///
603/// ```no_run
604/// # use genai_rs::FunctionExecutionResult;
605/// # let result: FunctionExecutionResult = todo!();
606/// println!("Function {} called with: {}", result.name, result.args);
607/// println!("  Returned: {}", result.result);
608/// println!("  Call ID: {}, Duration: {:?}", result.call_id, result.duration);
609/// ```
610#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
611#[non_exhaustive]
612pub struct FunctionExecutionResult {
613    /// Name of the function that was called
614    pub name: String,
615    /// The call_id from the FunctionCall this result responds to
616    pub call_id: String,
617    /// The arguments passed to the function
618    pub args: serde_json::Value,
619    /// The result returned by the function
620    pub result: serde_json::Value,
621    /// How long the function took to execute
622    #[serde(with = "duration_millis")]
623    pub duration: Duration,
624}
625
626impl FunctionExecutionResult {
627    /// Creates a new function execution result.
628    #[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    /// Returns true if this execution resulted in an error.
646    ///
647    /// Errors occur when:
648    /// - The function was not found in the registry or tool service
649    /// - The function execution failed (panicked or returned an error)
650    ///
651    /// # Example
652    ///
653    /// ```ignore
654    /// let result = client.interaction()
655    ///     .with_text("What's the weather?")
656    ///     .create_with_auto_functions()
657    ///     .await?;
658    ///
659    /// for execution in &result.executions {
660    ///     if execution.is_error() {
661    ///         eprintln!("Function {} failed: {:?}", execution.name, execution.result);
662    ///     }
663    /// }
664    /// ```
665    #[must_use]
666    pub fn is_error(&self) -> bool {
667        self.result.get("error").is_some()
668    }
669
670    /// Returns true if this execution succeeded (no error).
671    #[must_use]
672    pub fn is_success(&self) -> bool {
673        !self.is_error()
674    }
675
676    /// Returns the error message if this execution failed, None otherwise.
677    #[must_use]
678    pub fn error_message(&self) -> Option<&str> {
679        self.result.get("error").and_then(|v| v.as_str())
680    }
681}
682
683/// Serialize Duration as milliseconds for JSON compatibility
684mod 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/// Result from `create_with_auto_functions()` containing the final response
705/// and a history of all function executions.
706///
707/// This type provides visibility into which functions were called during
708/// automatic function execution, useful for debugging, logging, and evaluation.
709///
710/// # Memory Considerations
711///
712/// The `executions` vector accumulates all [`FunctionExecutionResult`] records
713/// from each iteration of the auto-function loop. For typical use cases (1-5
714/// iterations with 1-3 functions each), this is negligible.
715///
716/// For edge cases with many function calls or large result payloads, consider:
717///
718/// - **Limit iterations**: Use [`crate::InteractionBuilder::with_max_function_call_loops()`]
719///   to cap the maximum number of iterations (default: 5)
720/// - **Extract and drop**: Extract only the data you need, then drop the result
721/// - **Manual control**: For fine-grained memory management, implement function
722///   calling manually using [`crate::InteractionBuilder::add_functions()`] and
723///   [`crate::InteractionBuilder::create()`] instead of the auto-function helpers
724///
725/// Each `FunctionExecutionResult` contains the function name, call ID, result
726/// value (as `serde_json::Value`), and execution duration. Memory usage scales
727/// primarily with the size of function result payloads.
728///
729/// # Example
730///
731/// ```no_run
732/// # use genai_rs::{Client, AutoFunctionResult};
733/// # async fn example() -> Result<(), genai_rs::GenaiError> {
734/// # let client = Client::new("key".to_string());
735/// let result = client
736///     .interaction()
737///     .with_model("gemini-3-flash-preview")
738///     .with_text("What's the weather in London?")
739///     .create_with_auto_functions()
740///     .await?;
741///
742/// // Access the final response
743/// if let Some(text) = result.response.as_text() {
744///     println!("Answer: {}", text);
745/// }
746///
747/// // Access execution history
748/// for exec in &result.executions {
749///     println!("Called {} -> {}", exec.name, exec.result);
750/// }
751/// # Ok(())
752/// # }
753/// ```
754#[derive(Clone, Debug, Serialize, Deserialize)]
755#[non_exhaustive]
756pub struct AutoFunctionResult {
757    /// The final response from the model (after all function calls completed)
758    pub response: InteractionResponse,
759    /// All functions that were executed during the auto-function loop
760    pub executions: Vec<FunctionExecutionResult>,
761    /// Whether the auto-function loop was terminated due to reaching the maximum
762    /// number of iterations (set via `with_max_function_call_loops()`).
763    ///
764    /// When `true`, the `response` contains the last response from the model before
765    /// hitting the limit, which likely still contains pending function calls.
766    /// The `executions` vector contains all functions that were successfully executed
767    /// before the limit was reached.
768    ///
769    /// This allows debugging why the model is stuck in a loop and preserves
770    /// partial results that may still be useful.
771    #[serde(default)]
772    pub reached_max_loops: bool,
773}
774
775impl AutoFunctionResult {
776    /// Returns true if all function executions succeeded (no errors).
777    ///
778    /// This is useful for detecting missing function implementations or
779    /// function execution failures that would otherwise be silently
780    /// sent to the model as error results.
781    ///
782    /// # Example
783    ///
784    /// ```ignore
785    /// let result = client.interaction()
786    ///     .with_text("What's the weather?")
787    ///     .add_functions(vec![get_weather_function()])
788    ///     .create_with_auto_functions()
789    ///     .await?;
790    ///
791    /// assert!(result.all_executions_succeeded(),
792    ///     "Function executions failed: {:?}",
793    ///     result.failed_executions());
794    /// ```
795    #[must_use]
796    pub fn all_executions_succeeded(&self) -> bool {
797        self.executions.iter().all(|e| e.is_success())
798    }
799
800    /// Returns all executions that failed (had errors).
801    #[must_use]
802    pub fn failed_executions(&self) -> Vec<&FunctionExecutionResult> {
803        self.executions.iter().filter(|e| e.is_error()).collect()
804    }
805}
806
807/// Accumulator for building [`AutoFunctionResult`] from a stream of [`AutoFunctionStreamChunk`].
808///
809/// This helper collects all function execution results and the final response from
810/// a streaming auto-function interaction, producing the same result type as the
811/// non-streaming `create_with_auto_functions()` method.
812///
813/// # Example
814///
815/// ```no_run
816/// use futures_util::StreamExt;
817/// use genai_rs::{Client, AutoFunctionStreamChunk, AutoFunctionResultAccumulator};
818///
819/// # async fn example() -> Result<(), genai_rs::GenaiError> {
820/// let client = Client::new("your-api-key".to_string());
821///
822/// let mut stream = client
823///     .interaction()
824///     .with_model("gemini-3-flash-preview")
825///     .with_text("What's the weather in London?")
826///     .create_stream_with_auto_functions();
827///
828/// let mut accumulator = AutoFunctionResultAccumulator::new();
829///
830/// while let Some(event) = stream.next().await {
831///     let event = event?;
832///
833///     // Process deltas for UI updates
834///     if let AutoFunctionStreamChunk::Delta(content) = &event.chunk {
835///         if let Some(text) = content.as_text() {
836///             print!("{}", text);
837///         }
838///     }
839///
840///     // Feed all chunks to the accumulator
841///     if let Some(result) = accumulator.push(event.chunk) {
842///         // Stream is complete, we have the full result
843///         println!("\n\nExecuted {} functions", result.executions.len());
844///         for exec in &result.executions {
845///             println!("  {} took {:?}", exec.name, exec.duration);
846///         }
847///     }
848/// }
849/// # Ok(())
850/// # }
851/// ```
852#[derive(Clone, Debug, Default)]
853pub struct AutoFunctionResultAccumulator {
854    executions: Vec<FunctionExecutionResult>,
855}
856
857impl AutoFunctionResultAccumulator {
858    /// Creates a new empty accumulator.
859    #[must_use]
860    pub fn new() -> Self {
861        Self::default()
862    }
863
864    /// Feeds a chunk to the accumulator.
865    ///
866    /// Returns `Some(AutoFunctionResult)` when the stream ends, either:
867    /// - With a `Complete` chunk (normal completion, `reached_max_loops: false`)
868    /// - With a `MaxLoopsReached` chunk (limit hit, `reached_max_loops: true`)
869    ///
870    /// Returns `None` for all other chunk types.
871    ///
872    /// The accumulator collects all `FunctionResults` chunks and combines them
873    /// with the final response.
874    #[must_use]
875    #[allow(unreachable_patterns)] // Handle future variants from #[non_exhaustive] enum
876    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            // Handle future variants gracefully
895            _ => None,
896        }
897    }
898
899    /// Returns the accumulated executions so far.
900    ///
901    /// Useful for checking progress without consuming the accumulator.
902    #[must_use]
903    pub fn executions(&self) -> &[FunctionExecutionResult] {
904        &self.executions
905    }
906
907    /// Resets the accumulator to its initial empty state.
908    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        // Test that Delta and FunctionResults variants can be created
938        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        // Note: ExecutingFunctions and Complete require InteractionResponse which is harder to construct in tests
952    }
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        // Verify key fields are present in serialized output
967        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        // Verify full roundtrip
975        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        // Test Delta variant roundtrip
983        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        // Test FunctionResults variant roundtrip
1004        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        // Test Unknown variant handling (forward compatibility)
1035        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        // Verify it's an Unknown variant with data preserved
1040        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        // Verify roundtrip serialization
1046        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        // Test unknown chunk type without data field
1054        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        // Data should be null when not provided
1062        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        // Create a realistic AutoFunctionResult with multiple executions
1071        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        // Serialize
1124        let json_str = serde_json::to_string(&result).expect("Serialization should succeed");
1125
1126        // Verify key data is present in JSON
1127        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        // Deserialize
1155        let deserialized: AutoFunctionResult =
1156            serde_json::from_str(&json_str).expect("Deserialization should succeed");
1157
1158        // Verify response fields
1159        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        // Verify usage metadata
1174        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        // Verify executions
1180        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        // Verify reached_max_loops
1189        assert!(!deserialized.reached_max_loops);
1190    }
1191
1192    #[test]
1193    fn test_auto_function_result_reached_max_loops() {
1194        use crate::InteractionStatus;
1195
1196        // Create an AutoFunctionResult with reached_max_loops: true
1197        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        // Serialize
1231        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        // Deserialize
1239        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        // Test that JSON without reached_max_loops (from older versions) still deserializes
1248        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        // reached_max_loops should default to false
1269        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        // Create a MaxLoopsReached chunk
1280        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        // Serialize
1303        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        // Deserialize
1314        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        // Simulate function results being yielded
1334        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        // Simulate MaxLoopsReached being yielded
1349        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        // Test helper methods
1389        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        // Client-generated events like FunctionResults don't have event_id
1409        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        // Edge case: empty string event_id should still serialize/deserialize
1436        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}