Skip to main content

vtcode_llm/providers/gemini/wire/models/
mod.rs

1pub mod request;
2pub mod response;
3
4pub use request::{GenerateContentRequest, GenerationConfig, ThinkingConfig};
5pub use response::{Candidate, GenerateContentResponse};
6
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Content {
12    pub role: String,
13    pub parts: Vec<Part>,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct SystemInstruction {
18    pub(crate) parts: Vec<Part>,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct ServerToolCall {
23    #[serde(rename = "toolType", alias = "tool_type")]
24    pub(crate) tool_type: String,
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub(crate) args: Option<Value>,
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub(crate) id: Option<String>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct ServerToolResponse {
33    #[serde(rename = "toolType", alias = "tool_type")]
34    pub(crate) tool_type: String,
35    pub(crate) response: Value,
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub(crate) id: Option<String>,
38}
39
40impl Content {
41    pub fn user_text(text: impl Into<String>) -> Self {
42        Content {
43            role: "user".into(),
44            parts: vec![Part::Text { text: text.into(), thought_signature: None }],
45        }
46    }
47
48    pub fn system_text(text: impl Into<String>) -> Self {
49        // This creates a Content for backwards compatibility
50        // For systemInstruction field, use SystemInstruction::new() instead
51        Content {
52            role: "user".into(), // Convert system to user to avoid API error
53            parts: vec![Part::Text {
54                text: format!("System: {}", text.into()),
55                thought_signature: None,
56            }],
57        }
58    }
59
60    pub fn user_parts(parts: Vec<Part>) -> Self {
61        Content { role: "user".into(), parts }
62    }
63}
64
65impl SystemInstruction {
66    pub(crate) fn new(text: impl Into<String>) -> Self {
67        SystemInstruction {
68            parts: vec![Part::Text { text: text.into(), thought_signature: None }],
69        }
70    }
71}
72
73/// IMPORTANT: Variant ordering matters for `#[serde(untagged)]` deserialization.
74/// Serde tries variants in declaration order and returns the first successful match.
75/// Variants with required fields (FunctionCall, FunctionResponse, InlineData, Text)
76/// must come BEFORE CacheControl, which has only optional fields and would otherwise
77/// act as a catch-all, matching any JSON object.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79#[serde(untagged)]
80pub enum Part {
81    #[serde(rename_all = "camelCase")]
82    FunctionCall {
83        function_call: super::function_calling::FunctionCall,
84        /// Gemini 3 thought signature for maintaining reasoning context
85        /// Required for sequential function calling, optional for parallel calls (only first has it)
86        #[serde(skip_serializing_if = "Option::is_none")]
87        #[serde(rename = "thoughtSignature", alias = "thought_signature")]
88        thought_signature: Option<String>,
89    },
90    #[serde(rename_all = "camelCase")]
91    FunctionResponse {
92        function_response: super::function_calling::FunctionResponse,
93        /// Gemini 3 thought signature for maintaining reasoning context
94        /// Preserved when echoing function responses back to the model
95        #[serde(skip_serializing_if = "Option::is_none")]
96        #[serde(rename = "thoughtSignature", alias = "thought_signature")]
97        thought_signature: Option<String>,
98    },
99    #[serde(rename_all = "camelCase")]
100    ToolCall {
101        #[serde(rename = "toolCall")]
102        tool_call: ServerToolCall,
103        #[serde(skip_serializing_if = "Option::is_none")]
104        #[serde(rename = "thoughtSignature", alias = "thought_signature")]
105        thought_signature: Option<String>,
106    },
107    #[serde(rename_all = "camelCase")]
108    ToolResponse {
109        #[serde(rename = "toolResponse")]
110        tool_response: ServerToolResponse,
111        #[serde(skip_serializing_if = "Option::is_none")]
112        #[serde(rename = "thoughtSignature", alias = "thought_signature")]
113        thought_signature: Option<String>,
114    },
115    #[serde(rename_all = "camelCase")]
116    ExecutableCode {
117        #[serde(rename = "executableCode")]
118        executable_code: Value,
119        #[serde(skip_serializing_if = "Option::is_none")]
120        #[serde(rename = "thoughtSignature", alias = "thought_signature")]
121        thought_signature: Option<String>,
122    },
123    #[serde(rename_all = "camelCase")]
124    CodeExecutionResult {
125        #[serde(rename = "codeExecutionResult")]
126        code_execution_result: Value,
127        #[serde(skip_serializing_if = "Option::is_none")]
128        #[serde(rename = "thoughtSignature", alias = "thought_signature")]
129        thought_signature: Option<String>,
130    },
131    #[serde(rename_all = "camelCase")]
132    InlineData {
133        #[serde(rename = "inline_data")]
134        inline_data: InlineData,
135    },
136    Text {
137        text: String,
138        /// Gemini 3 thought signature for maintaining reasoning context
139        /// Must be preserved and sent back exactly as received
140        #[serde(skip_serializing_if = "Option::is_none")]
141        #[serde(rename = "thoughtSignature", alias = "thought_signature")]
142        thought_signature: Option<String>,
143    },
144    /// CacheControl MUST be last: it has only optional fields and would match any JSON object
145    #[serde(rename_all = "camelCase")]
146    CacheControl {
147        #[serde(rename = "ttlSeconds")]
148        ttl_seconds: Option<u64>,
149    },
150}
151
152impl Part {
153    /// Get the text content if this is a Text part
154    pub fn as_text(&self) -> Option<&str> {
155        match self {
156            Part::Text { text, .. } => Some(text),
157            _ => None,
158        }
159    }
160
161    pub fn thought_signature(&self) -> Option<&str> {
162        match self {
163            Part::FunctionCall { thought_signature, .. }
164            | Part::FunctionResponse { thought_signature, .. }
165            | Part::ToolCall { thought_signature, .. }
166            | Part::ToolResponse { thought_signature, .. }
167            | Part::ExecutableCode { thought_signature, .. }
168            | Part::CodeExecutionResult { thought_signature, .. }
169            | Part::Text { thought_signature, .. } => thought_signature.as_deref(),
170            Part::InlineData { .. } | Part::CacheControl { .. } => None,
171        }
172    }
173}
174
175#[derive(Debug, Clone, Default, Serialize, Deserialize)]
176pub struct Tool {
177    #[serde(skip_serializing_if = "Option::is_none", rename = "functionDeclarations")]
178    pub(crate) function_declarations: Option<Vec<FunctionDeclaration>>,
179    #[serde(skip_serializing_if = "Option::is_none", rename = "googleSearch")]
180    pub(crate) google_search: Option<Value>,
181    #[serde(skip_serializing_if = "Option::is_none", rename = "googleMaps")]
182    pub(crate) google_maps: Option<Value>,
183    #[serde(skip_serializing_if = "Option::is_none", rename = "urlContext")]
184    pub(crate) url_context: Option<Value>,
185    #[serde(skip_serializing_if = "Option::is_none", rename = "fileSearch")]
186    pub(crate) file_search: Option<Value>,
187    #[serde(skip_serializing_if = "Option::is_none", rename = "codeExecution")]
188    pub(crate) code_execution: Option<Value>,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct InlineData {
193    #[serde(rename = "mime_type")]
194    pub mime_type: String,
195    pub data: String,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct FunctionDeclaration {
200    pub name: String,
201    pub description: String,
202    pub parameters: Value,
203}
204
205#[derive(Debug, Clone, Default, Serialize, Deserialize)]
206pub struct ToolConfig {
207    #[serde(skip_serializing_if = "Option::is_none", rename = "functionCallingConfig")]
208    pub(crate) function_calling_config: Option<super::function_calling::FunctionCallingConfig>,
209    #[serde(skip_serializing_if = "Option::is_none", rename = "includeServerSideToolInvocations")]
210    pub(crate) include_server_side_tool_invocations: Option<bool>,
211}
212
213impl ToolConfig {
214    pub fn auto() -> Self {
215        Self {
216            function_calling_config: Some(super::function_calling::FunctionCallingConfig::auto()),
217            include_server_side_tool_invocations: None,
218        }
219    }
220
221    pub fn validated() -> Self {
222        Self {
223            function_calling_config: Some(super::function_calling::FunctionCallingConfig::validated()),
224            include_server_side_tool_invocations: None,
225        }
226    }
227
228    /// Whether the config carries nothing the API does not already assume, so a
229    /// request may omit `toolConfig` without changing provider behaviour.
230    pub(crate) fn is_default(&self) -> bool {
231        self.include_server_side_tool_invocations != Some(true)
232            && self
233                .function_calling_config
234                .as_ref()
235                .is_none_or(super::function_calling::FunctionCallingConfig::is_default_auto)
236    }
237}