Skip to main content

gemini_chat_api/
enums.rs

1//! Enums and constants for Gemini endpoints, headers, and models.
2
3use reqwest::header::{
4    HeaderMap, HeaderName, HeaderValue, ACCEPT, ACCEPT_LANGUAGE, CONTENT_TYPE, ORIGIN, REFERER,
5    USER_AGENT,
6};
7
8/// Gemini web endpoints used by this crate.
9#[derive(Debug, Clone, Copy)]
10pub enum Endpoint {
11    /// Initialize session and obtain the `SNlM0e` token.
12    Init,
13    /// Generate a chat response.
14    Generate,
15    /// Rotate authentication cookies.
16    RotateCookies,
17    /// Upload files/images.
18    Upload,
19}
20
21impl Endpoint {
22    /// Returns the URL for this endpoint.
23    pub fn url(&self) -> &'static str {
24        match self {
25            Endpoint::Init => "https://gemini.google.com/app",
26            Endpoint::Generate => "https://gemini.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate",
27            Endpoint::RotateCookies => "https://accounts.google.com/RotateCookies",
28            Endpoint::Upload => "https://content-push.googleapis.com/upload",
29        }
30    }
31}
32
33/// Returns default headers used by chat requests.
34///
35/// These headers mimic a modern browser; the web endpoint may reject requests
36/// without them.
37pub fn gemini_headers() -> HeaderMap {
38    let mut headers = HeaderMap::new();
39    headers.insert(
40        CONTENT_TYPE,
41        HeaderValue::from_static("application/x-www-form-urlencoded;charset=utf-8"),
42    );
43
44    headers.insert(
45        ORIGIN,
46        HeaderValue::from_static("https://gemini.google.com"),
47    );
48    headers.insert(
49        REFERER,
50        HeaderValue::from_static("https://gemini.google.com/"),
51    );
52    headers.insert(
53        HeaderName::from_static("x-same-domain"),
54        HeaderValue::from_static("1"),
55    );
56    // Chrome-like User-Agent (critical for authentication)
57    headers.insert(
58        USER_AGENT,
59        HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"),
60    );
61    // Browser headers
62    headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
63    headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9"));
64    headers.insert(
65        HeaderName::from_static("sec-ch-ua"),
66        HeaderValue::from_static(
67            "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"",
68        ),
69    );
70    headers.insert(
71        HeaderName::from_static("sec-ch-ua-mobile"),
72        HeaderValue::from_static("?0"),
73    );
74    headers.insert(
75        HeaderName::from_static("sec-ch-ua-platform"),
76        HeaderValue::from_static("\"Windows\""),
77    );
78    headers.insert(
79        HeaderName::from_static("sec-fetch-dest"),
80        HeaderValue::from_static("empty"),
81    );
82    headers.insert(
83        HeaderName::from_static("sec-fetch-mode"),
84        HeaderValue::from_static("cors"),
85    );
86    headers.insert(
87        HeaderName::from_static("sec-fetch-site"),
88        HeaderValue::from_static("same-origin"),
89    );
90    headers
91}
92
93/// Returns headers for cookie rotation requests.
94pub fn rotate_cookies_headers() -> HeaderMap {
95    let mut headers = HeaderMap::new();
96    headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
97    headers
98}
99
100/// Returns headers for file upload requests.
101///
102/// The `push-id` value is hard-coded to match the web endpoint's expectations.
103pub fn upload_headers() -> HeaderMap {
104    let mut headers = HeaderMap::new();
105    headers.insert(
106        HeaderName::from_static("push-id"),
107        HeaderValue::from_static("feeds/mcudyrk2a4khkz"),
108    );
109    headers
110}
111
112/// Available model configurations.
113///
114/// This enum controls both the model name and the extra header value used by
115/// the web endpoint.
116#[derive(Debug, Clone, Default)]
117pub enum Model {
118    /// Unspecified model - uses default.
119    #[default]
120    Unspecified,
121    /// Gemini 2.0 Flash.
122    G2_0Flash,
123    /// Gemini 2.0 Flash Thinking.
124    G2_0FlashThinking,
125    /// Gemini 2.5 Flash.
126    G2_5Flash,
127    /// Gemini 2.5 Pro.
128    G2_5Pro,
129    /// Gemini 2.0 Experimental Advanced (requires advanced subscription).
130    G2_0ExpAdvanced,
131    /// Gemini 2.5 Experimental Advanced (requires advanced subscription).
132    G2_5ExpAdvanced,
133    /// Gemini 3.0 Pro.
134    G3_0Pro,
135    /// Gemini 3.0 Flash.
136    G3_0Flash,
137    /// Gemini 3.0 Flash Thinking.
138    G3_0Thinking,
139}
140
141impl Model {
142    /// Returns the model name string used in saved conversation data.
143    pub fn name(&self) -> &'static str {
144        match self {
145            Model::Unspecified => "unspecified",
146            Model::G2_0Flash => "gemini-2.0-flash",
147            Model::G2_0FlashThinking => "gemini-2.0-flash-thinking",
148            Model::G2_5Flash => "gemini-2.5-flash",
149            Model::G2_5Pro => "gemini-2.5-pro",
150            Model::G2_0ExpAdvanced => "gemini-2.0-exp-advanced",
151            Model::G2_5ExpAdvanced => "gemini-2.5-exp-advanced",
152            Model::G3_0Pro => "gemini-3.0-pro",
153            Model::G3_0Flash => "gemini-3.0-flash",
154            Model::G3_0Thinking => "gemini-3.0-flash-thinking",
155        }
156    }
157
158    /// Returns model-specific headers for the web endpoint.
159    ///
160    /// The header key is `x-goog-ext-525001261-jspb`. `Model::Unspecified`
161    /// returns `None`, which means no extra model header is added.
162    pub fn headers(&self) -> Option<HeaderMap> {
163        let header_value = match self {
164            Model::Unspecified => return None,
165            Model::G2_0Flash => r#"[1,null,null,null,"f299729663a2343f"]"#,
166            Model::G2_0FlashThinking => r#"[null,null,null,null,"7ca48d02d802f20a"]"#,
167            Model::G2_5Flash => r#"[1,null,null,null,"35609594dbe934d8"]"#,
168            Model::G2_5Pro => r#"[1,null,null,null,"2525e3954d185b3c"]"#,
169            Model::G2_0ExpAdvanced => r#"[null,null,null,null,"b1e46a6037e6aa9f"]"#,
170            Model::G2_5ExpAdvanced => r#"[null,null,null,null,"203e6bb81620bcfe"]"#,
171            Model::G3_0Pro => {
172                r#"[1,null,null,null,"e6fa609c3fa255c0",null,null,0,[4],null,null,2]"#
173            }
174            Model::G3_0Thinking => {
175                r#"[1,null,null,null,"e051ce1aa80aa576",null,null,0,[4],null,null,2]"#
176            }
177            Model::G3_0Flash => {
178                r#"[1,null,null,null,"56fdd199312815e2",null,null,0,[4],null,null,2]"#
179            }
180        };
181
182        let mut headers = HeaderMap::new();
183        headers.insert(
184            HeaderName::from_static("x-goog-ext-525001261-jspb"),
185            HeaderValue::from_str(header_value).unwrap(),
186        );
187        Some(headers)
188    }
189
190    /// Returns `true` if this model requires an advanced subscription.
191    pub fn is_advanced_only(&self) -> bool {
192        matches!(self, Model::G2_0ExpAdvanced | Model::G2_5ExpAdvanced)
193    }
194
195    /// Creates a model from a name string.
196    ///
197    /// Accepted names are:
198    /// - `unspecified`
199    /// - `gemini-2.0-flash`
200    /// - `gemini-2.0-flash-thinking`
201    /// - `gemini-2.5-flash`
202    /// - `gemini-2.5-pro`
203    /// - `gemini-2.0-exp-advanced`
204    /// - `gemini-2.5-exp-advanced`
205    /// - `gemini-3.0-pro`
206    /// - `gemini-3.0-flash`
207    /// - `gemini-3.0-thinking`
208    ///
209    /// Note that `gemini-3.0-thinking` maps to [`Model::G3_0Thinking`], while
210    /// [`Model::name`] for that variant returns `gemini-3.0-flash-thinking`.
211    pub fn from_name(name: &str) -> Option<Self> {
212        match name {
213            "unspecified" => Some(Model::Unspecified),
214            "gemini-2.0-flash" => Some(Model::G2_0Flash),
215            "gemini-2.0-flash-thinking" => Some(Model::G2_0FlashThinking),
216            "gemini-2.5-flash" => Some(Model::G2_5Flash),
217            "gemini-2.5-pro" => Some(Model::G2_5Pro),
218            "gemini-2.0-exp-advanced" => Some(Model::G2_0ExpAdvanced),
219            "gemini-2.5-exp-advanced" => Some(Model::G2_5ExpAdvanced),
220            "gemini-3.0-pro" => Some(Model::G3_0Pro),
221            "gemini-3.0-flash" => Some(Model::G3_0Flash),
222            "gemini-3.0-thinking" => Some(Model::G3_0Thinking),
223            _ => None,
224        }
225    }
226}