Skip to main content

gemini_rust/generation/
model.rs

1use reqwest::Url;
2use serde::{Deserialize, Serialize};
3use time::OffsetDateTime;
4
5use crate::{
6    safety::{SafetyRating, SafetySetting},
7    Content, Modality, Part,
8};
9
10/// Reason why generation finished
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
13pub enum FinishReason {
14    /// Default value. This value is unused.
15    FinishReasonUnspecified,
16    /// Natural stop point of the model or provided stop sequence.
17    Stop,
18    /// The maximum number of tokens as specified in the request was reached.
19    MaxTokens,
20    /// The response candidate content was flagged for safety reasons.
21    Safety,
22    /// The response candidate content was flagged for recitation reasons.
23    Recitation,
24    /// The response candidate content was flagged for using an unsupported language.
25    Language,
26    /// Unknown reason.
27    Other,
28    /// Token generation stopped because the content contains forbidden terms.
29    Blocklist,
30    /// Token generation stopped for potentially containing prohibited content.
31    ProhibitedContent,
32    /// Token generation stopped because the content potentially contains Sensitive Personally Identifiable Information (SPII).
33    Spii,
34    /// The function call generated by the model is invalid.
35    MalformedFunctionCall,
36    /// Token generation stopped because generated images contain safety violations.
37    ImageSafety,
38    /// Model generated a tool call but no tools were enabled in the request.
39    UnexpectedToolCall,
40    /// Model called too many tools consecutively, thus the system exited execution.
41    TooManyToolCalls,
42}
43
44/// Citation metadata for content
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
46#[serde(rename_all = "camelCase")]
47pub struct CitationMetadata {
48    /// The citation sources
49    pub citation_sources: Vec<CitationSource>,
50}
51
52/// Citation source
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
54#[serde(rename_all = "camelCase")]
55pub struct CitationSource {
56    /// The URI of the citation source
57    pub uri: Option<String>,
58    /// The title of the citation source
59    pub title: Option<String>,
60    /// The start index of the citation in the response
61    pub start_index: Option<i32>,
62    /// The end index of the citation in the response
63    pub end_index: Option<i32>,
64    /// The license of the citation source
65    pub license: Option<String>,
66    /// The publication date of the citation source
67    #[serde(default, with = "time::serde::rfc3339::option")]
68    pub publication_date: Option<OffsetDateTime>,
69}
70
71/// A candidate response
72#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
73#[serde(rename_all = "camelCase")]
74pub struct Candidate {
75    /// The content of the candidate
76    #[serde(default)]
77    pub content: Content,
78    /// The safety ratings for the candidate
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub safety_ratings: Option<Vec<SafetyRating>>,
81    /// The citation metadata for the candidate
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub citation_metadata: Option<CitationMetadata>,
84    /// The grounding metadata for the candidate
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub grounding_metadata: Option<GroundingMetadata>,
87    /// The finish reason for the candidate
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub finish_reason: Option<FinishReason>,
90    /// The index of the candidate
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub index: Option<i32>,
93}
94
95/// Metadata about token usage
96#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
97#[serde(rename_all = "camelCase")]
98pub struct UsageMetadata {
99    /// The number of prompt tokens (null if request processing failed)
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub prompt_token_count: Option<i32>,
102    /// The number of response tokens (null if generation failed)
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub candidates_token_count: Option<i32>,
105    /// The total number of tokens (null if individual counts unavailable)
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub total_token_count: Option<i32>,
108    /// The number of thinking tokens (Gemini 2.5 series only)
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub thoughts_token_count: Option<i32>,
111    /// Detailed prompt token information
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub prompt_tokens_details: Option<Vec<PromptTokenDetails>>,
114    /// The number of cached content tokens (batch API)
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub cached_content_token_count: Option<i32>,
117    /// Detailed cache token information (batch API)
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub cache_tokens_details: Option<Vec<PromptTokenDetails>>,
120}
121
122/// Details about prompt tokens by modality
123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
124#[serde(rename_all = "camelCase")]
125pub struct PromptTokenDetails {
126    /// The modality (e.g., "TEXT")
127    pub modality: Modality,
128    /// Token count for this modality
129    pub token_count: i32,
130}
131
132/// Grounding metadata for responses that use grounding tools
133#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
134#[serde(rename_all = "camelCase")]
135pub struct GroundingMetadata {
136    /// Grounding chunks containing source information
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub grounding_chunks: Option<Vec<GroundingChunk>>,
139    /// Grounding supports connecting response text to sources
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub grounding_supports: Option<Vec<GroundingSupport>>,
142    /// Web search queries used for grounding
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub web_search_queries: Option<Vec<String>>,
145    /// Google Maps widget context token
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub google_maps_widget_context_token: Option<String>,
148}
149
150/// A chunk of grounding information from a source
151#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
152#[serde(rename_all = "camelCase")]
153pub struct GroundingChunk {
154    /// Maps-specific grounding information
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub maps: Option<MapsGroundingChunk>,
157    /// Web-specific grounding information
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub web: Option<WebGroundingChunk>,
160}
161
162/// Maps-specific grounding chunk information
163#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
164#[serde(rename_all = "camelCase")]
165pub struct MapsGroundingChunk {
166    /// The URI of the Maps source
167    pub uri: Url,
168    /// The title of the Maps source
169    pub title: String,
170    /// The place ID from Google Maps
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub place_id: Option<String>,
173}
174
175/// Web-specific grounding chunk information
176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
177#[serde(rename_all = "camelCase")]
178pub struct WebGroundingChunk {
179    /// The URI of the web source
180    pub uri: Url,
181    /// The title of the web source
182    pub title: String,
183}
184
185/// Support information connecting response text to grounding sources
186#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
187#[serde(rename_all = "camelCase")]
188pub struct GroundingSupport {
189    /// Segment of the response text
190    pub segment: GroundingSegment,
191    /// Indices of grounding chunks that support this segment
192    pub grounding_chunk_indices: Vec<u32>,
193}
194
195/// A segment of response text
196#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
197#[serde(rename_all = "camelCase")]
198pub struct GroundingSegment {
199    /// Start index of the segment in the response text
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub start_index: Option<u32>,
202    /// End index of the segment in the response text
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub end_index: Option<u32>,
205    /// The text content of the segment
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub text: Option<String>,
208}
209
210/// Response from the Gemini API for content generation
211#[deprecated(
212    since = "1.8.0",
213    note = "Use crate::interactions::Interaction instead. See migration guide: interactions-api/migration-plan.md"
214)]
215#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
216#[serde(rename_all = "camelCase")]
217pub struct GenerationResponse {
218    /// The candidates generated
219    #[serde(default, skip_serializing_if = "Vec::is_empty")]
220    pub candidates: Vec<Candidate>,
221    /// The prompt feedback
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub prompt_feedback: Option<PromptFeedback>,
224    /// Usage metadata
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub usage_metadata: Option<UsageMetadata>,
227    /// Model version used
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub model_version: Option<String>,
230    /// Response ID
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub response_id: Option<String>,
233}
234
235/// Reason why content was blocked
236#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
237#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
238pub enum BlockReason {
239    /// Default value. This value is unused.
240    BlockReasonUnspecified,
241    /// Prompt was blocked due to safety reasons. Inspect safetyRatings to understand which safety category blocked it.
242    Safety,
243    /// Prompt was blocked due to unknown reasons.
244    Other,
245    /// Prompt was blocked due to the terms which are included from the terminology blocklist.
246    Blocklist,
247    /// Prompt was blocked due to prohibited content.
248    ProhibitedContent,
249    /// Candidates blocked due to unsafe image generation content.
250    ImageSafety,
251}
252
253/// Feedback about the prompt
254#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
255#[serde(rename_all = "camelCase")]
256pub struct PromptFeedback {
257    /// The safety ratings for the prompt
258    #[serde(default, skip_serializing_if = "Vec::is_empty")]
259    pub safety_ratings: Vec<SafetyRating>,
260    /// The block reason if the prompt was blocked
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub block_reason: Option<BlockReason>,
263}
264
265impl GenerationResponse {
266    /// Get the text of the first candidate
267    pub fn text(&self) -> String {
268        self.candidates
269            .first()
270            .and_then(|c| {
271                c.content.parts.as_ref().and_then(|parts| {
272                    parts.first().and_then(|p| match p {
273                        Part::Text {
274                            text,
275                            thought: _,
276                            thought_signature: _,
277                        } => Some(text.clone()),
278                        _ => None,
279                    })
280                })
281            })
282            .unwrap_or_default()
283    }
284
285    /// Get the finish reason of the first candidate
286    pub fn finish_reason(&self) -> Option<FinishReason> {
287        self.candidates
288            .first()
289            .and_then(|c| c.finish_reason.clone())
290    }
291
292    /// Get function calls from the response
293    pub fn function_calls(&self) -> Vec<&crate::tools::FunctionCall> {
294        self.candidates
295            .iter()
296            .flat_map(|c| {
297                c.content
298                    .parts
299                    .as_ref()
300                    .map(|parts| {
301                        parts
302                            .iter()
303                            .filter_map(|p| match p {
304                                Part::FunctionCall {
305                                    function_call,
306                                    thought_signature: _,
307                                } => Some(function_call),
308                                _ => None,
309                            })
310                            .collect::<Vec<_>>()
311                    })
312                    .unwrap_or_default()
313            })
314            .collect()
315    }
316
317    /// Get function calls with their thought signatures from the response
318    pub fn function_calls_with_thoughts(
319        &self,
320    ) -> Vec<(&crate::tools::FunctionCall, Option<&String>)> {
321        self.candidates
322            .iter()
323            .flat_map(|c| {
324                c.content
325                    .parts
326                    .as_ref()
327                    .map(|parts| {
328                        parts
329                            .iter()
330                            .filter_map(|p| match p {
331                                Part::FunctionCall {
332                                    function_call,
333                                    thought_signature,
334                                } => Some((function_call, thought_signature.as_ref())),
335                                _ => None,
336                            })
337                            .collect::<Vec<_>>()
338                    })
339                    .unwrap_or_default()
340            })
341            .collect()
342    }
343
344    /// Get thought summaries from the response
345    pub fn thoughts(&self) -> Vec<String> {
346        self.candidates
347            .iter()
348            .flat_map(|c| {
349                c.content
350                    .parts
351                    .as_ref()
352                    .map(|parts| {
353                        parts
354                            .iter()
355                            .filter_map(|p| match p {
356                                Part::Text {
357                                    text,
358                                    thought: Some(true),
359                                    thought_signature: _,
360                                } => Some(text.clone()),
361                                _ => None,
362                            })
363                            .collect::<Vec<_>>()
364                    })
365                    .unwrap_or_default()
366            })
367            .collect()
368    }
369
370    /// Get all text parts (both regular text and thoughts)
371    pub fn all_text(&self) -> Vec<(String, bool)> {
372        self.candidates
373            .iter()
374            .flat_map(|c| {
375                c.content
376                    .parts
377                    .as_ref()
378                    .map(|parts| {
379                        parts
380                            .iter()
381                            .filter_map(|p| match p {
382                                Part::Text {
383                                    text,
384                                    thought,
385                                    thought_signature: _,
386                                } => Some((text.clone(), thought.unwrap_or(false))),
387                                _ => None,
388                            })
389                            .collect::<Vec<_>>()
390                    })
391                    .unwrap_or_default()
392            })
393            .collect()
394    }
395
396    /// Get text parts with their thought signatures from the response
397    pub fn text_with_thoughts(&self) -> Vec<(String, bool, Option<&String>)> {
398        self.candidates
399            .iter()
400            .flat_map(|c| {
401                c.content
402                    .parts
403                    .as_ref()
404                    .map(|parts| {
405                        parts
406                            .iter()
407                            .filter_map(|p| match p {
408                                Part::Text {
409                                    text,
410                                    thought,
411                                    thought_signature,
412                                } => Some((
413                                    text.clone(),
414                                    thought.unwrap_or(false),
415                                    thought_signature.as_ref(),
416                                )),
417                                _ => None,
418                            })
419                            .collect::<Vec<_>>()
420                    })
421                    .unwrap_or_default()
422            })
423            .collect()
424    }
425}
426
427/// Request to generate content
428#[deprecated(
429    since = "1.8.0",
430    note = "Use crate::interactions::CreateInteractionRequest instead. See migration guide: interactions-api/migration-plan.md"
431)]
432#[derive(Debug, Clone, Serialize, Deserialize)]
433#[serde(rename_all = "camelCase")]
434pub struct GenerateContentRequest {
435    /// The contents to generate content from
436    pub contents: Vec<Content>,
437    /// The generation config
438    #[serde(skip_serializing_if = "Option::is_none")]
439    pub generation_config: Option<GenerationConfig>,
440    /// The safety settings
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub safety_settings: Option<Vec<SafetySetting>>,
443    /// The tools that the model can use
444    #[serde(skip_serializing_if = "Option::is_none")]
445    pub tools: Option<Vec<crate::tools::Tool>>,
446    /// The tool config
447    #[serde(skip_serializing_if = "Option::is_none")]
448    pub tool_config: Option<crate::tools::ToolConfig>,
449    /// The system instruction
450    #[serde(skip_serializing_if = "Option::is_none")]
451    pub system_instruction: Option<Content>,
452    /// The cached content to use
453    #[serde(skip_serializing_if = "Option::is_none")]
454    pub cached_content: Option<String>,
455}
456
457/// Thinking level for Gemini 3 series models
458///
459/// Controls the depth of reasoning and analysis that the model applies.
460#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
461#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
462pub enum ThinkingLevel {
463    /// Unspecified thinking level (uses model default)
464    ThinkingLevelUnspecified,
465    /// Minimal thinking level - fastest responses with minimal reasoning
466    Minimal,
467    /// Low thinking level - faster responses with less reasoning
468    Low,
469    /// Medium thinking level - balanced reasoning depth
470    Medium,
471    /// High thinking level - deeper analysis with more comprehensive reasoning
472    High,
473}
474
475/// Media resolution level for images and PDFs
476///
477/// Controls the resolution used when processing inline images and PDF documents,
478/// which affects both quality and token consumption.
479#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
480#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
481pub enum MediaResolutionLevel {
482    /// Unspecified resolution (uses model default)
483    MediaResolutionUnspecified,
484    /// Low resolution - uses fewer tokens, lower quality
485    MediaResolutionLow,
486    /// Medium resolution - balanced token usage and quality
487    MediaResolutionMedium,
488    /// High resolution - uses more tokens, higher quality
489    MediaResolutionHigh,
490}
491
492/// Wrapper struct for per-part media resolution.
493/// Allows fine-grained control over the resolution used for individual inline images and PDFs.
494#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
495pub struct MediaResolution {
496    /// The media resolution level to use
497    pub level: MediaResolutionLevel,
498}
499
500/// Configuration for thinking (Gemini 2.5 and Gemini 3 series)
501///
502/// - For Gemini 2.5 models, use `thinking_budget` and `include_thoughts`.
503/// - For Gemini 3 models, use `thinking_level` (mutually exclusive with `thinking_budget`).
504#[derive(Debug, Clone, Serialize, Deserialize)]
505#[serde(rename_all = "camelCase")]
506pub struct ThinkingConfig {
507    /// The thinking budget (number of thinking tokens)
508    ///
509    /// - Set to 0 to disable thinking
510    /// - Set to -1 for dynamic thinking (model decides)
511    /// - Set to a positive number for a specific token budget
512    ///
513    /// Model-specific ranges:
514    /// - 2.5 Pro: 128 to 32768 (cannot disable thinking)
515    /// - 2.5 Flash: 0 to 24576
516    /// - 2.5 Flash Lite: 512 to 24576
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub thinking_budget: Option<i32>,
519
520    /// Whether to include thought summaries in the response
521    ///
522    /// When enabled, the response will include synthesized versions of the model's
523    /// raw thoughts, providing insights into the reasoning process.
524    #[serde(skip_serializing_if = "Option::is_none")]
525    pub include_thoughts: Option<bool>,
526
527    /// The thinking level (Required for Gemini 3)
528    ///
529    /// Gemini 3 uses thinking_level (Low/High) which is mutually exclusive with thinking_budget
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub thinking_level: Option<ThinkingLevel>,
532}
533
534impl ThinkingConfig {
535    /// Create a new thinking config with default settings
536    pub fn new() -> Self {
537        Self {
538            thinking_budget: None,
539            include_thoughts: None,
540            thinking_level: None,
541        }
542    }
543
544    /// Set the thinking budget
545    pub fn with_thinking_budget(mut self, budget: i32) -> Self {
546        self.thinking_budget = Some(budget);
547        self
548    }
549
550    /// Enable dynamic thinking (model decides the budget)
551    pub fn with_dynamic_thinking(mut self) -> Self {
552        self.thinking_budget = Some(-1);
553        self
554    }
555
556    /// Include thought summaries in the response
557    pub fn with_thoughts_included(mut self, include: bool) -> Self {
558        self.include_thoughts = Some(include);
559        self
560    }
561
562    /// Set the thinking level (Required for Gemini 3)
563    pub fn with_thinking_level(mut self, level: ThinkingLevel) -> Self {
564        self.thinking_level = Some(level);
565        self
566    }
567
568    /// Create a thinking config that enables dynamic thinking with thoughts included
569    pub fn dynamic_thinking() -> Self {
570        Self {
571            thinking_budget: Some(-1),
572            include_thoughts: Some(true),
573            thinking_level: None,
574        }
575    }
576}
577
578impl Default for ThinkingConfig {
579    fn default() -> Self {
580        Self::new()
581    }
582}
583
584/// Configuration for generation
585#[deprecated(
586    since = "1.8.0",
587    note = "Use crate::interactions::InteractionGenerationConfig instead"
588)]
589#[derive(Debug, Default, Clone, Serialize, Deserialize)]
590#[serde(rename_all = "camelCase")]
591pub struct GenerationConfig {
592    /// The temperature for the model (0.0 to 1.0)
593    ///
594    /// Controls the randomness of the output. Higher values (e.g., 0.9) make output
595    /// more random, lower values (e.g., 0.1) make output more deterministic.
596    #[serde(skip_serializing_if = "Option::is_none")]
597    pub temperature: Option<f32>,
598
599    /// The top-p value for the model (0.0 to 1.0)
600    ///
601    /// For each token generation step, the model considers the top_p percentage of
602    /// probability mass for potential token choices. Lower values are more selective,
603    /// higher values allow more variety.
604    #[serde(skip_serializing_if = "Option::is_none")]
605    pub top_p: Option<f32>,
606
607    /// The top-k value for the model
608    ///
609    /// For each token generation step, the model considers the top_k most likely tokens.
610    /// Lower values are more selective, higher values allow more variety.
611    #[serde(skip_serializing_if = "Option::is_none")]
612    pub top_k: Option<i32>,
613
614    /// Seed used in decoding.
615    ///
616    /// By default, the model uses a random value for each request if a seed is not provided.
617    /// Setting a specific seed, along with consistent values for other parameters like temperature, can make the model return the same response for repeated requests with the same input.
618    /// Identical outputs are not guaranteed across all runs, due to backend infrastructure variations, but it provides a "best effort" for reproducibility.
619    #[serde(skip_serializing_if = "Option::is_none")]
620    pub seed: Option<i32>,
621
622    /// The maximum number of tokens to generate
623    ///
624    /// Limits the length of the generated content. One token is roughly 4 characters.
625    #[serde(skip_serializing_if = "Option::is_none")]
626    pub max_output_tokens: Option<i32>,
627
628    /// The candidate count
629    ///
630    /// Number of alternative responses to generate.
631    #[serde(skip_serializing_if = "Option::is_none")]
632    pub candidate_count: Option<i32>,
633
634    /// Whether to stop on specific sequences
635    ///
636    /// The model will stop generating content when it encounters any of these sequences.
637    #[serde(skip_serializing_if = "Option::is_none")]
638    pub stop_sequences: Option<Vec<String>>,
639
640    /// The response mime type
641    ///
642    /// Specifies the format of the model's response.
643    #[serde(skip_serializing_if = "Option::is_none")]
644    pub response_mime_type: Option<String>,
645    /// The response schema
646    ///
647    /// Specifies the JSON schema for structured responses.
648    #[serde(skip_serializing_if = "Option::is_none")]
649    pub response_schema: Option<serde_json::Value>,
650
651    /// The response JSON schema (strict mode).
652    ///
653    /// Prefer this field for modern Gemini models that support JSON Schema natively.
654    #[serde(skip_serializing_if = "Option::is_none")]
655    pub response_json_schema: Option<serde_json::Value>,
656
657    /// Response modalities (for TTS and other multimodal outputs)
658    #[serde(skip_serializing_if = "Option::is_none")]
659    pub response_modalities: Option<Vec<String>>,
660
661    /// Optional. Config for image generation. An error will be returned if this field is set for models
662    /// that don't support these config options.
663    #[serde(skip_serializing_if = "Option::is_none")]
664    pub image_config: Option<ImageConfig>,
665
666    /// Speech configuration for text-to-speech generation
667    #[serde(skip_serializing_if = "Option::is_none")]
668    pub speech_config: Option<SpeechConfig>,
669
670    /// The thinking configuration
671    ///
672    /// Configuration for the model's thinking process (Gemini 2.5 and Gemini 3 series).
673    #[serde(skip_serializing_if = "Option::is_none")]
674    pub thinking_config: Option<ThinkingConfig>,
675
676    /// Global media resolution for all images and PDFs.
677    /// Controls the resolution used for inline image and PDF data, affecting token usage.
678    /// Can be overridden per-part using the Part::InlineData media_resolution field.
679    #[serde(skip_serializing_if = "Option::is_none", rename = "media_resolution")]
680    pub media_resolution: Option<MediaResolutionLevel>,
681}
682
683/// Response from the Gemini API for token counting
684#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
685#[serde(rename_all = "camelCase")]
686pub struct CountTokensResponse {
687    /// The total number of tokens counted across all instances.
688    pub total_tokens: u32,
689    /// The total number of tokens in the cached content.
690    #[serde(skip_serializing_if = "Option::is_none")]
691    pub cached_content_token_count: Option<u32>,
692}
693
694/// Config for image generation features.
695#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
696#[serde(rename_all = "camelCase")]
697pub struct ImageConfig {
698    /// Optional. The aspect ratio of the image to generate. Supported aspect ratios: 1:1, 2:3, 3:2, 3:4,
699    /// 4:3, 9:16, 16:9, 21:9.
700    ///
701    /// If not specified, the model will choose a default aspect ratio based on any reference images
702    /// provided.
703    #[serde(skip_serializing_if = "Option::is_none")]
704    pub aspect_ratio: Option<String>,
705    /// Optional. Specifies the size of generated images. Supported values are `1K`, `2K`, `4K`. If not
706    /// specified, the model will use default value `1K`.
707    #[serde(skip_serializing_if = "Option::is_none")]
708    pub image_size: Option<String>,
709}
710
711/// Configuration for speech generation (text-to-speech)
712#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
713#[serde(rename_all = "camelCase")]
714pub struct SpeechConfig {
715    /// Single voice configuration
716    #[serde(skip_serializing_if = "Option::is_none")]
717    pub voice_config: Option<VoiceConfig>,
718    /// Multi-speaker voice configuration
719    #[serde(skip_serializing_if = "Option::is_none")]
720    pub multi_speaker_voice_config: Option<MultiSpeakerVoiceConfig>,
721}
722
723/// Voice configuration for text-to-speech
724#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
725#[serde(rename_all = "camelCase")]
726pub struct VoiceConfig {
727    /// Prebuilt voice configuration
728    #[serde(skip_serializing_if = "Option::is_none")]
729    pub prebuilt_voice_config: Option<PrebuiltVoiceConfig>,
730}
731
732/// Prebuilt voice configuration
733#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
734#[serde(rename_all = "camelCase")]
735pub struct PrebuiltVoiceConfig {
736    /// The name of the voice to use
737    pub voice_name: String,
738}
739
740/// Multi-speaker voice configuration
741#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
742#[serde(rename_all = "camelCase")]
743pub struct MultiSpeakerVoiceConfig {
744    /// Configuration for each speaker
745    pub speaker_voice_configs: Vec<SpeakerVoiceConfig>,
746}
747
748/// Configuration for a specific speaker in multi-speaker TTS
749#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
750#[serde(rename_all = "camelCase")]
751pub struct SpeakerVoiceConfig {
752    /// The name of the speaker (must match the name used in the prompt)
753    pub speaker: String,
754    /// Voice configuration for this speaker
755    pub voice_config: VoiceConfig,
756}
757
758impl SpeechConfig {
759    /// Create a new speech config with a single voice
760    pub fn single_voice(voice_name: impl Into<String>) -> Self {
761        Self {
762            voice_config: Some(VoiceConfig {
763                prebuilt_voice_config: Some(PrebuiltVoiceConfig {
764                    voice_name: voice_name.into(),
765                }),
766            }),
767            multi_speaker_voice_config: None,
768        }
769    }
770
771    /// Create a new speech config with multiple speakers
772    pub fn multi_speaker(speakers: Vec<SpeakerVoiceConfig>) -> Self {
773        Self {
774            voice_config: None,
775            multi_speaker_voice_config: Some(MultiSpeakerVoiceConfig {
776                speaker_voice_configs: speakers,
777            }),
778        }
779    }
780}
781
782impl SpeakerVoiceConfig {
783    /// Create a new speaker voice configuration
784    pub fn new(speaker: impl Into<String>, voice_name: impl Into<String>) -> Self {
785        Self {
786            speaker: speaker.into(),
787            voice_config: VoiceConfig {
788                prebuilt_voice_config: Some(PrebuiltVoiceConfig {
789                    voice_name: voice_name.into(),
790                }),
791            },
792        }
793    }
794}