Skip to main content

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

1use super::{Content, SystemInstruction, Tool, ToolConfig};
2use serde::{Deserialize, Deserializer, Serialize};
3use serde_json::Value;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct GenerateContentRequest {
7    pub(crate) contents: Vec<Content>,
8    #[serde(skip_serializing_if = "Option::is_none")]
9    pub(crate) tools: Option<Vec<Tool>>,
10    #[serde(skip_serializing_if = "Option::is_none", rename = "toolConfig")]
11    pub(crate) tool_config: Option<ToolConfig>,
12    #[serde(skip_serializing_if = "Option::is_none", rename = "systemInstruction")]
13    pub(crate) system_instruction: Option<SystemInstruction>,
14    /// Explicit context-cache name (`cachedContents/...`). When set, the
15    /// cached system/tools/prefix are supplied by the cache, not this body.
16    #[serde(skip_serializing_if = "Option::is_none", rename = "cachedContent")]
17    pub(crate) cached_content: Option<String>,
18    #[serde(
19        default,
20        skip_serializing_if = "Option::is_none",
21        rename = "generationConfig",
22        deserialize_with = "deserialize_boxed_generation_config_opt"
23    )]
24    pub(crate) generation_config: Option<Box<GenerationConfig>>,
25}
26
27#[derive(Debug, Clone, Default, Serialize, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct GenerationConfig {
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub(crate) temperature: Option<f32>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub(crate) top_p: Option<f32>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub(crate) top_k: Option<i32>,
36    #[serde(skip_serializing_if = "Option::is_none", rename = "candidateCount")]
37    pub(crate) candidate_count: Option<i32>,
38    #[serde(skip_serializing_if = "Option::is_none", rename = "maxOutputTokens")]
39    pub(crate) max_output_tokens: Option<u32>,
40    #[serde(skip_serializing_if = "Option::is_none", rename = "stopSequences")]
41    pub(crate) stop_sequences: Option<Vec<String>>,
42    #[serde(skip_serializing_if = "Option::is_none", rename = "responseMimeType")]
43    pub(crate) response_mime_type: Option<String>,
44    #[serde(skip_serializing_if = "Option::is_none", rename = "responseSchema")]
45    pub(crate) response_schema: Option<Value>,
46    #[serde(skip_serializing_if = "Option::is_none", rename = "presencePenalty")]
47    pub(crate) presence_penalty: Option<f32>,
48    #[serde(skip_serializing_if = "Option::is_none", rename = "frequencyPenalty")]
49    pub(crate) frequency_penalty: Option<f32>,
50    #[serde(skip_serializing_if = "Option::is_none", rename = "thinkingConfig")]
51    pub(crate) thinking_config: Option<ThinkingConfig>,
52}
53
54impl GenerationConfig {
55    fn is_empty(&self) -> bool {
56        self.temperature.is_none()
57            && self.top_p.is_none()
58            && self.top_k.is_none()
59            && self.candidate_count.is_none()
60            && self.max_output_tokens.is_none()
61            && self.stop_sequences.is_none()
62            && self.response_mime_type.is_none()
63            && self.response_schema.is_none()
64            && self.presence_penalty.is_none()
65            && self.frequency_penalty.is_none()
66            && self.thinking_config.is_none()
67    }
68
69    fn into_boxed_if_non_empty(self) -> Option<Box<Self>> {
70        (!self.is_empty()).then_some(Box::new(self))
71    }
72}
73
74#[derive(Debug, Clone, Default, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct ThinkingConfig {
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub(crate) thinking_level: Option<String>,
79}
80
81fn deserialize_boxed_generation_config_opt<'de, D>(deserializer: D) -> Result<Option<Box<GenerationConfig>>, D::Error>
82where
83    D: Deserializer<'de>,
84{
85    Option::<GenerationConfig>::deserialize(deserializer)
86        .map(|value| value.and_then(GenerationConfig::into_boxed_if_non_empty))
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn empty_generation_config_deserializes_to_none() {
95        let request: GenerateContentRequest = serde_json::from_str(
96            r#"{
97                "contents": [],
98                "generationConfig": {}
99            }"#,
100        )
101        .unwrap();
102
103        assert!(request.generation_config.is_none());
104    }
105
106    #[test]
107    fn boxed_generation_config_is_smaller_than_inline_option() {
108        use std::mem::size_of;
109
110        assert!(size_of::<Option<Box<GenerationConfig>>>() < size_of::<Option<GenerationConfig>>());
111    }
112}