Skip to main content

vtcode_llm/providers/gemini/
provider.rs

1use super::*;
2use vtcode_config::core::ModelConfig;
3
4pub struct GeminiProvider {
5    pub(super) api_key: Arc<str>,
6    pub(super) http_client: HttpClient,
7    pub(super) base_url: Arc<str>,
8    pub(super) model: Arc<str>,
9    pub(super) prompt_cache_enabled: bool,
10    pub(super) prompt_cache_settings: GeminiPromptCacheSettings,
11    pub(super) timeouts: TimeoutsConfig,
12    pub(super) model_behavior: Option<ModelConfig>,
13    /// Live `cachedContents` slot for explicit prompt-cache mode.
14    pub(super) explicit_cache: explicit_cache::ExplicitCacheState,
15}
16
17impl GeminiProvider {
18    pub fn new(api_key: String) -> Self {
19        Self::with_model_internal(
20            api_key,
21            models::google::GEMINI_3_FLASH_PREVIEW.to_string(),
22            None,
23            None,
24            TimeoutsConfig::default(),
25            None,
26        )
27    }
28
29    pub fn with_model(api_key: String, model: String) -> Self {
30        Self::with_model_internal(api_key, model, None, None, TimeoutsConfig::default(), None)
31    }
32
33    pub fn new_with_client(
34        api_key: String,
35        model: String,
36        http_client: reqwest::Client,
37        base_url: String,
38        timeouts: TimeoutsConfig,
39        prompt_cache_enabled: bool,
40        prompt_cache_settings: GeminiPromptCacheSettings,
41    ) -> Self {
42        Self {
43            api_key: Arc::from(api_key.as_str()),
44            http_client,
45            base_url: Arc::from(base_url.as_str()),
46            model: Arc::from(model.as_str()),
47            prompt_cache_enabled,
48            prompt_cache_settings,
49            timeouts,
50            model_behavior: None,
51            explicit_cache: explicit_cache::ExplicitCacheState::default(),
52        }
53    }
54
55    pub fn from_config(
56        api_key: Option<String>,
57        model: Option<String>,
58        base_url: Option<String>,
59        prompt_cache: Option<PromptCachingConfig>,
60        timeouts: Option<TimeoutsConfig>,
61        _anthropic: Option<AnthropicConfig>,
62        model_behavior: Option<ModelConfig>,
63    ) -> Self {
64        let api_key_value = api_key.unwrap_or_default();
65        let model_value = resolve_model(model, models::google::GEMINI_3_FLASH_PREVIEW);
66
67        Self::with_model_internal(
68            api_key_value,
69            model_value,
70            prompt_cache,
71            base_url,
72            timeouts.unwrap_or_default(),
73            model_behavior,
74        )
75    }
76
77    fn with_model_internal(
78        api_key: String,
79        model: String,
80        prompt_cache: Option<PromptCachingConfig>,
81        base_url: Option<String>,
82        timeouts: TimeoutsConfig,
83        model_behavior: Option<ModelConfig>,
84    ) -> Self {
85        use crate::http_client::HttpClientFactory;
86
87        let (prompt_cache_enabled, prompt_cache_settings) = extract_prompt_cache_settings(
88            prompt_cache,
89            |providers| &providers.gemini,
90            |cfg, provider_settings| {
91                cfg.enabled && provider_settings.enabled && provider_settings.mode != GeminiPromptCacheMode::Off
92            },
93        );
94
95        Self {
96            api_key: Arc::from(api_key.as_str()),
97            http_client: HttpClientFactory::for_llm(&timeouts),
98            base_url: Arc::from(
99                override_base_url(urls::GEMINI_API_BASE, base_url, Some(env_vars::GEMINI_BASE_URL)).as_str(),
100            ),
101            model: Arc::from(model.as_str()),
102            prompt_cache_enabled,
103            prompt_cache_settings,
104            timeouts,
105            model_behavior,
106            explicit_cache: explicit_cache::ExplicitCacheState::default(),
107        }
108    }
109
110    /// Handle HTTP response errors and convert to appropriate LLMError.
111    /// Uses shared rate limit detection from error_handling module.
112    #[inline]
113    pub(super) fn handle_http_error(status: reqwest::StatusCode, error_text: &str) -> LLMError {
114        let status_code = status.as_u16();
115
116        // Handle authentication errors
117        if status_code == 401 || status_code == 403 {
118            let formatted_error = error_display::format_llm_error(
119                "Gemini",
120                &format!(
121                    "Authentication failed: {error_text}. Check your GOOGLE_API_KEY or GEMINI_API_KEY environment variable."
122                ),
123            );
124            return LLMError::Authentication { message: formatted_error, metadata: None };
125        }
126
127        // Handle rate limit and quota errors using shared detection
128        if is_rate_limit_error(status_code, error_text) {
129            return LLMError::RateLimit { metadata: None };
130        }
131
132        // Handle invalid request errors
133        if status_code == 400 {
134            let formatted_error = error_display::format_llm_error("Gemini", &format!("Invalid request: {error_text}"));
135            return LLMError::InvalidRequest { message: formatted_error, metadata: None };
136        }
137
138        // Generic error for other cases
139        let formatted_error = error_display::format_llm_error("Gemini", &format!("HTTP {status}: {error_text}"));
140        LLMError::Provider { message: formatted_error, metadata: None }
141    }
142}