genai_rs/
content.rs

1//! Core content types for the Interactions API.
2//!
3//! This module contains `Content`, the primary enum representing all content
4//! types that can appear in API requests and responses, along with its custom serialization
5//! and deserialization implementations.
6
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10// =============================================================================
11// Annotation Types (for Text Content with Citations)
12// =============================================================================
13
14/// An annotation linking a text span to its source.
15///
16/// Annotations provide citation information for specific portions of generated text,
17/// typically appearing when using grounding tools like `GoogleSearch` or `UrlContext`.
18///
19/// # Byte Indices
20///
21/// **Important:** The `start_index` and `end_index` fields are **byte positions** (not
22/// character indices) in the text content. This matches the UTF-8 byte offsets used
23/// by the Gemini API. For multi-byte characters (like emoji or non-ASCII text), you
24/// may need to use byte-slicing rather than character indexing.
25///
26/// # Example
27///
28/// ```no_run
29/// # use genai_rs::{InteractionResponse, Annotation};
30/// # let response: InteractionResponse = todo!();
31/// // Get all annotations from the response
32/// for annotation in response.all_annotations() {
33///     println!(
34///         "Text at bytes {}..{} sourced from: {}",
35///         annotation.start_index,
36///         annotation.end_index,
37///         annotation.source.as_deref().unwrap_or("<no source>")
38///     );
39/// }
40/// ```
41///
42/// # Extracting Annotated Text
43///
44/// To extract the annotated substring from the response text:
45///
46/// ```no_run
47/// # use genai_rs::{InteractionResponse, Annotation};
48/// # let response: InteractionResponse = todo!();
49/// # let annotation: &Annotation = todo!();
50/// if let Some(text) = response.as_text() {
51///     // Use byte slicing since indices are byte positions
52///     let bytes = text.as_bytes();
53///     if annotation.end_index <= bytes.len() {
54///         if let Ok(span) = std::str::from_utf8(&bytes[annotation.start_index..annotation.end_index]) {
55///             println!("Cited text: {}", span);
56///         }
57///     }
58/// }
59/// ```
60#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(rename_all = "snake_case")]
62#[non_exhaustive]
63pub struct Annotation {
64    /// Start of the segment in the text (byte position, inclusive).
65    ///
66    /// This is a byte offset into the UTF-8 encoded text, not a character index.
67    #[serde(default)]
68    pub start_index: usize,
69
70    /// End of the segment in the text (byte position, exclusive).
71    ///
72    /// This is a byte offset into the UTF-8 encoded text, not a character index.
73    #[serde(default)]
74    pub end_index: usize,
75
76    /// Source attributed for this portion of the text.
77    ///
78    /// This could be a URL, title, or other identifier depending on the grounding
79    /// tool used (e.g., `GoogleSearch` or `UrlContext`).
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub source: Option<String>,
82}
83
84impl Annotation {
85    /// Creates a new annotation with the given span indices and optional source.
86    ///
87    /// # Arguments
88    ///
89    /// * `start_index` - Start of the segment in the text (byte position, inclusive)
90    /// * `end_index` - End of the segment in the text (byte position, exclusive)
91    /// * `source` - Optional source attribution (URL, title, or identifier)
92    ///
93    /// # Example
94    ///
95    /// ```
96    /// # use genai_rs::Annotation;
97    /// let annotation = Annotation::new(0, 10, Some("https://example.com".to_string()));
98    /// assert_eq!(annotation.start_index, 0);
99    /// assert_eq!(annotation.end_index, 10);
100    /// assert!(annotation.has_source());
101    /// ```
102    #[must_use]
103    pub fn new(start_index: usize, end_index: usize, source: Option<String>) -> Self {
104        Self {
105            start_index,
106            end_index,
107            source,
108        }
109    }
110
111    /// Returns the byte length of the annotated span.
112    ///
113    /// This is equivalent to `end_index - start_index`.
114    #[must_use]
115    pub fn byte_len(&self) -> usize {
116        self.end_index.saturating_sub(self.start_index)
117    }
118
119    /// Returns `true` if this annotation has a source attribution.
120    #[must_use]
121    pub fn has_source(&self) -> bool {
122        self.source.is_some()
123    }
124
125    /// Extracts the annotated substring from the given text.
126    ///
127    /// Returns `None` if the indices are out of bounds or if the byte slice
128    /// doesn't form valid UTF-8.
129    ///
130    /// # Arguments
131    ///
132    /// * `text` - The full text content to extract from
133    ///
134    /// # Example
135    ///
136    /// ```
137    /// # use genai_rs::Annotation;
138    /// let annotation = Annotation::new(0, 5, Some("https://example.com".to_string()));
139    ///
140    /// let text = "Hello, world!";
141    /// assert_eq!(annotation.extract_span(text), Some("Hello"));
142    /// ```
143    #[must_use]
144    pub fn extract_span<'a>(&self, text: &'a str) -> Option<&'a str> {
145        let bytes = text.as_bytes();
146        if self.end_index <= bytes.len() && self.start_index <= self.end_index {
147            std::str::from_utf8(&bytes[self.start_index..self.end_index]).ok()
148        } else {
149            None
150        }
151    }
152}
153
154// =============================================================================
155// Google Search Result Item
156// =============================================================================
157
158/// A single result from a Google Search.
159///
160/// Contains the source information for a grounding chunk including the title,
161/// URL, and optionally the rendered content that was used for grounding.
162///
163/// # Example
164///
165/// ```no_run
166/// # use genai_rs::{Content, GoogleSearchResultItem};
167/// # let content: Content = todo!();
168/// if let Content::GoogleSearchResult { result, .. } = content {
169///     for item in result {
170///         println!("Source: {} - {}", item.title, item.url);
171///     }
172/// }
173/// ```
174#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
175#[serde(default)]
176pub struct GoogleSearchResultItem {
177    /// Title of the search result (often the domain name)
178    pub title: String,
179    /// URL of the source (typically a grounding redirect URL)
180    pub url: String,
181    /// The rendered content from the source (if available)
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub rendered_content: Option<String>,
184}
185
186impl GoogleSearchResultItem {
187    /// Creates a new GoogleSearchResultItem.
188    #[must_use]
189    pub fn new(title: impl Into<String>, url: impl Into<String>) -> Self {
190        Self {
191            title: title.into(),
192            url: url.into(),
193            rendered_content: None,
194        }
195    }
196
197    /// Returns `true` if this result has rendered content.
198    #[must_use]
199    pub fn has_rendered_content(&self) -> bool {
200        self.rendered_content.is_some()
201    }
202}
203
204// =============================================================================
205// URL Context Result Item
206// =============================================================================
207
208/// A single result from a URL Context fetch.
209///
210/// Contains the status of the URL fetch operation.
211///
212/// # Example
213///
214/// ```no_run
215/// # use genai_rs::{Content, UrlContextResultItem};
216/// # let content: Content = todo!();
217/// if let Content::UrlContextResult { result, .. } = content {
218///     for item in result {
219///         println!("URL: {} - Status: {}", item.url, item.status);
220///     }
221/// }
222/// ```
223#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
224#[serde(default)]
225pub struct UrlContextResultItem {
226    /// The URL that was fetched
227    pub url: String,
228    /// Status of the fetch operation (e.g., "success", "error", "unsafe")
229    pub status: String,
230}
231
232impl UrlContextResultItem {
233    /// Creates a new UrlContextResultItem.
234    #[must_use]
235    pub fn new(url: impl Into<String>, status: impl Into<String>) -> Self {
236        Self {
237            url: url.into(),
238            status: status.into(),
239        }
240    }
241
242    /// Returns `true` if the fetch was successful.
243    #[must_use]
244    pub fn is_success(&self) -> bool {
245        self.status == "success"
246    }
247
248    /// Returns `true` if the fetch failed with an error.
249    #[must_use]
250    pub fn is_error(&self) -> bool {
251        self.status == "error"
252    }
253
254    /// Returns `true` if the URL was blocked as unsafe.
255    #[must_use]
256    pub fn is_unsafe(&self) -> bool {
257        self.status == "unsafe"
258    }
259}
260
261// =============================================================================
262// File Search Result Item
263// =============================================================================
264
265/// A single result from a File Search.
266///
267/// Contains the extracted text from a semantic search match, including the title,
268/// text content, and the source file search store.
269///
270/// # Example
271///
272/// ```no_run
273/// # use genai_rs::{Content, FileSearchResultItem};
274/// # let content: Content = todo!();
275/// if let Content::FileSearchResult { result, .. } = content {
276///     for item in result {
277///         println!("Match from '{}': {}", item.store, item.text);
278///     }
279/// }
280/// ```
281#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
282#[serde(default)]
283pub struct FileSearchResultItem {
284    /// Title of the matched document
285    pub title: String,
286    /// Extracted text content from the semantic match
287    pub text: String,
288    /// Name of the file search store containing this result
289    #[serde(rename = "file_search_store")]
290    pub store: String,
291}
292
293impl FileSearchResultItem {
294    /// Creates a new FileSearchResultItem.
295    #[must_use]
296    pub fn new(
297        title: impl Into<String>,
298        text: impl Into<String>,
299        store: impl Into<String>,
300    ) -> Self {
301        Self {
302            title: title.into(),
303            text: text.into(),
304            store: store.into(),
305        }
306    }
307
308    /// Returns `true` if this result has any text content.
309    #[must_use]
310    pub fn has_text(&self) -> bool {
311        !self.text.is_empty()
312    }
313}
314
315/// Programming language for code execution.
316///
317/// This enum represents the programming language used in code execution requests.
318/// Currently only Python is supported by the Gemini API.
319///
320/// # Forward Compatibility (Evergreen Philosophy)
321///
322/// This enum is marked `#[non_exhaustive]`, which means:
323/// - Match statements must include a wildcard arm (`_ => ...`)
324/// - New variants may be added in minor version updates without breaking your code
325///
326/// When the API returns a language value that this library doesn't recognize,
327/// it will be captured as `CodeExecutionLanguage::Unknown` rather than causing a
328/// deserialization error. This follows the
329/// [Evergreen spec](https://github.com/google-deepmind/evergreen-spec)
330/// philosophy of graceful degradation.
331///
332/// # Example
333///
334/// ```no_run
335/// # use genai_rs::{Content, CodeExecutionLanguage};
336/// # let content: Content = todo!();
337/// if let Content::CodeExecutionCall { language, code, .. } = content {
338///     match language {
339///         CodeExecutionLanguage::Python => println!("Python code: {}", code),
340///         CodeExecutionLanguage::Unknown { language_type, .. } => {
341///             println!("Unknown language '{}': {}", language_type, code);
342///         }
343///         _ => println!("Other language: {}", code),
344///     }
345/// }
346/// ```
347#[derive(Clone, Debug, Default, PartialEq, Eq)]
348#[non_exhaustive]
349pub enum CodeExecutionLanguage {
350    /// Python programming language
351    #[default]
352    Python,
353    /// Unknown language (for forward compatibility).
354    ///
355    /// This variant captures any unrecognized language values from the API,
356    /// allowing the library to handle new languages gracefully.
357    ///
358    /// The `language_type` field contains the unrecognized language string,
359    /// and `data` contains the full JSON value for debugging.
360    Unknown {
361        /// The unrecognized language string from the API
362        language_type: String,
363        /// The raw JSON value, preserved for debugging
364        data: serde_json::Value,
365    },
366}
367
368impl CodeExecutionLanguage {
369    /// Check if this is an unknown language.
370    #[must_use]
371    pub const fn is_unknown(&self) -> bool {
372        matches!(self, Self::Unknown { .. })
373    }
374
375    /// Returns the language type name if this is an unknown language.
376    ///
377    /// Returns `None` for known languages.
378    #[must_use]
379    pub fn unknown_language_type(&self) -> Option<&str> {
380        match self {
381            Self::Unknown { language_type, .. } => Some(language_type),
382            _ => None,
383        }
384    }
385
386    /// Returns the raw JSON data if this is an unknown language.
387    ///
388    /// Returns `None` for known languages.
389    #[must_use]
390    pub fn unknown_data(&self) -> Option<&serde_json::Value> {
391        match self {
392            Self::Unknown { data, .. } => Some(data),
393            _ => None,
394        }
395    }
396}
397
398impl Serialize for CodeExecutionLanguage {
399    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
400    where
401        S: serde::Serializer,
402    {
403        match self {
404            Self::Python => serializer.serialize_str("PYTHON"),
405            Self::Unknown { language_type, .. } => serializer.serialize_str(language_type),
406        }
407    }
408}
409
410impl<'de> Deserialize<'de> for CodeExecutionLanguage {
411    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
412    where
413        D: serde::Deserializer<'de>,
414    {
415        let value = serde_json::Value::deserialize(deserializer)?;
416
417        match value.as_str() {
418            Some("PYTHON") => Ok(Self::Python),
419            Some(other) => {
420                tracing::warn!(
421                    "Encountered unknown CodeExecutionLanguage '{}'. \
422                     This may indicate a new API feature. \
423                     The language will be preserved in the Unknown variant.",
424                    other
425                );
426                Ok(Self::Unknown {
427                    language_type: other.to_string(),
428                    data: value,
429                })
430            }
431            None => {
432                // Non-string value - preserve it in Unknown
433                let language_type = format!("<non-string: {}>", value);
434                tracing::warn!(
435                    "CodeExecutionLanguage received non-string value: {}. \
436                     Preserving in Unknown variant.",
437                    value
438                );
439                Ok(Self::Unknown {
440                    language_type,
441                    data: value,
442                })
443            }
444        }
445    }
446}
447
448impl fmt::Display for CodeExecutionLanguage {
449    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450        match self {
451            Self::Python => write!(f, "PYTHON"),
452            Self::Unknown { language_type, .. } => write!(f, "{}", language_type),
453        }
454    }
455}
456
457/// Resolution level for image and video content processing.
458///
459/// Controls the quality vs. token cost trade-off when processing images and videos.
460/// Lower resolution uses fewer tokens (lower cost), while higher resolution provides
461/// more detail for the model to analyze.
462///
463/// # Token Cost Trade-offs
464///
465/// | Resolution | Token Cost | Detail Level |
466/// |------------|------------|--------------|
467/// | Low | Lowest | Basic shapes and colors |
468/// | Medium | Moderate | Standard detail |
469/// | High | Higher | Fine details visible |
470/// | UltraHigh | Highest | Maximum fidelity |
471///
472/// # Forward Compatibility (Evergreen Philosophy)
473///
474/// This enum is marked `#[non_exhaustive]`, which means:
475/// - Match statements must include a wildcard arm (`_ => ...`)
476/// - New variants may be added in minor version updates without breaking your code
477///
478/// When the API returns a resolution value that this library doesn't recognize,
479/// it will be captured as `Resolution::Unknown` rather than causing a
480/// deserialization error. This follows the
481/// [Evergreen spec](https://github.com/google-deepmind/evergreen-spec)
482/// philosophy of graceful degradation.
483///
484/// # Example
485///
486/// ```
487/// use genai_rs::Resolution;
488///
489/// // Use Low for cheap, basic analysis
490/// let low_cost = Resolution::Low;
491///
492/// // Use High for detailed analysis
493/// let detailed = Resolution::High;
494///
495/// // Default is Medium
496/// assert_eq!(Resolution::default(), Resolution::Medium);
497/// ```
498#[derive(Clone, Debug, Default, PartialEq, Eq)]
499#[non_exhaustive]
500pub enum Resolution {
501    /// Lowest token cost, basic shapes and colors
502    Low,
503    /// Moderate token cost, standard detail (default)
504    #[default]
505    Medium,
506    /// Higher token cost, fine details visible
507    High,
508    /// Highest token cost, maximum fidelity
509    UltraHigh,
510    /// Unknown resolution (for forward compatibility).
511    ///
512    /// This variant captures any unrecognized resolution values from the API,
513    /// allowing the library to handle new resolutions gracefully.
514    ///
515    /// The `resolution_type` field contains the unrecognized resolution string,
516    /// and `data` contains the JSON value (typically the same string).
517    Unknown {
518        /// The unrecognized resolution string from the API
519        resolution_type: String,
520        /// The raw JSON value, preserved for debugging
521        data: serde_json::Value,
522    },
523}
524
525impl Resolution {
526    /// Check if this is an unknown resolution.
527    #[must_use]
528    pub const fn is_unknown(&self) -> bool {
529        matches!(self, Self::Unknown { .. })
530    }
531
532    /// Returns the resolution type name if this is an unknown resolution.
533    ///
534    /// Returns `None` for known resolutions.
535    #[must_use]
536    pub fn unknown_resolution_type(&self) -> Option<&str> {
537        match self {
538            Self::Unknown {
539                resolution_type, ..
540            } => Some(resolution_type),
541            _ => None,
542        }
543    }
544
545    /// Returns the raw JSON data if this is an unknown resolution.
546    ///
547    /// Returns `None` for known resolutions.
548    #[must_use]
549    pub fn unknown_data(&self) -> Option<&serde_json::Value> {
550        match self {
551            Self::Unknown { data, .. } => Some(data),
552            _ => None,
553        }
554    }
555}
556
557impl Serialize for Resolution {
558    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
559    where
560        S: serde::Serializer,
561    {
562        match self {
563            Self::Low => serializer.serialize_str("low"),
564            Self::Medium => serializer.serialize_str("medium"),
565            Self::High => serializer.serialize_str("high"),
566            Self::UltraHigh => serializer.serialize_str("ultra_high"),
567            Self::Unknown {
568                resolution_type, ..
569            } => serializer.serialize_str(resolution_type),
570        }
571    }
572}
573
574impl<'de> Deserialize<'de> for Resolution {
575    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
576    where
577        D: serde::Deserializer<'de>,
578    {
579        let value = serde_json::Value::deserialize(deserializer)?;
580
581        match value.as_str() {
582            Some("low") => Ok(Self::Low),
583            Some("medium") => Ok(Self::Medium),
584            Some("high") => Ok(Self::High),
585            Some("ultra_high") => Ok(Self::UltraHigh),
586            Some(other) => {
587                tracing::warn!(
588                    "Encountered unknown Resolution '{}'. \
589                     This may indicate a new API feature. \
590                     The resolution will be preserved in the Unknown variant.",
591                    other
592                );
593                Ok(Self::Unknown {
594                    resolution_type: other.to_string(),
595                    data: value,
596                })
597            }
598            None => {
599                // Non-string value - preserve it in Unknown
600                let resolution_type = format!("<non-string: {}>", value);
601                tracing::warn!(
602                    "Resolution received non-string value: {}. \
603                     Preserving in Unknown variant.",
604                    value
605                );
606                Ok(Self::Unknown {
607                    resolution_type,
608                    data: value,
609                })
610            }
611        }
612    }
613}
614
615impl fmt::Display for Resolution {
616    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
617        match self {
618            Self::Low => write!(f, "low"),
619            Self::Medium => write!(f, "medium"),
620            Self::High => write!(f, "high"),
621            Self::UltraHigh => write!(f, "ultra_high"),
622            Self::Unknown {
623                resolution_type, ..
624            } => write!(f, "{}", resolution_type),
625        }
626    }
627}
628
629/// Content object for Interactions API - uses flat structure with type field.
630///
631/// This enum represents all content types that can appear in API requests and responses.
632/// It includes an `Unknown` variant for forward compatibility with new API content types.
633///
634/// # Forward Compatibility
635///
636/// This enum is marked `#[non_exhaustive]`, which means:
637/// - Match statements must include a wildcard arm (`_ => ...`)
638/// - New variants may be added in minor version updates without breaking your code
639///
640/// When the API returns a content type that this library doesn't recognize, it will be
641/// captured as `Content::Unknown` rather than causing a deserialization error.
642/// This allows your code to continue working even when Google adds new content types.
643///
644/// Use [`super::InteractionResponse::has_unknown`] and [`super::InteractionResponse::unknown_content`]
645/// to detect and inspect unknown content.
646///
647/// # Example
648///
649/// ```no_run
650/// # use genai_rs::{Content, InteractionResponse};
651/// # let response: InteractionResponse = todo!();
652/// for content in &response.outputs {
653///     match content {
654///         Content::Text { text, annotations } => {
655///             println!("Text: {:?}", text);
656///             if let Some(annots) = annotations {
657///                 println!("  {} annotations", annots.len());
658///             }
659///         }
660///         Content::FunctionCall { name, .. } => println!("Function: {}", name),
661///         Content::Unknown { content_type, .. } => {
662///             println!("Unknown content type: {}", content_type);
663///         }
664///         // Required due to #[non_exhaustive] - handles future variants
665///         _ => {}
666///     }
667/// }
668/// ```
669#[derive(Clone, Debug, PartialEq)]
670#[non_exhaustive]
671pub enum Content {
672    /// Text content with optional source annotations.
673    ///
674    /// Annotations are present when grounding tools like `GoogleSearch` or `UrlContext`
675    /// provide citation information linking text spans to their sources.
676    Text {
677        /// The text content.
678        ///
679        /// This is `Option<String>` because during streaming, `content.start` events
680        /// announce the content type before any text arrives. The actual text is
681        /// delivered in subsequent `content.delta` events. For non-streaming responses
682        /// and delta events, this will always be `Some`.
683        text: Option<String>,
684        /// Source annotations for portions of the text.
685        ///
686        /// Present when the response includes citation information from grounding tools.
687        /// Use [`annotations()`](Self::annotations) for convenient access.
688        annotations: Option<Vec<Annotation>>,
689    },
690    /// Thought content (internal reasoning).
691    ///
692    /// Contains a cryptographic signature for verification of the thinking process.
693    /// The actual thought text is not exposed in the API response - only the signature
694    /// which can be used to validate that the response was generated through the
695    /// model's reasoning process.
696    ///
697    /// The `signature` field is `Option<String>` because `content.start` events
698    /// announce the type before the signature arrives via `content.delta`.
699    Thought { signature: Option<String> },
700    /// Thought signature (cryptographic signature for thought verification)
701    ///
702    /// This variant typically appears only during streaming responses, providing
703    /// a cryptographic signature that verifies the authenticity of thought content.
704    ThoughtSignature { signature: String },
705    /// Image content
706    Image {
707        data: Option<String>,
708        uri: Option<String>,
709        mime_type: Option<String>,
710        resolution: Option<Resolution>,
711    },
712    /// Audio content
713    Audio {
714        data: Option<String>,
715        uri: Option<String>,
716        mime_type: Option<String>,
717    },
718    /// Video content
719    Video {
720        data: Option<String>,
721        uri: Option<String>,
722        mime_type: Option<String>,
723        resolution: Option<Resolution>,
724    },
725    /// Document content for file-based inputs.
726    ///
727    /// PDF (`application/pdf`) is the primary supported format with full vision capabilities
728    /// for understanding text, images, charts, and tables. Other formats like TXT, Markdown,
729    /// HTML, and XML are processed as plain text only, losing visual structure.
730    Document {
731        data: Option<String>,
732        uri: Option<String>,
733        mime_type: Option<String>,
734    },
735    /// Function call (output from model)
736    FunctionCall {
737        /// Unique identifier for this function call
738        id: Option<String>,
739        name: String,
740        args: serde_json::Value,
741    },
742    /// Function result (input to model with execution result)
743    FunctionResult {
744        /// Function name (optional per API spec)
745        name: Option<String>,
746        /// The call_id from the FunctionCall being responded to
747        call_id: String,
748        result: serde_json::Value,
749        /// Indicates if the function execution resulted in an error
750        is_error: Option<bool>,
751    },
752    /// Code execution call (model requesting code execution)
753    ///
754    /// This variant appears when the model initiates code execution
755    /// via the `CodeExecution` built-in tool.
756    ///
757    /// # Example
758    ///
759    /// ```no_run
760    /// # use genai_rs::{Content, CodeExecutionLanguage};
761    /// # let content: Content = todo!();
762    /// if let Content::CodeExecutionCall { id, language, code } = content {
763    ///     println!("Executing {:?} code (id: {:?}): {}", language, id, code);
764    /// }
765    /// ```
766    CodeExecutionCall {
767        /// Unique identifier for this code execution call (optional per API spec)
768        id: Option<String>,
769        /// Programming language (currently only Python is supported)
770        language: CodeExecutionLanguage,
771        /// Source code to execute
772        code: String,
773    },
774    /// Code execution result (returned after code runs)
775    ///
776    /// Contains the result of executed code from the `CodeExecution` tool.
777    ///
778    /// # Security Note
779    ///
780    /// When displaying results to end users, check `is_error` first. Error
781    /// results may contain stack traces or system information that shouldn't be exposed
782    /// directly to users without sanitization.
783    ///
784    /// # Example
785    ///
786    /// ```no_run
787    /// # use genai_rs::Content;
788    /// # let content: Content = todo!();
789    /// if let Content::CodeExecutionResult { is_error, result, .. } = content {
790    ///     if is_error {
791    ///         eprintln!("Error: {}", result);
792    ///     } else {
793    ///         println!("Result: {}", result);
794    ///     }
795    /// }
796    /// ```
797    CodeExecutionResult {
798        /// The call_id matching the CodeExecutionCall this result is for (optional per API spec)
799        call_id: Option<String>,
800        /// Whether the code execution resulted in an error
801        is_error: bool,
802        /// The output of the code execution (stdout for success, error message for failure)
803        result: String,
804    },
805    /// Google Search call (model requesting a search)
806    ///
807    /// Appears when the model initiates a Google Search via the `GoogleSearch` tool.
808    /// The model may execute multiple queries in a single call.
809    GoogleSearchCall {
810        /// Unique identifier for this search call (used to match with result)
811        id: String,
812        /// Search queries executed by the model
813        queries: Vec<String>,
814    },
815    /// Google Search result (grounding data from search)
816    ///
817    /// Contains the results returned by the `GoogleSearch` built-in tool.
818    /// Each result includes a title and URL for the source.
819    GoogleSearchResult {
820        /// ID of the corresponding GoogleSearchCall
821        call_id: String,
822        /// Search results with source information
823        result: Vec<GoogleSearchResultItem>,
824    },
825    /// URL Context call (model requesting URL content)
826    ///
827    /// Appears when the model requests URL content via the `UrlContext` tool.
828    UrlContextCall {
829        /// Unique identifier for this URL context call
830        id: String,
831        /// URLs to fetch (extracted from arguments.urls in wire format)
832        urls: Vec<String>,
833    },
834    /// URL Context result (fetched content from URL)
835    ///
836    /// Contains the results from the `UrlContext` built-in tool.
837    UrlContextResult {
838        /// ID of the corresponding UrlContextCall
839        call_id: String,
840        /// Results for each URL that was fetched
841        result: Vec<UrlContextResultItem>,
842    },
843    /// File Search result (semantic search results from document stores)
844    ///
845    /// Contains the results returned by the `FileSearch` built-in tool.
846    /// Each result includes the title, extracted text, and source store name.
847    ///
848    /// # Example
849    ///
850    /// ```no_run
851    /// # use genai_rs::{Content, FileSearchResultItem};
852    /// # let content: Content = todo!();
853    /// if let Content::FileSearchResult { call_id, result } = content {
854    ///     println!("Results for call {}: {} matches", call_id, result.len());
855    ///     for item in result {
856    ///         println!("  {}: {}", item.title, item.text);
857    ///     }
858    /// }
859    /// ```
860    FileSearchResult {
861        /// ID of the corresponding file search call
862        call_id: String,
863        /// Search results with extracted text and source information
864        result: Vec<FileSearchResultItem>,
865    },
866    /// Computer use call (model requesting browser interaction)
867    ///
868    /// Appears when the model initiates browser automation via the `ComputerUse` tool.
869    ///
870    /// # Security Considerations
871    ///
872    /// Computer use calls allow the model to interact with web browsers on your behalf.
873    /// Always review calls before execution in production environments, especially when:
874    /// - Accessing sensitive websites (banking, admin panels)
875    /// - Performing state-changing operations (form submissions, purchases)
876    /// - Working with untrusted user input
877    ///
878    /// # Example
879    ///
880    /// ```no_run
881    /// # use genai_rs::Content;
882    /// # let content: Content = todo!();
883    /// if let Content::ComputerUseCall { id, action, parameters } = content {
884    ///     println!("Browser action '{}' requested (id: {})", action, id);
885    ///     println!("Parameters: {:?}", parameters);
886    /// }
887    /// ```
888    ComputerUseCall {
889        /// Unique identifier for this computer use call
890        id: String,
891        /// The browser action to perform (e.g., "navigate", "click", "type")
892        action: String,
893        /// Action-specific parameters
894        parameters: serde_json::Value,
895    },
896    /// Computer use result (returned after browser interaction)
897    ///
898    /// Contains the outcome of a browser action executed via the `ComputerUse` tool.
899    ///
900    /// # Security Note
901    ///
902    /// Results may contain sensitive information like:
903    /// - Screenshots of the current page
904    /// - DOM content from visited pages
905    /// - Cookie or session data
906    ///
907    /// Sanitize output before displaying to end users.
908    ///
909    /// # Example
910    ///
911    /// ```no_run
912    /// # use genai_rs::Content;
913    /// # let content: Content = todo!();
914    /// if let Content::ComputerUseResult { success, output, error, .. } = content {
915    ///     if success {
916    ///         println!("Action succeeded: {:?}", output);
917    ///     } else {
918    ///         eprintln!("Action failed: {:?}", error);
919    ///     }
920    /// }
921    /// ```
922    ComputerUseResult {
923        /// References the `id` field from the corresponding `ComputerUseCall` variant.
924        call_id: String,
925        /// Whether the action succeeded
926        success: bool,
927        /// Action output data (may include page content, extracted data, etc.)
928        output: Option<serde_json::Value>,
929        /// Error message if action failed
930        error: Option<String>,
931        /// Optional screenshot data (base64-encoded image)
932        screenshot: Option<String>,
933    },
934    /// Unknown content type for forward compatibility.
935    ///
936    /// This variant captures content types that the library doesn't recognize yet.
937    /// This can happen when Google adds new features to the API before this library
938    /// is updated to support them.
939    ///
940    /// The `content_type` field contains the unrecognized type string from the API,
941    /// and `data` contains the full JSON object for inspection or debugging.
942    ///
943    /// # When This Occurs
944    ///
945    /// - New API features not yet supported by this library
946    /// - Beta features in testing
947    /// - Region-specific content types
948    ///
949    /// # Example
950    ///
951    /// ```no_run
952    /// # use genai_rs::Content;
953    /// # let content: Content = todo!();
954    /// if let Content::Unknown { content_type, data } = content {
955    ///     eprintln!("Encountered unknown type '{}': {:?}", content_type, data);
956    /// }
957    /// ```
958    ///
959    /// # Serialization Behavior
960    ///
961    /// Unknown variants can be serialized back to JSON, enabling round-trip in
962    /// multi-turn conversations. The serialization follows these rules:
963    ///
964    /// 1. **Type field**: The `content_type` becomes the `"type"` field in output
965    /// 2. **Object data**: If `data` is a JSON object, its fields are flattened
966    ///    into the output (excluding any existing `"type"` field to avoid duplicates)
967    /// 3. **Non-object data**: If `data` is a non-object value (array, string, etc.),
968    ///    it's placed under a `"data"` key
969    /// 4. **Null data**: Omitted entirely from the output
970    ///
971    /// ## Example: Object Data (Common Case)
972    ///
973    /// ```
974    /// # use genai_rs::Content;
975    /// # use serde_json::json;
976    /// let content = Content::Unknown {
977    ///     content_type: "new_feature".to_string(),
978    ///     data: json!({"field1": "value1", "field2": 42}),
979    /// };
980    /// // Serializes to: {"type": "new_feature", "field1": "value1", "field2": 42}
981    /// ```
982    ///
983    /// ## Example: Duplicate Type Field
984    ///
985    /// If `data` contains a `"type"` field, it's excluded during serialization
986    /// (the `content_type` takes precedence):
987    ///
988    /// ```
989    /// # use genai_rs::Content;
990    /// # use serde_json::json;
991    /// let content = Content::Unknown {
992    ///     content_type: "my_type".to_string(),
993    ///     data: json!({"type": "ignored", "value": 123}),
994    /// };
995    /// // Serializes to: {"type": "my_type", "value": 123}
996    /// // Note: "type": "ignored" is not included
997    /// ```
998    ///
999    /// ## Example: Non-Object Data
1000    ///
1001    /// ```
1002    /// # use genai_rs::Content;
1003    /// # use serde_json::json;
1004    /// let content = Content::Unknown {
1005    ///     content_type: "array_type".to_string(),
1006    ///     data: json!([1, 2, 3]),
1007    /// };
1008    /// // Serializes to: {"type": "array_type", "data": [1, 2, 3]}
1009    /// ```
1010    ///
1011    /// # Field Ordering
1012    ///
1013    /// **Note:** Field ordering is not preserved during round-trip serialization,
1014    /// but all field **values** are fully preserved. When serializing an `Unknown`
1015    /// variant, the `"type"` field is always written first, followed by the remaining
1016    /// fields from `data`. This means the output field order may differ from the
1017    /// original API response.
1018    ///
1019    /// This has **no practical impact** on API compatibility because JSON objects
1020    /// are inherently unordered per RFC 8259. The Gemini API does not depend on
1021    /// field ordering.
1022    ///
1023    /// If you need to preserve the exact original field ordering (e.g., for logging
1024    /// or debugging purposes), access the raw `data` field directly via
1025    /// [`unknown_data()`](Self::unknown_data) instead of re-serializing the variant.
1026    ///
1027    /// # Manual Construction
1028    ///
1029    /// While Unknown variants are typically created by deserialization, you can
1030    /// construct them manually for testing or edge cases. Note that:
1031    ///
1032    /// - The `content_type` can be any string (including empty, though not recommended)
1033    /// - The `data` can be any valid JSON value
1034    /// - For multi-turn conversations, the serialized form must match what the API expects
1035    Unknown {
1036        /// The unrecognized type name from the API
1037        content_type: String,
1038        /// The full JSON data for this content, preserved for debugging
1039        data: serde_json::Value,
1040    },
1041}
1042
1043// Custom Serialize implementation for Content.
1044// This handles the Unknown variant specially by merging content_type into the data.
1045impl Serialize for Content {
1046    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1047    where
1048        S: serde::Serializer,
1049    {
1050        use serde::ser::SerializeMap;
1051
1052        match self {
1053            Self::Text { text, annotations } => {
1054                let mut map = serializer.serialize_map(None)?;
1055                map.serialize_entry("type", "text")?;
1056                if let Some(t) = text {
1057                    map.serialize_entry("text", t)?;
1058                }
1059                if let Some(annots) = annotations
1060                    && !annots.is_empty()
1061                {
1062                    map.serialize_entry("annotations", annots)?;
1063                }
1064                map.end()
1065            }
1066            Self::Thought { signature } => {
1067                let mut map = serializer.serialize_map(None)?;
1068                map.serialize_entry("type", "thought")?;
1069                if let Some(s) = signature {
1070                    map.serialize_entry("signature", s)?;
1071                }
1072                map.end()
1073            }
1074            Self::ThoughtSignature { signature } => {
1075                let mut map = serializer.serialize_map(None)?;
1076                map.serialize_entry("type", "thought_signature")?;
1077                map.serialize_entry("signature", signature)?;
1078                map.end()
1079            }
1080            Self::Image {
1081                data,
1082                uri,
1083                mime_type,
1084                resolution,
1085            } => {
1086                let mut map = serializer.serialize_map(None)?;
1087                map.serialize_entry("type", "image")?;
1088                if let Some(d) = data {
1089                    map.serialize_entry("data", d)?;
1090                }
1091                if let Some(u) = uri {
1092                    map.serialize_entry("uri", u)?;
1093                }
1094                if let Some(m) = mime_type {
1095                    map.serialize_entry("mime_type", m)?;
1096                }
1097                if let Some(r) = resolution {
1098                    map.serialize_entry("resolution", r)?;
1099                }
1100                map.end()
1101            }
1102            Self::Audio {
1103                data,
1104                uri,
1105                mime_type,
1106            } => {
1107                let mut map = serializer.serialize_map(None)?;
1108                map.serialize_entry("type", "audio")?;
1109                if let Some(d) = data {
1110                    map.serialize_entry("data", d)?;
1111                }
1112                if let Some(u) = uri {
1113                    map.serialize_entry("uri", u)?;
1114                }
1115                if let Some(m) = mime_type {
1116                    map.serialize_entry("mime_type", m)?;
1117                }
1118                map.end()
1119            }
1120            Self::Video {
1121                data,
1122                uri,
1123                mime_type,
1124                resolution,
1125            } => {
1126                let mut map = serializer.serialize_map(None)?;
1127                map.serialize_entry("type", "video")?;
1128                if let Some(d) = data {
1129                    map.serialize_entry("data", d)?;
1130                }
1131                if let Some(u) = uri {
1132                    map.serialize_entry("uri", u)?;
1133                }
1134                if let Some(m) = mime_type {
1135                    map.serialize_entry("mime_type", m)?;
1136                }
1137                if let Some(r) = resolution {
1138                    map.serialize_entry("resolution", r)?;
1139                }
1140                map.end()
1141            }
1142            Self::Document {
1143                data,
1144                uri,
1145                mime_type,
1146            } => {
1147                let mut map = serializer.serialize_map(None)?;
1148                map.serialize_entry("type", "document")?;
1149                if let Some(d) = data {
1150                    map.serialize_entry("data", d)?;
1151                }
1152                if let Some(u) = uri {
1153                    map.serialize_entry("uri", u)?;
1154                }
1155                if let Some(m) = mime_type {
1156                    map.serialize_entry("mime_type", m)?;
1157                }
1158                map.end()
1159            }
1160            Self::FunctionCall { id, name, args } => {
1161                let mut map = serializer.serialize_map(None)?;
1162                map.serialize_entry("type", "function_call")?;
1163                if let Some(i) = id {
1164                    map.serialize_entry("id", i)?;
1165                }
1166                map.serialize_entry("name", name)?;
1167                map.serialize_entry("arguments", args)?;
1168                map.end()
1169            }
1170            Self::FunctionResult {
1171                name,
1172                call_id,
1173                result,
1174                is_error,
1175            } => {
1176                let mut map = serializer.serialize_map(None)?;
1177                map.serialize_entry("type", "function_result")?;
1178                if let Some(n) = name {
1179                    map.serialize_entry("name", n)?;
1180                }
1181                map.serialize_entry("call_id", call_id)?;
1182                map.serialize_entry("result", result)?;
1183                if let Some(err) = is_error {
1184                    map.serialize_entry("is_error", err)?;
1185                }
1186                map.end()
1187            }
1188            Self::CodeExecutionCall { id, language, code } => {
1189                let mut map = serializer.serialize_map(None)?;
1190                map.serialize_entry("type", "code_execution_call")?;
1191                if let Some(i) = id {
1192                    map.serialize_entry("id", i)?;
1193                }
1194                // Wire format nests language and code inside arguments object
1195                let arguments = serde_json::json!({
1196                    "language": language,
1197                    "code": code
1198                });
1199                map.serialize_entry("arguments", &arguments)?;
1200                map.end()
1201            }
1202            Self::CodeExecutionResult {
1203                call_id,
1204                is_error,
1205                result,
1206            } => {
1207                let mut map = serializer.serialize_map(None)?;
1208                map.serialize_entry("type", "code_execution_result")?;
1209                if let Some(cid) = call_id {
1210                    map.serialize_entry("call_id", cid)?;
1211                }
1212                map.serialize_entry("is_error", is_error)?;
1213                map.serialize_entry("result", result)?;
1214                map.end()
1215            }
1216            Self::GoogleSearchCall { id, queries } => {
1217                let mut map = serializer.serialize_map(None)?;
1218                map.serialize_entry("type", "google_search_call")?;
1219                map.serialize_entry("id", id)?;
1220                // Serialize as nested arguments.queries to match API format
1221                let arguments = serde_json::json!({ "queries": queries });
1222                map.serialize_entry("arguments", &arguments)?;
1223                map.end()
1224            }
1225            Self::GoogleSearchResult { call_id, result } => {
1226                let mut map = serializer.serialize_map(None)?;
1227                map.serialize_entry("type", "google_search_result")?;
1228                map.serialize_entry("call_id", call_id)?;
1229                map.serialize_entry("result", result)?;
1230                map.end()
1231            }
1232            Self::UrlContextCall { id, urls } => {
1233                let mut map = serializer.serialize_map(None)?;
1234                map.serialize_entry("type", "url_context_call")?;
1235                map.serialize_entry("id", id)?;
1236                // Wire format nests urls inside arguments object
1237                let arguments = serde_json::json!({ "urls": urls });
1238                map.serialize_entry("arguments", &arguments)?;
1239                map.end()
1240            }
1241            Self::UrlContextResult { call_id, result } => {
1242                let mut map = serializer.serialize_map(None)?;
1243                map.serialize_entry("type", "url_context_result")?;
1244                map.serialize_entry("call_id", call_id)?;
1245                map.serialize_entry("result", result)?;
1246                map.end()
1247            }
1248            Self::FileSearchResult { call_id, result } => {
1249                let mut map = serializer.serialize_map(None)?;
1250                map.serialize_entry("type", "file_search_result")?;
1251                map.serialize_entry("call_id", call_id)?;
1252                map.serialize_entry("result", result)?;
1253                map.end()
1254            }
1255            Self::ComputerUseCall {
1256                id,
1257                action,
1258                parameters,
1259            } => {
1260                let mut map = serializer.serialize_map(None)?;
1261                map.serialize_entry("type", "computer_use_call")?;
1262                map.serialize_entry("id", id)?;
1263                map.serialize_entry("action", action)?;
1264                map.serialize_entry("parameters", parameters)?;
1265                map.end()
1266            }
1267            Self::ComputerUseResult {
1268                call_id,
1269                success,
1270                output,
1271                error,
1272                screenshot,
1273            } => {
1274                let mut map = serializer.serialize_map(None)?;
1275                map.serialize_entry("type", "computer_use_result")?;
1276                map.serialize_entry("call_id", call_id)?;
1277                map.serialize_entry("success", success)?;
1278                if let Some(out) = output {
1279                    map.serialize_entry("output", out)?;
1280                }
1281                if let Some(err) = error {
1282                    map.serialize_entry("error", err)?;
1283                }
1284                if let Some(ss) = screenshot {
1285                    map.serialize_entry("screenshot", ss)?;
1286                }
1287                map.end()
1288            }
1289            Self::Unknown { content_type, data } => {
1290                // For Unknown, merge the content_type into the data object
1291                let mut map = serializer.serialize_map(None)?;
1292                map.serialize_entry("type", content_type)?;
1293                // Flatten the data fields into the map if it's an object
1294                match data {
1295                    serde_json::Value::Object(obj) => {
1296                        for (key, value) in obj {
1297                            if key != "type" {
1298                                // Don't duplicate the type field
1299                                map.serialize_entry(key, value)?;
1300                            }
1301                        }
1302                    }
1303                    // For non-object data (unlikely but possible), preserve under "data" key
1304                    other if !other.is_null() => {
1305                        map.serialize_entry("data", other)?;
1306                    }
1307                    _ => {} // Null data is omitted
1308                }
1309                map.end()
1310            }
1311        }
1312    }
1313}
1314
1315impl Content {
1316    /// Extract the text content, if this is a Text variant with non-empty text.
1317    ///
1318    /// Returns `Some` only for `Text` variants with non-empty text.
1319    /// Returns `None` for all other variants including `Thought`.
1320    #[must_use]
1321    pub fn as_text(&self) -> Option<&str> {
1322        match self {
1323            Self::Text { text: Some(t), .. } if !t.is_empty() => Some(t),
1324            _ => None,
1325        }
1326    }
1327
1328    /// Returns annotations if this is Text content with annotations.
1329    ///
1330    /// Returns `Some` with a slice of annotations only for `Text` variants that
1331    /// have non-empty annotations. Returns `None` for all other variants.
1332    ///
1333    /// Annotations are typically present when using grounding tools like
1334    /// `GoogleSearch` or `UrlContext`.
1335    ///
1336    /// # Example
1337    ///
1338    /// ```no_run
1339    /// # use genai_rs::{Content, Annotation};
1340    /// # let content: Content = todo!();
1341    /// if let Some(annotations) = content.annotations() {
1342    ///     for annotation in annotations {
1343    ///         println!("Source: {:?} for bytes {}..{}",
1344    ///             annotation.source,
1345    ///             annotation.start_index,
1346    ///             annotation.end_index);
1347    ///     }
1348    /// }
1349    /// ```
1350    #[must_use]
1351    pub fn annotations(&self) -> Option<&[Annotation]> {
1352        match self {
1353            Self::Text {
1354                annotations: Some(annots),
1355                ..
1356            } if !annots.is_empty() => Some(annots),
1357            _ => None,
1358        }
1359    }
1360
1361    /// Extract the thought signature, if this is a Thought variant with a signature.
1362    ///
1363    /// The signature is a cryptographic value used for verification of the thinking
1364    /// process. The actual thought text is not exposed in API responses.
1365    ///
1366    /// Returns `Some` only for `Thought` variants with a non-empty signature.
1367    /// Returns `None` for all other variants including `ThoughtSignature`.
1368    #[must_use]
1369    pub fn thought_signature(&self) -> Option<&str> {
1370        match self {
1371            Self::Thought { signature: Some(s) } if !s.is_empty() => Some(s),
1372            _ => None,
1373        }
1374    }
1375
1376    /// Check if this is a Text content type.
1377    #[must_use]
1378    pub const fn is_text(&self) -> bool {
1379        matches!(self, Self::Text { .. })
1380    }
1381
1382    /// Check if this is a Thought content type.
1383    #[must_use]
1384    pub const fn is_thought(&self) -> bool {
1385        matches!(self, Self::Thought { .. })
1386    }
1387
1388    /// Check if this is a ThoughtSignature content type.
1389    #[must_use]
1390    pub const fn is_thought_signature(&self) -> bool {
1391        matches!(self, Self::ThoughtSignature { .. })
1392    }
1393
1394    /// Check if this is a FunctionCall content type.
1395    #[must_use]
1396    pub const fn is_function_call(&self) -> bool {
1397        matches!(self, Self::FunctionCall { .. })
1398    }
1399
1400    /// Returns `true` if this is an unknown content type.
1401    ///
1402    /// Use this to check for content types that the library doesn't recognize.
1403    /// See [`Content::Unknown`] for more details.
1404    #[must_use]
1405    pub const fn is_unknown(&self) -> bool {
1406        matches!(self, Self::Unknown { .. })
1407    }
1408
1409    /// Check if this is a CodeExecutionCall content type.
1410    #[must_use]
1411    pub const fn is_code_execution_call(&self) -> bool {
1412        matches!(self, Self::CodeExecutionCall { .. })
1413    }
1414
1415    /// Check if this is a CodeExecutionResult content type.
1416    #[must_use]
1417    pub const fn is_code_execution_result(&self) -> bool {
1418        matches!(self, Self::CodeExecutionResult { .. })
1419    }
1420
1421    /// Check if this is a GoogleSearchCall content type.
1422    #[must_use]
1423    pub const fn is_google_search_call(&self) -> bool {
1424        matches!(self, Self::GoogleSearchCall { .. })
1425    }
1426
1427    /// Check if this is a GoogleSearchResult content type.
1428    #[must_use]
1429    pub const fn is_google_search_result(&self) -> bool {
1430        matches!(self, Self::GoogleSearchResult { .. })
1431    }
1432
1433    /// Check if this is a FileSearchResult content type.
1434    #[must_use]
1435    pub const fn is_file_search_result(&self) -> bool {
1436        matches!(self, Self::FileSearchResult { .. })
1437    }
1438
1439    /// Check if this is a UrlContextCall content type.
1440    #[must_use]
1441    pub const fn is_url_context_call(&self) -> bool {
1442        matches!(self, Self::UrlContextCall { .. })
1443    }
1444
1445    /// Check if this is a UrlContextResult content type.
1446    #[must_use]
1447    pub const fn is_url_context_result(&self) -> bool {
1448        matches!(self, Self::UrlContextResult { .. })
1449    }
1450
1451    /// Check if this is a ComputerUseCall content type.
1452    #[must_use]
1453    pub const fn is_computer_use_call(&self) -> bool {
1454        matches!(self, Self::ComputerUseCall { .. })
1455    }
1456
1457    /// Check if this is a ComputerUseResult content type.
1458    #[must_use]
1459    pub const fn is_computer_use_result(&self) -> bool {
1460        matches!(self, Self::ComputerUseResult { .. })
1461    }
1462
1463    /// Returns the content type name if this is an unknown content type.
1464    ///
1465    /// Returns `None` for known content types.
1466    #[must_use]
1467    pub fn unknown_content_type(&self) -> Option<&str> {
1468        match self {
1469            Self::Unknown { content_type, .. } => Some(content_type),
1470            _ => None,
1471        }
1472    }
1473
1474    /// Returns the raw JSON data if this is an unknown content type.
1475    ///
1476    /// Returns `None` for known content types.
1477    #[must_use]
1478    pub fn unknown_data(&self) -> Option<&serde_json::Value> {
1479        match self {
1480            Self::Unknown { data, .. } => Some(data),
1481            _ => None,
1482        }
1483    }
1484
1485    // =========================================================================
1486    // Content Constructors
1487    // =========================================================================
1488    //
1489    // These associated functions create Content variants for sending
1490    // to the API. They replace the standalone functions in interactions_api.rs.
1491
1492    /// Creates text content.
1493    ///
1494    /// # Example
1495    ///
1496    /// ```
1497    /// use genai_rs::Content;
1498    ///
1499    /// let content = Content::text("Hello, world!");
1500    /// assert!(content.is_text());
1501    /// ```
1502    #[must_use]
1503    pub fn text(text: impl Into<String>) -> Self {
1504        Self::Text {
1505            text: Some(text.into()),
1506            annotations: None,
1507        }
1508    }
1509
1510    /// Creates thought content with a signature.
1511    ///
1512    /// **Note:** Thought content is typically OUTPUT from the model, not user input.
1513    /// The signature is a cryptographic value for verification. This constructor
1514    /// is provided for completeness but rarely needed in typical usage.
1515    ///
1516    /// # Example
1517    ///
1518    /// ```
1519    /// use genai_rs::Content;
1520    ///
1521    /// let thought = Content::thought("signature_value_here");
1522    /// assert!(thought.is_thought());
1523    /// ```
1524    #[must_use]
1525    pub fn thought(signature: impl Into<String>) -> Self {
1526        Self::Thought {
1527            signature: Some(signature.into()),
1528        }
1529    }
1530
1531    /// Creates a function call content with an optional ID.
1532    ///
1533    /// Use this when you need to specify a call ID, typically when echoing function calls back
1534    /// in manual conversation construction.
1535    ///
1536    /// # Example
1537    ///
1538    /// ```
1539    /// use genai_rs::Content;
1540    /// use serde_json::json;
1541    ///
1542    /// let call = Content::function_call_with_id(
1543    ///     Some("call_123"),
1544    ///     "get_weather",
1545    ///     json!({"location": "San Francisco"})
1546    /// );
1547    /// assert!(call.is_function_call());
1548    /// ```
1549    #[must_use]
1550    pub fn function_call_with_id(
1551        id: Option<impl Into<String>>,
1552        name: impl Into<String>,
1553        args: serde_json::Value,
1554    ) -> Self {
1555        Self::FunctionCall {
1556            id: id.map(|s| s.into()),
1557            name: name.into(),
1558            args,
1559        }
1560    }
1561
1562    /// Creates a function call content (without call ID).
1563    ///
1564    /// # Example
1565    ///
1566    /// ```
1567    /// use genai_rs::Content;
1568    /// use serde_json::json;
1569    ///
1570    /// let call = Content::function_call(
1571    ///     "get_weather",
1572    ///     json!({"location": "San Francisco"})
1573    /// );
1574    /// assert!(call.is_function_call());
1575    /// ```
1576    #[must_use]
1577    pub fn function_call(name: impl Into<String>, args: serde_json::Value) -> Self {
1578        Self::function_call_with_id(None::<String>, name, args)
1579    }
1580
1581    /// Creates a function result content.
1582    ///
1583    /// This is the correct way to send function execution results back to the Interactions API.
1584    /// The call_id must match the id from the FunctionCall you're responding to.
1585    ///
1586    /// # Panics
1587    ///
1588    /// Will log a warning if call_id is empty or whitespace-only, as this may cause
1589    /// API errors when the server tries to match the result to a function call.
1590    ///
1591    /// # Example
1592    ///
1593    /// ```
1594    /// use genai_rs::Content;
1595    /// use serde_json::json;
1596    ///
1597    /// let result = Content::function_result(
1598    ///     "get_weather",
1599    ///     "call_abc123",
1600    ///     json!({"temperature": "72F", "conditions": "sunny"})
1601    /// );
1602    /// ```
1603    #[must_use]
1604    pub fn function_result(
1605        name: impl Into<String>,
1606        call_id: impl Into<String>,
1607        result: serde_json::Value,
1608    ) -> Self {
1609        let function_name = name.into();
1610        let call_id_str = call_id.into();
1611
1612        // Validate call_id is not empty
1613        if call_id_str.trim().is_empty() {
1614            tracing::warn!(
1615                "Empty call_id provided for function result '{}'. \
1616                 This may cause the API to fail to match the result to its function call.",
1617                function_name
1618            );
1619        }
1620
1621        Self::FunctionResult {
1622            name: Some(function_name),
1623            call_id: call_id_str,
1624            result,
1625            is_error: None,
1626        }
1627    }
1628
1629    /// Creates function result content indicating an error.
1630    ///
1631    /// Use this when function execution fails and you need to report the error
1632    /// back to the model.
1633    ///
1634    /// # Example
1635    ///
1636    /// ```
1637    /// use genai_rs::Content;
1638    /// use serde_json::json;
1639    ///
1640    /// let error_result = Content::function_result_error(
1641    ///     "get_weather",
1642    ///     "call_abc123",
1643    ///     json!({"error": "API rate limit exceeded"})
1644    /// );
1645    /// ```
1646    #[must_use]
1647    pub fn function_result_error(
1648        name: impl Into<String>,
1649        call_id: impl Into<String>,
1650        result: serde_json::Value,
1651    ) -> Self {
1652        let function_name = name.into();
1653        let call_id_str = call_id.into();
1654
1655        Self::FunctionResult {
1656            name: Some(function_name),
1657            call_id: call_id_str,
1658            result,
1659            is_error: Some(true),
1660        }
1661    }
1662
1663    /// Creates image content from base64-encoded data.
1664    ///
1665    /// # Example
1666    ///
1667    /// ```
1668    /// use genai_rs::Content;
1669    ///
1670    /// let image = Content::image_data(
1671    ///     "base64encodeddata...",
1672    ///     "image/png"
1673    /// );
1674    /// ```
1675    #[must_use]
1676    pub fn image_data(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
1677        Self::Image {
1678            data: Some(data.into()),
1679            uri: None,
1680            mime_type: Some(mime_type.into()),
1681            resolution: None,
1682        }
1683    }
1684
1685    /// Creates image content from base64-encoded data with specified resolution.
1686    ///
1687    /// # Resolution Trade-offs
1688    ///
1689    /// | Level | Token Cost | Detail |
1690    /// |-------|-----------|--------|
1691    /// | Low | Lowest | Basic shapes and colors |
1692    /// | Medium | Moderate | Standard detail |
1693    /// | High | Higher | Fine details visible |
1694    /// | UltraHigh | Highest | Maximum fidelity |
1695    ///
1696    /// # Example
1697    ///
1698    /// ```
1699    /// use genai_rs::{Content, Resolution};
1700    ///
1701    /// let image = Content::image_data_with_resolution(
1702    ///     "base64encodeddata...",
1703    ///     "image/png",
1704    ///     Resolution::High
1705    /// );
1706    /// ```
1707    #[must_use]
1708    pub fn image_data_with_resolution(
1709        data: impl Into<String>,
1710        mime_type: impl Into<String>,
1711        resolution: Resolution,
1712    ) -> Self {
1713        Self::Image {
1714            data: Some(data.into()),
1715            uri: None,
1716            mime_type: Some(mime_type.into()),
1717            resolution: Some(resolution),
1718        }
1719    }
1720
1721    /// Creates image content from a URI.
1722    ///
1723    /// # Arguments
1724    ///
1725    /// * `uri` - The URI of the image
1726    /// * `mime_type` - The MIME type (required by the API for URI-based content)
1727    ///
1728    /// # Example
1729    ///
1730    /// ```
1731    /// use genai_rs::Content;
1732    ///
1733    /// let image = Content::image_uri(
1734    ///     "https://example.com/image.png",
1735    ///     "image/png"
1736    /// );
1737    /// ```
1738    #[must_use]
1739    pub fn image_uri(uri: impl Into<String>, mime_type: impl Into<String>) -> Self {
1740        Self::Image {
1741            data: None,
1742            uri: Some(uri.into()),
1743            mime_type: Some(mime_type.into()),
1744            resolution: None,
1745        }
1746    }
1747
1748    /// Creates image content from a URI with specified resolution.
1749    ///
1750    /// # Example
1751    ///
1752    /// ```
1753    /// use genai_rs::{Content, Resolution};
1754    ///
1755    /// let image = Content::image_uri_with_resolution(
1756    ///     "https://example.com/image.png",
1757    ///     "image/png",
1758    ///     Resolution::Low  // Use low resolution to reduce token cost
1759    /// );
1760    /// ```
1761    #[must_use]
1762    pub fn image_uri_with_resolution(
1763        uri: impl Into<String>,
1764        mime_type: impl Into<String>,
1765        resolution: Resolution,
1766    ) -> Self {
1767        Self::Image {
1768            data: None,
1769            uri: Some(uri.into()),
1770            mime_type: Some(mime_type.into()),
1771            resolution: Some(resolution),
1772        }
1773    }
1774
1775    /// Creates audio content from base64-encoded data.
1776    ///
1777    /// # Example
1778    ///
1779    /// ```
1780    /// use genai_rs::Content;
1781    ///
1782    /// let audio = Content::audio_data(
1783    ///     "base64encodeddata...",
1784    ///     "audio/mp3"
1785    /// );
1786    /// ```
1787    #[must_use]
1788    pub fn audio_data(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
1789        Self::Audio {
1790            data: Some(data.into()),
1791            uri: None,
1792            mime_type: Some(mime_type.into()),
1793        }
1794    }
1795
1796    /// Creates audio content from a URI.
1797    ///
1798    /// # Arguments
1799    ///
1800    /// * `uri` - The URI of the audio file
1801    /// * `mime_type` - The MIME type (required by the API for URI-based content)
1802    ///
1803    /// # Example
1804    ///
1805    /// ```
1806    /// use genai_rs::Content;
1807    ///
1808    /// let audio = Content::audio_uri(
1809    ///     "https://example.com/audio.mp3",
1810    ///     "audio/mp3"
1811    /// );
1812    /// ```
1813    #[must_use]
1814    pub fn audio_uri(uri: impl Into<String>, mime_type: impl Into<String>) -> Self {
1815        Self::Audio {
1816            data: None,
1817            uri: Some(uri.into()),
1818            mime_type: Some(mime_type.into()),
1819        }
1820    }
1821
1822    /// Creates video content from base64-encoded data.
1823    ///
1824    /// # Example
1825    ///
1826    /// ```
1827    /// use genai_rs::Content;
1828    ///
1829    /// let video = Content::video_data(
1830    ///     "base64encodeddata...",
1831    ///     "video/mp4"
1832    /// );
1833    /// ```
1834    #[must_use]
1835    pub fn video_data(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
1836        Self::Video {
1837            data: Some(data.into()),
1838            uri: None,
1839            mime_type: Some(mime_type.into()),
1840            resolution: None,
1841        }
1842    }
1843
1844    /// Creates video content from base64-encoded data with specified resolution.
1845    ///
1846    /// # Example
1847    ///
1848    /// ```
1849    /// use genai_rs::{Content, Resolution};
1850    ///
1851    /// let video = Content::video_data_with_resolution(
1852    ///     "base64encodeddata...",
1853    ///     "video/mp4",
1854    ///     Resolution::Low  // Use low resolution to reduce token cost for long videos
1855    /// );
1856    /// ```
1857    #[must_use]
1858    pub fn video_data_with_resolution(
1859        data: impl Into<String>,
1860        mime_type: impl Into<String>,
1861        resolution: Resolution,
1862    ) -> Self {
1863        Self::Video {
1864            data: Some(data.into()),
1865            uri: None,
1866            mime_type: Some(mime_type.into()),
1867            resolution: Some(resolution),
1868        }
1869    }
1870
1871    /// Creates video content from a URI.
1872    ///
1873    /// # Arguments
1874    ///
1875    /// * `uri` - The URI of the video file
1876    /// * `mime_type` - The MIME type (required by the API for URI-based content)
1877    ///
1878    /// # Example
1879    ///
1880    /// ```
1881    /// use genai_rs::Content;
1882    ///
1883    /// let video = Content::video_uri(
1884    ///     "https://example.com/video.mp4",
1885    ///     "video/mp4"
1886    /// );
1887    /// ```
1888    #[must_use]
1889    pub fn video_uri(uri: impl Into<String>, mime_type: impl Into<String>) -> Self {
1890        Self::Video {
1891            data: None,
1892            uri: Some(uri.into()),
1893            mime_type: Some(mime_type.into()),
1894            resolution: None,
1895        }
1896    }
1897
1898    /// Creates video content from a URI with specified resolution.
1899    ///
1900    /// # Example
1901    ///
1902    /// ```
1903    /// use genai_rs::{Content, Resolution};
1904    ///
1905    /// let video = Content::video_uri_with_resolution(
1906    ///     "https://example.com/video.mp4",
1907    ///     "video/mp4",
1908    ///     Resolution::Medium
1909    /// );
1910    /// ```
1911    #[must_use]
1912    pub fn video_uri_with_resolution(
1913        uri: impl Into<String>,
1914        mime_type: impl Into<String>,
1915        resolution: Resolution,
1916    ) -> Self {
1917        Self::Video {
1918            data: None,
1919            uri: Some(uri.into()),
1920            mime_type: Some(mime_type.into()),
1921            resolution: Some(resolution),
1922        }
1923    }
1924
1925    /// Creates document content from base64-encoded data.
1926    ///
1927    /// Use this for PDF files and other document formats.
1928    ///
1929    /// # Example
1930    ///
1931    /// ```
1932    /// use genai_rs::Content;
1933    ///
1934    /// let document = Content::document_data(
1935    ///     "base64encodeddata...",
1936    ///     "application/pdf"
1937    /// );
1938    /// ```
1939    #[must_use]
1940    pub fn document_data(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
1941        Self::Document {
1942            data: Some(data.into()),
1943            uri: None,
1944            mime_type: Some(mime_type.into()),
1945        }
1946    }
1947
1948    /// Creates document content from a URI.
1949    ///
1950    /// Use this for PDF files and other document formats accessible via URI.
1951    ///
1952    /// # Arguments
1953    ///
1954    /// * `uri` - The URI of the document
1955    /// * `mime_type` - The MIME type (required by the API for URI-based content)
1956    ///
1957    /// # Example
1958    ///
1959    /// ```
1960    /// use genai_rs::Content;
1961    ///
1962    /// let document = Content::document_uri(
1963    ///     "https://example.com/document.pdf",
1964    ///     "application/pdf"
1965    /// );
1966    /// ```
1967    #[must_use]
1968    pub fn document_uri(uri: impl Into<String>, mime_type: impl Into<String>) -> Self {
1969        Self::Document {
1970            data: None,
1971            uri: Some(uri.into()),
1972            mime_type: Some(mime_type.into()),
1973        }
1974    }
1975
1976    /// Creates content from a URI and MIME type.
1977    ///
1978    /// The content type is inferred from the MIME type:
1979    ///
1980    /// - `image/*` → [`Content::Image`]
1981    /// - `audio/*` → [`Content::Audio`]
1982    /// - `video/*` → [`Content::Video`]
1983    /// - Other MIME types (including `application/*`, `text/*`) → [`Content::Document`]
1984    ///
1985    /// # Arguments
1986    ///
1987    /// * `uri` - The file URI (typically from the Files API)
1988    /// * `mime_type` - The MIME type of the file
1989    ///
1990    /// # Example
1991    ///
1992    /// ```
1993    /// use genai_rs::Content;
1994    ///
1995    /// // Creates Image variant for image MIME types
1996    /// let image = Content::from_uri_and_mime(
1997    ///     "files/abc123",
1998    ///     "image/png"
1999    /// );
2000    ///
2001    /// // Creates Document variant for PDF
2002    /// let doc = Content::from_uri_and_mime(
2003    ///     "files/def456",
2004    ///     "application/pdf"
2005    /// );
2006    /// ```
2007    #[must_use]
2008    pub fn from_uri_and_mime(uri: impl Into<String>, mime_type: impl Into<String>) -> Self {
2009        let uri_str = uri.into();
2010        let mime_str = mime_type.into();
2011
2012        // Choose the appropriate content type based on MIME type prefix
2013        if mime_str.starts_with("image/") {
2014            Self::Image {
2015                data: None,
2016                uri: Some(uri_str),
2017                mime_type: Some(mime_str),
2018                resolution: None,
2019            }
2020        } else if mime_str.starts_with("audio/") {
2021            Self::Audio {
2022                data: None,
2023                uri: Some(uri_str),
2024                mime_type: Some(mime_str),
2025            }
2026        } else if mime_str.starts_with("video/") {
2027            Self::Video {
2028                data: None,
2029                uri: Some(uri_str),
2030                mime_type: Some(mime_str),
2031                resolution: None,
2032            }
2033        } else {
2034            // Default to document for PDFs, text files, and other types
2035            Self::Document {
2036                data: None,
2037                uri: Some(uri_str),
2038                mime_type: Some(mime_str),
2039            }
2040        }
2041    }
2042
2043    /// Creates file content from a Files API metadata object.
2044    ///
2045    /// Use this to reference files uploaded via the Files API. The content type
2046    /// is inferred from the file's MIME type (image, audio, video, or document).
2047    ///
2048    /// # Arguments
2049    ///
2050    /// * `file` - The uploaded file metadata from the Files API
2051    ///
2052    /// # Example
2053    ///
2054    /// ```no_run
2055    /// use genai_rs::{Client, Content};
2056    ///
2057    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2058    /// let client = Client::new("api-key".to_string());
2059    ///
2060    /// let file = client.upload_file("video.mp4").await?;
2061    /// let content = Content::from_file(&file);
2062    ///
2063    /// let response = client.interaction()
2064    ///     .with_model("gemini-3-flash-preview")
2065    ///     .with_content(vec![
2066    ///         Content::text("Describe this video"),
2067    ///         content,
2068    ///     ])
2069    ///     .create()
2070    ///     .await?;
2071    /// # Ok(())
2072    /// # }
2073    /// ```
2074    #[must_use]
2075    pub fn from_file(file: &crate::http::files::FileMetadata) -> Self {
2076        Self::from_uri_and_mime(file.uri.clone(), file.mime_type.clone())
2077    }
2078
2079    // =========================================================================
2080    // Builder Methods
2081    // =========================================================================
2082
2083    /// Sets the resolution on image or video content.
2084    ///
2085    /// This builder method enables fluent chaining for setting resolution:
2086    ///
2087    /// ```
2088    /// use genai_rs::{Content, Resolution};
2089    ///
2090    /// let image = Content::image_uri("files/abc123", "image/png")
2091    ///     .with_resolution(Resolution::High);
2092    ///
2093    /// let video = Content::video_uri("files/def456", "video/mp4")
2094    ///     .with_resolution(Resolution::Low);
2095    /// ```
2096    ///
2097    /// # Resolution Trade-offs
2098    ///
2099    /// | Level | Token Cost | Detail Level |
2100    /// |-------|------------|--------------|
2101    /// | Low | Lowest | Basic shapes and colors |
2102    /// | Medium | Moderate | Standard detail (default) |
2103    /// | High | Higher | Fine details visible |
2104    /// | UltraHigh | Highest | Maximum fidelity |
2105    ///
2106    /// # Behavior on Non-Media Content
2107    ///
2108    /// For content types that don't support resolution (Text, Audio, Document,
2109    /// FunctionCall, etc.), this method logs a warning and returns the content
2110    /// unchanged.
2111    #[must_use]
2112    pub fn with_resolution(self, resolution: Resolution) -> Self {
2113        match self {
2114            Self::Image {
2115                data,
2116                uri,
2117                mime_type,
2118                ..
2119            } => Self::Image {
2120                data,
2121                uri,
2122                mime_type,
2123                resolution: Some(resolution),
2124            },
2125            Self::Video {
2126                data,
2127                uri,
2128                mime_type,
2129                ..
2130            } => Self::Video {
2131                data,
2132                uri,
2133                mime_type,
2134                resolution: Some(resolution),
2135            },
2136            other => {
2137                tracing::warn!(
2138                    "with_resolution() called on content type that doesn't support resolution. \
2139                     Resolution is only applicable to Image and Video content."
2140                );
2141                other
2142            }
2143        }
2144    }
2145
2146    /// Creates a function result from this function call.
2147    ///
2148    /// This builder method enables fluent patterns for handling function calls:
2149    ///
2150    /// ```
2151    /// use genai_rs::Content;
2152    /// use serde_json::json;
2153    ///
2154    /// // Given a function call content
2155    /// let call = Content::function_call_with_id(
2156    ///     Some("call_123"),
2157    ///     "get_weather",
2158    ///     json!({"location": "San Francisco"})
2159    /// );
2160    ///
2161    /// // Execute the function and create the result
2162    /// let result = call.with_result(json!({"temperature": "72F", "conditions": "sunny"}));
2163    /// assert!(matches!(result, Content::FunctionResult { .. }));
2164    /// ```
2165    ///
2166    /// # Behavior on Non-FunctionCall Content
2167    ///
2168    /// For content types other than `FunctionCall`, this method logs a warning
2169    /// and returns the original content unchanged.
2170    ///
2171    /// # Error Results
2172    ///
2173    /// To indicate a function execution error, use [`with_result_error`](Self::with_result_error)
2174    /// instead.
2175    #[must_use]
2176    pub fn with_result(self, result: serde_json::Value) -> Self {
2177        match &self {
2178            Self::FunctionCall { id, name, .. } => Self::FunctionResult {
2179                name: Some(name.clone()),
2180                call_id: id.clone().unwrap_or_default(),
2181                result,
2182                is_error: None,
2183            },
2184            _ => {
2185                tracing::warn!(
2186                    "with_result() called on non-FunctionCall content type. \
2187                     This method is only valid for FunctionCall content."
2188                );
2189                self
2190            }
2191        }
2192    }
2193
2194    /// Creates a function error result from this function call.
2195    ///
2196    /// Use this when function execution fails and you need to report the error
2197    /// back to the model.
2198    ///
2199    /// ```
2200    /// use genai_rs::Content;
2201    /// use serde_json::json;
2202    ///
2203    /// let call = Content::function_call_with_id(
2204    ///     Some("call_123"),
2205    ///     "get_weather",
2206    ///     json!({"location": "San Francisco"})
2207    /// );
2208    ///
2209    /// // Report execution error
2210    /// let error = call.with_result_error(json!({"error": "API rate limit exceeded"}));
2211    /// ```
2212    #[must_use]
2213    pub fn with_result_error(self, result: serde_json::Value) -> Self {
2214        match &self {
2215            Self::FunctionCall { id, name, .. } => Self::FunctionResult {
2216                name: Some(name.clone()),
2217                call_id: id.clone().unwrap_or_default(),
2218                result,
2219                is_error: Some(true),
2220            },
2221            _ => {
2222                tracing::warn!(
2223                    "with_result_error() called on non-FunctionCall content type. \
2224                     This method is only valid for FunctionCall content."
2225                );
2226                self
2227            }
2228        }
2229    }
2230}
2231
2232// Custom Deserialize implementation to handle unknown content types gracefully.
2233//
2234// This tries to deserialize known types first, and falls back to Unknown for
2235// unrecognized types. This provides forward compatibility when Google adds
2236// new content types to the API.
2237impl<'de> Deserialize<'de> for Content {
2238    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2239    where
2240        D: serde::Deserializer<'de>,
2241    {
2242        #[cfg(feature = "strict-unknown")]
2243        use serde::de::Error as _;
2244
2245        // First, deserialize into a raw JSON value
2246        let value = serde_json::Value::deserialize(deserializer)?;
2247
2248        // Helper enum for deserializing known types
2249        #[derive(Deserialize)]
2250        #[serde(tag = "type", rename_all = "snake_case")]
2251        enum KnownContent {
2252            Text {
2253                text: Option<String>,
2254                #[serde(default)]
2255                annotations: Option<Vec<Annotation>>,
2256            },
2257            Thought {
2258                signature: Option<String>,
2259            },
2260            ThoughtSignature {
2261                #[serde(default)]
2262                signature: String,
2263            },
2264            Image {
2265                data: Option<String>,
2266                uri: Option<String>,
2267                mime_type: Option<String>,
2268                resolution: Option<Resolution>,
2269            },
2270            Audio {
2271                data: Option<String>,
2272                uri: Option<String>,
2273                mime_type: Option<String>,
2274            },
2275            Video {
2276                data: Option<String>,
2277                uri: Option<String>,
2278                mime_type: Option<String>,
2279                resolution: Option<Resolution>,
2280            },
2281            Document {
2282                data: Option<String>,
2283                uri: Option<String>,
2284                mime_type: Option<String>,
2285            },
2286            FunctionCall {
2287                #[serde(default)]
2288                id: Option<String>,
2289                // In streaming, content.start events may not include name yet
2290                #[serde(default)]
2291                name: Option<String>,
2292                #[serde(rename = "arguments", default)]
2293                args: serde_json::Value,
2294            },
2295            FunctionResult {
2296                name: Option<String>,
2297                call_id: String,
2298                result: serde_json::Value,
2299                is_error: Option<bool>,
2300            },
2301            CodeExecutionCall {
2302                #[serde(default)]
2303                id: Option<String>,
2304                // API returns language/code in the arguments object
2305                #[serde(default)]
2306                language: Option<CodeExecutionLanguage>,
2307                #[serde(default)]
2308                code: Option<String>,
2309                // Fallback for old API format
2310                #[serde(default)]
2311                arguments: Option<serde_json::Value>,
2312            },
2313            CodeExecutionResult {
2314                #[serde(default)]
2315                call_id: Option<String>,
2316                #[serde(default)]
2317                is_error: Option<bool>,
2318                #[serde(default)]
2319                result: Option<String>,
2320            },
2321            GoogleSearchCall {
2322                id: String,
2323                #[serde(default)]
2324                arguments: Option<serde_json::Value>,
2325            },
2326            GoogleSearchResult {
2327                call_id: String,
2328                #[serde(default)]
2329                result: Vec<GoogleSearchResultItem>,
2330            },
2331            UrlContextCall {
2332                id: String,
2333                #[serde(default)]
2334                arguments: Option<serde_json::Value>,
2335            },
2336            UrlContextResult {
2337                call_id: String,
2338                #[serde(default)]
2339                result: Vec<UrlContextResultItem>,
2340            },
2341            FileSearchResult {
2342                call_id: String,
2343                #[serde(default)]
2344                result: Vec<FileSearchResultItem>,
2345            },
2346            ComputerUseCall {
2347                id: String,
2348                action: String,
2349                #[serde(default)]
2350                parameters: Option<serde_json::Value>,
2351            },
2352            ComputerUseResult {
2353                call_id: String,
2354                success: bool,
2355                #[serde(default)]
2356                output: Option<serde_json::Value>,
2357                #[serde(default)]
2358                error: Option<String>,
2359                #[serde(default)]
2360                screenshot: Option<String>,
2361            },
2362        }
2363
2364        // Try to deserialize as a known type
2365        match serde_json::from_value::<KnownContent>(value.clone()) {
2366            Ok(known) => Ok(match known {
2367                KnownContent::Text { text, annotations } => Content::Text { text, annotations },
2368                KnownContent::Thought { signature } => Content::Thought { signature },
2369                KnownContent::ThoughtSignature { signature } => {
2370                    Content::ThoughtSignature { signature }
2371                }
2372                KnownContent::Image {
2373                    data,
2374                    uri,
2375                    mime_type,
2376                    resolution,
2377                } => Content::Image {
2378                    data,
2379                    uri,
2380                    mime_type,
2381                    resolution,
2382                },
2383                KnownContent::Audio {
2384                    data,
2385                    uri,
2386                    mime_type,
2387                } => Content::Audio {
2388                    data,
2389                    uri,
2390                    mime_type,
2391                },
2392                KnownContent::Video {
2393                    data,
2394                    uri,
2395                    mime_type,
2396                    resolution,
2397                } => Content::Video {
2398                    data,
2399                    uri,
2400                    mime_type,
2401                    resolution,
2402                },
2403                KnownContent::Document {
2404                    data,
2405                    uri,
2406                    mime_type,
2407                } => Content::Document {
2408                    data,
2409                    uri,
2410                    mime_type,
2411                },
2412                KnownContent::FunctionCall { id, name, args } => {
2413                    // In streaming, content.start may not include name yet;
2414                    // deltas will fill it in. Use empty string as placeholder.
2415                    Content::FunctionCall {
2416                        id,
2417                        name: name.unwrap_or_default(),
2418                        args,
2419                    }
2420                }
2421                KnownContent::FunctionResult {
2422                    name,
2423                    call_id,
2424                    result,
2425                    is_error,
2426                } => Content::FunctionResult {
2427                    name,
2428                    call_id,
2429                    result,
2430                    is_error,
2431                },
2432                KnownContent::CodeExecutionCall {
2433                    id,
2434                    language,
2435                    code,
2436                    arguments,
2437                } => {
2438                    // Prefer direct fields, fall back to parsing arguments
2439                    if let (Some(lang), Some(source)) = (language, code) {
2440                        Content::CodeExecutionCall {
2441                            id,
2442                            language: lang,
2443                            code: source,
2444                        }
2445                    } else if let Some(args) = arguments {
2446                        // Parse old format: {"language": "PYTHON", "code": "..."}
2447                        // Code is required - if missing, treat as Unknown per Evergreen philosophy
2448                        let source = match args.get("code").and_then(|v| v.as_str()) {
2449                            Some(code) => code.to_string(),
2450                            None => {
2451                                tracing::warn!(
2452                                    "CodeExecutionCall arguments missing required 'code' field for id: {:?}. \
2453                                     Treating as Unknown variant to preserve data for debugging.",
2454                                    id
2455                                );
2456                                return Ok(Content::Unknown {
2457                                    content_type: "code_execution_call".to_string(),
2458                                    data: value.clone(),
2459                                });
2460                            }
2461                        };
2462
2463                        // Language defaults to Python if missing or malformed (most common case)
2464                        let lang = match args.get("language") {
2465                            Some(lang_value) => {
2466                                match serde_json::from_value::<CodeExecutionLanguage>(
2467                                    lang_value.clone(),
2468                                ) {
2469                                    Ok(lang) => lang,
2470                                    Err(e) => {
2471                                        tracing::warn!(
2472                                            "CodeExecutionCall has invalid language value for id: {:?}, \
2473                                             defaulting to Python. Parse error: {}",
2474                                            id,
2475                                            e
2476                                        );
2477                                        CodeExecutionLanguage::Python
2478                                    }
2479                                }
2480                            }
2481                            None => CodeExecutionLanguage::Python,
2482                        };
2483
2484                        Content::CodeExecutionCall {
2485                            id,
2486                            language: lang,
2487                            code: source,
2488                        }
2489                    } else {
2490                        // Malformed CodeExecutionCall - treat as Unknown to preserve data
2491                        // per Evergreen philosophy (see CLAUDE.md). This avoids silently
2492                        // degrading to an empty code string which could cause subtle bugs.
2493                        tracing::warn!(
2494                            "CodeExecutionCall missing both direct fields and arguments for id: {:?}. \
2495                             Treating as Unknown variant to preserve data for debugging.",
2496                            id
2497                        );
2498                        Content::Unknown {
2499                            content_type: "code_execution_call".to_string(),
2500                            data: value.clone(),
2501                        }
2502                    }
2503                }
2504                KnownContent::CodeExecutionResult {
2505                    call_id,
2506                    is_error,
2507                    result,
2508                } => Content::CodeExecutionResult {
2509                    call_id,
2510                    // Default to false (success) when is_error is not provided
2511                    is_error: is_error.unwrap_or(false),
2512                    result: result.unwrap_or_default(),
2513                },
2514                KnownContent::GoogleSearchCall { id, arguments } => {
2515                    // Extract queries from arguments.queries
2516                    let queries = arguments
2517                        .as_ref()
2518                        .and_then(|args| args.get("queries"))
2519                        .and_then(|q| q.as_array())
2520                        .map(|arr| {
2521                            arr.iter()
2522                                .filter_map(|v| v.as_str().map(String::from))
2523                                .collect()
2524                        })
2525                        .unwrap_or_default();
2526
2527                    Content::GoogleSearchCall { id, queries }
2528                }
2529                KnownContent::GoogleSearchResult { call_id, result } => {
2530                    Content::GoogleSearchResult { call_id, result }
2531                }
2532                KnownContent::UrlContextCall { id, arguments } => {
2533                    // Extract urls from arguments.urls
2534                    let urls = arguments
2535                        .as_ref()
2536                        .and_then(|args| args.get("urls"))
2537                        .and_then(|u| u.as_array())
2538                        .map(|arr| {
2539                            arr.iter()
2540                                .filter_map(|v| v.as_str().map(String::from))
2541                                .collect()
2542                        })
2543                        .unwrap_or_default();
2544
2545                    Content::UrlContextCall { id, urls }
2546                }
2547                KnownContent::UrlContextResult { call_id, result } => {
2548                    Content::UrlContextResult { call_id, result }
2549                }
2550                KnownContent::FileSearchResult { call_id, result } => {
2551                    Content::FileSearchResult { call_id, result }
2552                }
2553                KnownContent::ComputerUseCall {
2554                    id,
2555                    action,
2556                    parameters,
2557                } => Content::ComputerUseCall {
2558                    id,
2559                    action,
2560                    parameters: parameters.unwrap_or(serde_json::Value::Null),
2561                },
2562                KnownContent::ComputerUseResult {
2563                    call_id,
2564                    success,
2565                    output,
2566                    error,
2567                    screenshot,
2568                } => Content::ComputerUseResult {
2569                    call_id,
2570                    success,
2571                    output,
2572                    error,
2573                    screenshot,
2574                },
2575            }),
2576            Err(parse_error) => {
2577                // Unknown type - extract type name and preserve data
2578                let content_type = value
2579                    .get("type")
2580                    .and_then(|v| v.as_str())
2581                    .unwrap_or("<missing type>")
2582                    .to_string();
2583
2584                // Log the actual parse error for debugging - this helps distinguish
2585                // between truly unknown types and malformed known types
2586                tracing::warn!(
2587                    "Encountered unknown Content type '{}'. \
2588                     Parse error: {}. \
2589                     This may indicate a new API feature or a malformed response. \
2590                     The content will be preserved in the Unknown variant.",
2591                    content_type,
2592                    parse_error
2593                );
2594
2595                #[cfg(feature = "strict-unknown")]
2596                {
2597                    Err(D::Error::custom(format!(
2598                        "Unknown Content type '{}'. \
2599                         Strict mode is enabled via the 'strict-unknown' feature flag. \
2600                         Either update the library or disable strict mode.",
2601                        content_type
2602                    )))
2603                }
2604
2605                #[cfg(not(feature = "strict-unknown"))]
2606                {
2607                    Ok(Content::Unknown {
2608                        content_type,
2609                        data: value,
2610                    })
2611                }
2612            }
2613        }
2614    }
2615}