XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Enhance Gemini provider with cookie handling, XSRF retry, and conversation fixes (#3501)

* Gemini: cookie snapshot, XSRF retry, prompt Add robust cookie handling, XSRF retry logic and prompt normalization for the Gemini provider. - Introduce _normalize_messages and _resolve_gemini_prompt to handle prompt-only and messages inputs. - Improve unknown-model error to list supported models. - Use a request-local cookie snapshot (request_cookies) so original cookie dict isn't mutated during requests. - start_auto_refresh now accepts cookies, waits before rotating, and updates stored cookies only when appropriate. - Detect XSRF errors (_is_xsrf_error) and retry by refetching snlm0e/sid and updating params/data. - Propagate request_cookies through upload_images, image responses and Authorization generation. - Add/adjust unit tests to cover prompt-only requests, XSRF retry flow, and auto-refresh behaviors. * Update test_gemini.py * Fix Gemini conversation reuse for anonymous users Gemini returns conversation identifiers to anonymous sessions but rejects them on subsequent turns with BardErrorInfo 1096. This commit fixes the issue by discarding conversation handles for unauthenticated users and keeping full message history instead, while maintaining conversation reuse for authenticated sessions. Adds helper functions `_has_authenticated_session()` and `_resolve_gemini_conversation()` to handle the logic, and includes tests for both anonymous and authenticated conversation flows. * Add gemini-3.6 & expanded thinking support Align Gemini provider with authenticated Gemini Web behavior: switch default/aliases to gemini-3.6-flash, introduce a boolean expanded_thinking option (mapped to request field 80) instead of legacy per-depth think, and expand the request payload to 97 entries. Populate key request fields (79=model, 80=expanded, 17=turn counter, 96=first-turn flag) and wire conversation.turn_index through to requests. Add/adjust MODEL_ALIASES and EXPANDED_MODEL_ALIASES, register new models in ModelRegistry and any_model_map, and update unit tests to validate the new format and mappings.

2e84a647
Ammar <ammar.alkotb@gmail.com>
提交于

代码差异

5 个文件 +188 -65
Modified etc/unittest/test_gemini.py +92 -30
@@ -27,6 +27,7 @@ from g4f.Provider.needs_auth.Gemini import (
27 27 )
28 28 from g4f.errors import MissingAuthError, ResponseError, ResponseStatusError
29 29 from g4f.models import ModelRegistry
30 from g4f.providers.any_model_map import model_map
30 31
31 32 GEMINI_MODULE = importlib.import_module("g4f.Provider.needs_auth.Gemini")
32 33
@@ -83,45 +84,96 @@ class GeminiHelpersTest(unittest.TestCase):
83 84 self.assertIsNone(field_13[11])
84 85 self.assertEqual(field_13[12], 2)
85 86
86 def test_mode_categories_and_default_thinking_depths(self):
87 def test_current_models_match_authenticated_web_request_fields(self):
87 88 expected = {
88 "gemini-3.5-flash": (1, 4),
89 "gemini-3.5-flash-thinking": (2, 0),
90 "gemini-3.1-pro": (3, 4),
91 "gemini-auto": (4, 4),
92 "gemini-3.5-flash-thinking-lite": (5, 0),
93 "gemini-flash-lite": (6, 4),
89 "gemini-3.6-flash": (1, 1),
90 "gemini-3.5-flash-lite": (6, 1),
91 "gemini-3.1-pro": (3, 1),
94 92 }
95 93
96 for requested, (mode, default_think) in expected.items():
94 for requested, (mode, request_96) in expected.items():
97 95 with self.subTest(model=requested):
98 model, think = _resolve_model(requested)
96 model, expanded = _resolve_model(requested)
99 97 request = Gemini.build_request(
100 "test", "en", model, think, request_uuid="test-request"
98 "test", "en", model, expanded, request_uuid="test-request"
101 99 )
100 self.assertEqual(len(request), 97)
101 self.assertEqual(request[6], [1])
102 self.assertIsNone(request[9])
103 self.assertEqual(request[17], [[0]])
104 self.assertEqual(request[41], [1])
105 self.assertEqual(request[68], 2)
102 106 self.assertEqual(request[79], mode)
103 self.assertEqual(request[17], [[default_think]])
107 self.assertEqual(request[80], 1)
108 self.assertEqual(request[91], 0)
109 self.assertEqual(request[96], request_96)
110
111 def test_expanded_thinking_is_independent_for_every_current_model(self):
112 for requested in (
113 "gemini-3.6-flash",
114 "gemini-3.5-flash-lite",
115 "gemini-3.1-pro",
116 ):
117 with self.subTest(model=requested):
118 model, _ = _resolve_model(requested)
119 request = Gemini.build_request(
120 "test", "en", model, True, request_uuid="test-request"
121 )
122 self.assertEqual(request[17], [[0]])
123 self.assertEqual(request[80], 2)
124 self.assertEqual(request[96], 1)
104 125
105 def test_explicit_thinking_depths_are_preserved(self):
126 def test_legacy_thinking_depths_map_to_binary_expanded_option(self):
106 127 for requested_depth in range(5):
107 128 with self.subTest(depth=requested_depth):
108 model, think = _resolve_model(
129 model, expanded = _resolve_model(
109 130 f"gemini-3.5-flash-thinking@think={requested_depth}"
110 131 )
132 self.assertEqual(model, "gemini-3.6-flash")
133 self.assertEqual(expanded, requested_depth <= 2)
134
135 def test_conversation_turn_counter_uses_request_field_17(self):
136 for model, expanded in (
137 ("gemini-3.6-flash", False),
138 ("gemini-3.6-flash", True),
139 ("gemini-3.5-flash-lite", False),
140 ("gemini-3.1-pro", False),
141 ):
142 with self.subTest(model=model, expanded=expanded):
143 conversation = Conversation(
144 "conversation-id",
145 "response-id",
146 "choice-id",
147 model,
148 turn_index=2,
149 )
150
111 151 request = Gemini.build_request(
112 "test", "en", model, think, request_uuid="test-request"
152 "test",
153 "en",
154 model,
155 expanded,
156 conversation=conversation,
157 request_uuid="test-request",
113 158 )
114 self.assertEqual(request[17], [[requested_depth]])
159
160 self.assertEqual(request[17], [[2]])
161 self.assertEqual(request[96], 0)
115 162
116 163 def test_legacy_model_names_resolve_to_current_modes(self):
117 164 expected = {
118 "gemini-2.0": "gemini-3.5-flash",
119 "gemini-2.0-flash": "gemini-3.5-flash",
120 "gemini-2.0-flash-thinking": "gemini-3.5-flash-thinking",
121 "gemini-2.0-flash-thinking-with-apps": "gemini-3.5-flash-thinking",
122 "gemini-2.5-flash": "gemini-3.5-flash",
165 "gemini-2.0": "gemini-3.6-flash",
166 "gemini-2.0-flash": "gemini-3.6-flash",
167 "gemini-2.0-flash-thinking": "gemini-3.6-flash",
168 "gemini-2.0-flash-thinking-with-apps": "gemini-3.6-flash",
169 "gemini-2.5-flash": "gemini-3.6-flash",
170 "gemini-3.5-flash": "gemini-3.6-flash",
171 "gemini-3.5-flash-thinking": "gemini-3.6-flash",
172 "gemini-auto": "gemini-3.6-flash",
123 173 "gemini-2.5-pro": "gemini-3.1-pro",
124 "gemini-3.1-flash-lite": "gemini-flash-lite",
174 "gemini-3.1-flash-lite": "gemini-3.5-flash-lite",
175 "gemini-flash-lite": "gemini-3.5-flash-lite",
176 "gemini-3.5-flash-thinking-lite": "gemini-3.5-flash-lite",
125 177 }
126 178
127 179 for legacy_name, current_name in expected.items():
@@ -131,16 +183,20 @@ class GeminiHelpersTest(unittest.TestCase):
131 183
132 184 def test_unknown_model_lists_supported_models(self):
133 185 with self.assertRaises(ValueError) as context:
134 _resolve_model("gemini-3.6-flash")
186 _resolve_model("gemini-3.7-flash")
135 187
136 188 message = str(context.exception)
137 self.assertIn("Unknown Gemini model: gemini-3.6-flash", message)
189 self.assertIn("Unknown Gemini model: gemini-3.7-flash", message)
138 190 self.assertIn("Supported models:", message)
139 self.assertIn("gemini-3.5-flash", message)
191 self.assertIn("gemini-3.6-flash", message)
192 self.assertIn("gemini-3.5-flash-lite", message)
140 193
141 194 def test_public_model_registry_exposes_current_models(self):
142 195 self.assertEqual(ModelRegistry.get("gemini-auto").name, "gemini-auto")
143 196 for model in (
197 "gemini-3.6-flash",
198 "gemini-3.5-flash-lite",
199 "gemini-3.1-pro",
144 200 "gemini-3.5-flash-thinking",
145 201 "gemini-auto",
146 202 "gemini-3.5-flash-thinking-lite",
@@ -148,6 +204,7 @@ class GeminiHelpersTest(unittest.TestCase):
148 204 ):
149 205 with self.subTest(model=model):
150 206 self.assertEqual(ModelRegistry.get(model).name, model)
207 self.assertEqual(model_map[model]["Gemini"], model)
151 208
152 209 def test_prompt_only_requests_accept_missing_messages(self):
153 210 messages = _normalize_messages(None)
@@ -170,21 +227,21 @@ class GeminiHelpersTest(unittest.TestCase):
170 227 "conversation-id",
171 228 "response-id",
172 229 "choice-id",
173 "gemini-auto",
230 "gemini-3.6-flash",
174 231 )
175 232
176 233 self.assertFalse(_has_authenticated_session({}))
177 234 resolved = _resolve_gemini_conversation(
178 235 conversation,
179 "gemini-auto",
236 "gemini-3.6-flash",
180 237 {},
181 238 )
182 239 prompt = _resolve_gemini_prompt(messages, None, resolved)
183 240 request = Gemini.build_request(
184 241 prompt,
185 242 "en",
186 "gemini-auto",
187 4,
243 "gemini-3.6-flash",
244 False,
188 245 conversation=resolved,
189 246 request_uuid="test-request",
190 247 )
@@ -216,7 +273,7 @@ class GeminiHelpersTest(unittest.TestCase):
216 273 self.assertIsNone(
217 274 _resolve_gemini_conversation(
218 275 conversation,
219 "gemini-auto",
276 "gemini-3.1-pro",
220 277 cookies,
221 278 )
222 279 )
@@ -254,6 +311,7 @@ class GeminiHelpersTest(unittest.TestCase):
254 311
255 312 with self.assertRaises(MissingAuthError):
256 313 ProbeGemini.validate_model_access("gemini-3.1-pro")
314 ProbeGemini.validate_model_access("gemini-3.6-flash")
257 315 ProbeGemini.validate_model_access("gemini-3.5-flash")
258 316 ProbeGemini.validate_model_access(
259 317 "gemini-3.1-pro", allow_model_fallback=True
@@ -382,10 +440,11 @@ class GeminiStreamTest(unittest.IsolatedAsyncioTestCase):
382 440 side_effect=check_status,
383 441 ):
384 442 generator = ProbeGemini.create_async_generator(
385 model="gemini-3.5-flash",
443 model="gemini-3.6-flash",
386 444 messages=None,
387 445 prompt="prompt-only request",
388 446 cookies=source_cookies,
447 expanded_thinking=True,
389 448 max_retries=1,
390 449 )
391 450 await generator.__anext__()
@@ -408,6 +467,9 @@ class GeminiStreamTest(unittest.IsolatedAsyncioTestCase):
408 467 self.assertEqual(source_cookies["__Secure-1PSIDTS"], "old")
409 468 request = json.loads(json.loads(session.calls[1]["data"]["f.req"])[1])
410 469 self.assertEqual(request[0][0], "prompt-only request")
470 self.assertEqual(request[79], 1)
471 self.assertEqual(request[80], 2)
472 self.assertEqual(request[96], 1)
411 473
412 474 async def test_auto_refresh_waits_before_rotating(self):
413 475 with patch.object(
Modified g4f/Provider/needs_auth/Gemini.py +75 -32
@@ -126,21 +126,35 @@ MAX_CONCURRENT_UPLOADS = 4
126 126 RETRYABLE_STATUS_CODES = {408, 425, 429, 500, 502, 503, 504}
127 127
128 128 models = {
129 "gemini-3.5-flash": {"mode": 1, "think": 4},
130 "gemini-3.5-flash-thinking": {"mode": 2, "think": 0},
131 "gemini-3.1-pro": {"mode": 3, "think": 4},
132 "gemini-auto": {"mode": 4, "think": 4},
133 "gemini-3.5-flash-thinking-lite": {"mode": 5, "think": 0},
134 "gemini-flash-lite": {"mode": 6, "think": 4},
129 # Values captured from authenticated Gemini Web requests. The model is
130 # selected by field 79; expanded thinking is an independent field 80.
131 "gemini-3.6-flash": {"mode": 1},
132 "gemini-3.5-flash-lite": {"mode": 6},
133 "gemini-3.1-pro": {"mode": 3},
135 134 }
136 135 MODEL_ALIASES = {
137 "gemini-2.0": "gemini-3.5-flash",
138 "gemini-2.0-flash": "gemini-3.5-flash",
139 "gemini-2.0-flash-thinking": "gemini-3.5-flash-thinking",
140 "gemini-2.0-flash-thinking-with-apps": "gemini-3.5-flash-thinking",
141 "gemini-2.5-flash": "gemini-3.5-flash",
136 "gemini-2.0": "gemini-3.6-flash",
137 "gemini-2.0-flash": "gemini-3.6-flash",
138 "gemini-2.0-flash-thinking": "gemini-3.6-flash",
139 "gemini-2.0-flash-thinking-with-apps": "gemini-3.6-flash",
140 "gemini-2.5-flash": "gemini-3.6-flash",
142 141 "gemini-2.5-pro": "gemini-3.1-pro",
143 "gemini-3.1-flash-lite": "gemini-flash-lite",
142 "gemini-3.1-flash-lite": "gemini-3.5-flash-lite",
143 "gemini-3.5-flash": "gemini-3.6-flash",
144 "gemini-3.5-flash-thinking": "gemini-3.6-flash",
145 "gemini-3.6-flash-thinking": "gemini-3.6-flash",
146 "gemini-auto": "gemini-3.6-flash",
147 "gemini-3.5-flash-thinking-lite": "gemini-3.5-flash-lite",
148 "gemini-3.5-flash-lite-thinking": "gemini-3.5-flash-lite",
149 "gemini-flash-lite": "gemini-3.5-flash-lite",
150 }
151 EXPANDED_MODEL_ALIASES = {
152 "gemini-2.0-flash-thinking",
153 "gemini-2.0-flash-thinking-with-apps",
154 "gemini-3.5-flash-thinking",
155 "gemini-3.6-flash-thinking",
156 "gemini-3.5-flash-thinking-lite",
157 "gemini-3.5-flash-lite-thinking",
144 158 }
145 159
146 160
@@ -223,10 +237,12 @@ async def _iter_response_lines(
223 237 yield buffer.decode("utf-8", errors="replace")
224 238
225 239
226 def _resolve_model(model: str, think_override: int = None) -> tuple[str, int]:
240 def _resolve_model(model: str, think_override: int = None) -> tuple[str, bool]:
241 requested_model = model
227 242 think_mode = think_override
228 243 if "@think=" in model:
229 244 model, think_value = model.rsplit("@think=", 1)
245 requested_model = model
230 246 try:
231 247 think_mode = int(think_value)
232 248 except ValueError as exc:
@@ -240,7 +256,12 @@ def _resolve_model(model: str, think_override: int = None) -> tuple[str, int]:
240 256 f"Unknown Gemini model: {model}. "
241 257 f"Supported models: {', '.join(models)}"
242 258 )
243 return model, models[model]["think"] if think_mode is None else think_mode
259 expanded_thinking = (
260 requested_model in EXPANDED_MODEL_ALIASES
261 if think_mode is None
262 else think_mode <= 2
263 )
264 return model, expanded_thinking
244 265
245 266
246 267 def _normalize_messages(messages: Messages | None) -> Messages:
@@ -276,7 +297,9 @@ def _resolve_gemini_conversation(
276 297 ):
277 298 if conversation is None:
278 299 return None
279 if getattr(conversation, "model", None) != model:
300 model = MODEL_ALIASES.get(model, model)
301 conversation_model = getattr(conversation, "model", None)
302 if MODEL_ALIASES.get(conversation_model, conversation_model) != model:
280 303 return None
281 304 # Gemini currently returns conversation identifiers to anonymous sessions,
282 305 # but rejects those identifiers with BardErrorInfo 1096 when they are used
@@ -299,7 +322,7 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin):
299 322 active_by_default = True
300 323 use_nodriver = True
301 324
302 default_model = "gemini-3.5-flash"
325 default_model = "gemini-3.6-flash"
303 326 default_image_model = default_model
304 327 default_vision_model = default_model
305 328 image_models = [default_image_model]
@@ -442,9 +465,8 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin):
442 465 @classmethod
443 466 def get_model_headers(cls, model: str) -> dict[str, str]:
444 467 family = MODEL_FAMILIES.get(model)
445 # The 80-field request's mode category selects Flash/Thinking/Lite.
446 # Pro additionally needs the account-specific model header or Google
447 # silently routes it back to Flash.
468 # Request field 79 selects the model. Pro additionally needs the
469 # account-specific model header or Google silently routes it to Flash.
448 470 if family != "pro":
449 471 return {}
450 472 for model_data in cls._account_models.values():
@@ -458,6 +480,7 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin):
458 480 model: str,
459 481 allow_model_fallback: bool = False,
460 482 ) -> None:
483 model = MODEL_ALIASES.get(model, model)
461 484 if allow_model_fallback or cls._account_status is None:
462 485 return
463 486 if cls._account_status == ACCOUNT_STATUS_UNAUTHENTICATED:
@@ -508,12 +531,11 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin):
508 531 audio: dict = None,
509 532 auth_user: int | str = None,
510 533 think_override: int = None,
534 expanded_thinking: bool = None,
511 535 **kwargs
512 536 ) -> AsyncResult:
513 537 messages = _normalize_messages(messages)
514 538 model = model or cls.default_model
515 if cls.model_aliases and model in cls.model_aliases:
516 model = cls.model_aliases[model]
517 539 if audio is not None or model == "gemini-audio":
518 540 prompt = format_media_prompt(messages, prompt)
519 541 filename = get_filename(["gemini"], prompt, ".ogx", prompt)
@@ -524,7 +546,11 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin):
524 546 f.write(chunk)
525 547 yield AudioResponse(f"/media/{filename}", text=prompt)
526 548 return
527 if think_override is None:
549 if expanded_thinking is not None:
550 if not isinstance(expanded_thinking, bool):
551 raise TypeError("expanded_thinking must be a boolean")
552 think_override = 0 if expanded_thinking else 4
553 elif think_override is None:
528 554 think_override = {
529 555 "none": 4,
530 556 "minimal": 4,
@@ -533,7 +559,7 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin):
533 559 "high": 1,
534 560 "xhigh": 0,
535 561 }.get(kwargs.get("reasoning_effort"))
536 model, think_mode = _resolve_model(model, think_override)
562 model, expanded_thinking = _resolve_model(model, think_override)
537 563 if cookies is not None:
538 564 cls._cookies = cookies
539 565 elif cls._cookies is None:
@@ -631,7 +657,7 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin):
631 657 'f.req': json.dumps([None, json.dumps(cls.build_request(
632 658 prompt,
633 659 model=model,
634 think_mode=think_mode,
660 expanded_thinking=expanded_thinking,
635 661 language=language,
636 662 conversation=conversation,
637 663 uploads=uploads,
@@ -760,6 +786,11 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin):
760 786 response_part[1][1],
761 787 response_part[4][0][0],
762 788 model,
789 turn_index=(
790 getattr(conversation, "turn_index", 0) + 1
791 if conversation is not None
792 else 1
793 ),
763 794 )
764 795 except (IndexError, TypeError):
765 796 pass
@@ -896,14 +927,19 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin):
896 927 prompt: str,
897 928 language: str,
898 929 model: str,
899 think_mode: int,
930 expanded_thinking: bool = False,
900 931 conversation: Conversation = None,
901 932 uploads: list[list[str, str]] = None,
902 933 tools: list[list[str]] = None,
903 934 request_uuid: str = None,
904 935 ) -> list:
905 936 image_list = [[[image_url, 1], image_name] for image_url, image_name in uploads] if uploads else []
906 request = [None] * 80
937 turn_index = (
938 getattr(conversation, "turn_index", 0)
939 if conversation is not None
940 else 0
941 )
942 request = [None] * 97
907 943 request[0] = [prompt, 0, None, image_list, None, None, 0]
908 944 request[1] = [language]
909 945 request[2] = [
@@ -918,21 +954,26 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin):
918 954 None,
919 955 "",
920 956 ]
921 request[6] = [0]
957 request[6] = [1]
922 958 request[7] = 1
923 request[9] = tools or []
959 if tools:
960 request[9] = tools
924 961 request[10] = 1
925 962 request[11] = 0
926 request[17] = [[think_mode]]
963 request[17] = [[turn_index]]
927 964 request[18] = 0
928 965 request[27] = 1
929 966 request[30] = [4]
930 request[41] = [2]
967 request[41] = [1]
931 968 request[53] = 0
932 969 request[59] = request_uuid or str(uuid.uuid4())
933 970 request[61] = []
934 request[68] = 1
971 request[68] = 2
935 972 request[79] = models[model]["mode"]
973 request[80] = 2 if expanded_thinking else 1
974 request[91] = 0
975 # Gemini Web marks the first turn with 1 and follow-up turns with 0.
976 request[96] = int(conversation is None)
936 977 return request
937 978
938 979 @classmethod
@@ -1038,12 +1079,14 @@ class Conversation(JsonConversation):
1038 1079 conversation_id: str,
1039 1080 response_id: str,
1040 1081 choice_id: str,
1041 model: str
1082 model: str,
1083 turn_index: int = 0,
1042 1084 ) -> None:
1043 1085 self.conversation_id = conversation_id
1044 1086 self.response_id = response_id
1045 1087 self.choice_id = choice_id
1046 1088 self.model = model
1089 self.turn_index = turn_index
1047 1090
1048 1091
1049 1092 async def iter_filter_base64(chunks: AsyncIterator[bytes]) -> AsyncIterator[bytes]:
Modified g4f/Provider/needs_auth/gemini_utils.py +3 -3
@@ -16,11 +16,11 @@ MODEL_HEADER_AUXILIARY = {
16 16 "x-goog-ext-73010990-jspb": "[0]",
17 17 }
18 18 MODEL_FAMILIES = {
19 "gemini-3.5-flash": "flash",
20 "gemini-3.5-flash-thinking": "thinking",
19 "gemini-3.6-flash": "flash",
20 "gemini-3.5-flash-lite": "flash",
21 21 "gemini-3.1-pro": "pro",
22 22 }
23 ANONYMOUS_MODELS = {"gemini-3.5-flash", "gemini-auto"}
23 ANONYMOUS_MODELS = {"gemini-3.6-flash", "gemini-3.5-flash-lite"}
24 24 KNOWN_MODEL_IDS = {
25 25 "fbb127bbb056c959": "flash",
26 26 "5bf011840784117a": "thinking",
Modified g4f/models.py +12 -0
@@ -424,6 +424,18 @@ gemini_3_1_flash_lite = Model(
424 424 best_provider = "Gemini"
425 425 )
426 426
427 gemini_3_6_flash = Model(
428 name = 'gemini-3.6-flash',
429 base_provider = 'Google',
430 best_provider = "Gemini"
431 )
432
433 gemini_3_5_flash_lite = Model(
434 name = 'gemini-3.5-flash-lite',
435 base_provider = 'Google',
436 best_provider = "Gemini"
437 )
438
427 439 gemini_3_5_flash = Model(
428 440 name = 'gemini-3.5-flash',
429 441 base_provider = 'Google',
Modified g4f/providers/any_model_map.py +6 -0
@@ -311,6 +311,12 @@ model_map = {
311 311 "Pollinations": "gemini",
312 312 "Puter": "openrouter:google/gemini-3.5-flash"
313 313 },
314 "gemini-3.6-flash": {
315 "Gemini": "gemini-3.6-flash"
316 },
317 "gemini-3.5-flash-lite": {
318 "Gemini": "gemini-3.5-flash-lite"
319 },
314 320 "gemini-3.5-flash-thinking": {
315 321 "Gemini": "gemini-3.5-flash-thinking",
316 322 "GeminiPro": "gemini-3.5-flash-thinking"