gemini-webapi 1.17.3__py3-none-any.whl → 1.18.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,5 @@
1
+ import re
2
+ import reprlib
1
3
  from typing import Any
2
4
 
3
5
  import orjson as json
@@ -5,63 +7,201 @@ import orjson as json
5
7
  from .logger import logger
6
8
 
7
9
 
8
- def get_nested_value(data: list, path: list[int], default: Any = None) -> Any:
10
+ _LENGTH_MARKER_PATTERN = re.compile(r"(\d+)\n")
11
+
12
+
13
+ def _get_char_count_for_utf16_units(
14
+ s: str, start_idx: int, utf16_units: int
15
+ ) -> tuple[int, int]:
16
+ """
17
+ Calculate the number of Python characters (code points) and actual UTF-16
18
+ units found.
19
+ """
20
+
21
+ count = 0
22
+ units = 0
23
+ limit = len(s)
24
+
25
+ while units < utf16_units and (start_idx + count) < limit:
26
+ char = s[start_idx + count]
27
+ u = 2 if ord(char) > 0xFFFF else 1
28
+ if units + u > utf16_units:
29
+ break
30
+ units += u
31
+ count += 1
32
+
33
+ return count, units
34
+
35
+
36
+ def get_nested_value(
37
+ data: Any, path: list[int | str], default: Any = None, verbose: bool = False
38
+ ) -> Any:
9
39
  """
10
- Safely get a value from a nested list by a sequence of indices.
40
+ Safely navigate through a nested structure (list or dict) using a sequence of keys/indices.
11
41
 
12
42
  Parameters
13
43
  ----------
14
- data: `list`
15
- The nested list to traverse.
16
- path: `list[int]`
17
- A list of indices representing the path to the desired value.
18
- default: `Any`, optional
19
- The default value to return if the path is not found.
44
+ data: `Any`
45
+ The nested structure to traverse.
46
+ path: `list[int | str]`
47
+ A list of indices or keys representing the path.
48
+ default: `Any`
49
+ Value to return if the path is invalid.
50
+ verbose: `bool`
51
+ If True, log debug information when the path cannot be fully traversed.
20
52
  """
21
53
 
22
54
  current = data
23
55
 
24
56
  for i, key in enumerate(path):
25
- try:
26
- current = current[key]
27
- except (IndexError, TypeError, KeyError):
28
- current_repr = repr(current)
29
- if len(current_repr) > 200:
30
- current_repr = f"{current_repr[:197]}..."
57
+ found = False
58
+ if isinstance(key, int):
59
+ if isinstance(current, list) and 0 <= key < len(current):
60
+ current = current[key]
61
+ found = True
62
+ elif isinstance(key, str):
63
+ if isinstance(current, dict) and key in current:
64
+ current = current[key]
65
+ found = True
66
+
67
+ if not found:
68
+ if verbose:
69
+ logger.debug(
70
+ f"Safe navigation: path {path} ended at index {i} (key '{key}'), "
71
+ f"returning default. Context: {reprlib.repr(current)}"
72
+ )
73
+ return default
74
+
75
+ return current if current is not None else default
31
76
 
77
+
78
+ def _parse_with_length_markers(content: str) -> list | None:
79
+ """
80
+ Parse streaming responses using length markers as hints.
81
+ Google's format: [length]\n[json_payload]\n
82
+ The length value includes the newline after the number and the newline after the JSON.
83
+ """
84
+
85
+ pos = 0
86
+ total_len = len(content)
87
+ collected_chunks = []
88
+
89
+ while pos < total_len:
90
+ while pos < total_len and content[pos].isspace():
91
+ pos += 1
92
+
93
+ if pos >= total_len:
94
+ break
95
+
96
+ match = _LENGTH_MARKER_PATTERN.match(content, pos=pos)
97
+ if not match:
98
+ break
99
+
100
+ length_val = match.group(1)
101
+ length = int(length_val)
102
+
103
+ # Content starts immediately after the digits.
104
+ # Google uses UTF-16 code units (JavaScript .length) for the length marker.
105
+ start_content = match.start() + len(length_val)
106
+ char_count, units_found = _get_char_count_for_utf16_units(
107
+ content, start_content, length
108
+ )
109
+ end_hint = start_content + char_count
110
+ pos = end_hint
111
+
112
+ if units_found < length:
32
113
  logger.debug(
33
- f"Safe navigation: path {path} ended at index {i} (key '{key}'), "
34
- f"returning default. Context: {current_repr}"
114
+ f"Chunk at pos {start_content} is truncated. Expected {length} UTF-16 units, got {units_found}."
35
115
  )
116
+ break
36
117
 
37
- return default
118
+ chunk = content[start_content:end_hint].strip()
119
+ if not chunk:
120
+ continue
38
121
 
39
- if current is None and default is not None:
40
- return default
122
+ try:
123
+ parsed = json.loads(chunk)
124
+ if isinstance(parsed, list):
125
+ collected_chunks.extend(parsed)
126
+ else:
127
+ collected_chunks.append(parsed)
128
+ except json.JSONDecodeError:
129
+ logger.warning(
130
+ f"Failed to parse chunk at pos {start_content} with length {length}. "
131
+ f"Snippet: {reprlib.repr(chunk)}"
132
+ )
41
133
 
42
- return current
134
+ return collected_chunks if collected_chunks else None
43
135
 
44
136
 
45
- def extract_json_from_response(text: str) -> list:
137
+ def parse_stream_frames(buffer: str) -> tuple[list[Any], str]:
46
138
  """
47
- Clean and extract the JSON content from a Google API response.
139
+ Parse as many JSON frames as possible from an accumulated buffer.
140
+
141
+ This function implements Google's length-prefixed framing protocol. Each frame starts
142
+ with a length marker (number of characters) followed by a newline and the JSON content.
143
+ If a frame is partially received, it stays in the buffer for the next call.
48
144
 
49
145
  Parameters
50
146
  ----------
51
- text: `str`
52
- The raw response text from a Google API.
147
+ buffer: `str`
148
+ The accumulated string buffer containing raw streaming data from the API.
53
149
 
54
150
  Returns
55
151
  -------
56
- `list`
57
- The extracted JSON array or object (should be an array).
58
-
59
- Raises
60
- ------
61
- `TypeError`
62
- If the input is not a string.
63
- `ValueError`
64
- If no JSON object is found or the response is empty.
152
+ `tuple[list[Any], str]`
153
+ A tuple containing:
154
+ - A list of parsed JSON objects (envelopes) extracted from the buffer.
155
+ - The remaining unparsed part of the buffer (incomplete frames).
156
+ """
157
+
158
+ pos = 0
159
+ total_len = len(buffer)
160
+ parsed_objects = []
161
+
162
+ while pos < total_len:
163
+ while pos < total_len and buffer[pos].isspace():
164
+ pos += 1
165
+
166
+ if pos >= total_len:
167
+ break
168
+
169
+ match = _LENGTH_MARKER_PATTERN.match(buffer, pos=pos)
170
+ if not match:
171
+ # If we have a prefix but no length marker yet, wait for more data
172
+ break
173
+
174
+ length_val = match.group(1)
175
+ length = int(length_val)
176
+ start_content = match.start() + len(length_val)
177
+ char_count, units_found = _get_char_count_for_utf16_units(
178
+ buffer, start_content, length
179
+ )
180
+
181
+ if units_found < length:
182
+ # Chunk is truncated, wait for more data
183
+ break
184
+
185
+ end_pos = start_content + char_count
186
+ chunk = buffer[start_content:end_pos].strip()
187
+ pos = end_pos
188
+
189
+ if chunk:
190
+ try:
191
+ parsed = json.loads(chunk)
192
+ if isinstance(parsed, list):
193
+ parsed_objects.extend(parsed)
194
+ else:
195
+ parsed_objects.append(parsed)
196
+ except json.JSONDecodeError:
197
+ logger.debug(f"Streaming: Failed to parse chunk: {reprlib.repr(chunk)}")
198
+
199
+ return parsed_objects, buffer[pos:]
200
+
201
+
202
+ def extract_json_from_response(text: str) -> list:
203
+ """
204
+ Extract and normalize JSON content from a Google API response.
65
205
  """
66
206
 
67
207
  if not isinstance(text, str):
@@ -69,12 +209,42 @@ def extract_json_from_response(text: str) -> list:
69
209
  f"Input text is expected to be a string, got {type(text).__name__} instead."
70
210
  )
71
211
 
72
- # Find the first line which is valid JSON
73
- for line in text.splitlines():
212
+ content = text
213
+ if content.startswith(")]}'"):
214
+ content = content[4:]
215
+
216
+ content = content.lstrip()
217
+
218
+ # Extract with a length marker
219
+ result = _parse_with_length_markers(content)
220
+ if result is not None:
221
+ return result
222
+
223
+ # Extract the entire content
224
+ content_stripped = content.strip()
225
+ try:
226
+ parsed = json.loads(content_stripped)
227
+ return parsed if isinstance(parsed, list) else [parsed]
228
+ except json.JSONDecodeError:
229
+ pass
230
+
231
+ # Extract with NDJSON
232
+ collected_lines = []
233
+ for line in content_stripped.splitlines():
234
+ line = line.strip()
235
+ if not line:
236
+ continue
74
237
  try:
75
- return json.loads(line.strip())
238
+ parsed = json.loads(line)
76
239
  except json.JSONDecodeError:
77
240
  continue
78
241
 
79
- # If no JSON is found, raise ValueError
242
+ if isinstance(parsed, list):
243
+ collected_lines.extend(parsed)
244
+ elif isinstance(parsed, dict):
245
+ collected_lines.append(parsed)
246
+
247
+ if collected_lines:
248
+ return collected_lines
249
+
80
250
  raise ValueError("Could not find a valid JSON object or array in the response.")
@@ -2,27 +2,29 @@ import os
2
2
  import time
3
3
  from pathlib import Path
4
4
 
5
- from httpx import AsyncClient
5
+ from httpx import AsyncClient, Cookies
6
6
 
7
7
  from ..constants import Endpoint, Headers
8
8
  from ..exceptions import AuthError
9
9
 
10
10
 
11
- async def rotate_1psidts(cookies: dict, proxy: str | None = None) -> str:
11
+ async def rotate_1psidts(
12
+ cookies: dict | Cookies, proxy: str | None = None
13
+ ) -> tuple[str | None, Cookies | None]:
12
14
  """
13
15
  Refresh the __Secure-1PSIDTS cookie and store the refreshed cookie value in cache file.
14
16
 
15
17
  Parameters
16
18
  ----------
17
- cookies : `dict`
19
+ cookies : `dict | httpx.Cookies`
18
20
  Cookies to be used in the request.
19
21
  proxy: `str`, optional
20
22
  Proxy URL.
21
23
 
22
24
  Returns
23
25
  -------
24
- `str`
25
- New value of the __Secure-1PSIDTS cookie.
26
+ `tuple[str | None, httpx.Cookies | None]`
27
+ New value of the __Secure-1PSIDTS cookie and the full updated cookies jar.
26
28
 
27
29
  Raises
28
30
  ------
@@ -38,22 +40,39 @@ async def rotate_1psidts(cookies: dict, proxy: str | None = None) -> str:
38
40
  or (Path(__file__).parent / "temp")
39
41
  )
40
42
  path.mkdir(parents=True, exist_ok=True)
41
- filename = f".cached_1psidts_{cookies['__Secure-1PSID']}.txt"
43
+
44
+ # Safely get __Secure-1PSID value for filename
45
+ if isinstance(cookies, Cookies):
46
+ # Prefer .google.com domain to avoid CookieConflict
47
+ secure_1psid = cookies.get(
48
+ "__Secure-1PSID", domain=".google.com"
49
+ ) or cookies.get("__Secure-1PSID")
50
+ else:
51
+ secure_1psid = cookies.get("__Secure-1PSID")
52
+
53
+ if not secure_1psid:
54
+ return None, None
55
+
56
+ filename = f".cached_1psidts_{secure_1psid}.txt"
42
57
  path = path / filename
43
58
 
44
59
  # Check if the cache file was modified in the last minute to avoid 429 Too Many Requests
45
- if not (path.is_file() and time.time() - os.path.getmtime(path) <= 60):
46
- async with AsyncClient(proxy=proxy) as client:
47
- response = await client.post(
48
- url=Endpoint.ROTATE_COOKIES.value,
49
- headers=Headers.ROTATE_COOKIES.value,
50
- cookies=cookies,
51
- data='[000,"-0000000000000000000"]',
52
- )
53
- if response.status_code == 401:
54
- raise AuthError
55
- response.raise_for_status()
56
-
57
- if new_1psidts := response.cookies.get("__Secure-1PSIDTS"):
58
- path.write_text(new_1psidts)
59
- return new_1psidts
60
+ if path.is_file() and time.time() - os.path.getmtime(path) <= 60:
61
+ return path.read_text(), None
62
+
63
+ async with AsyncClient(http2=True, proxy=proxy) as client:
64
+ response = await client.post(
65
+ url=Endpoint.ROTATE_COOKIES,
66
+ headers=Headers.ROTATE_COOKIES.value,
67
+ cookies=cookies,
68
+ content='[000,"-0000000000000000000"]',
69
+ )
70
+ if response.status_code == 401:
71
+ raise AuthError
72
+ response.raise_for_status()
73
+
74
+ if new_1psidts := response.cookies.get("__Secure-1PSIDTS"):
75
+ path.write_text(new_1psidts)
76
+ return new_1psidts, response.cookies
77
+
78
+ return None, response.cookies
@@ -1,22 +1,38 @@
1
+ import io
2
+ import random
1
3
  from pathlib import Path
2
4
 
3
5
  from httpx import AsyncClient
4
- from pydantic import validate_call
6
+ from pydantic import ConfigDict, validate_call
5
7
 
6
8
  from ..constants import Endpoint, Headers
7
9
 
8
10
 
9
- @validate_call
10
- async def upload_file(file: str | Path, proxy: str | None = None) -> str:
11
+ def _generate_random_name(extension: str = ".txt") -> str:
12
+ """
13
+ Generate a random filename using a large integer for better performance.
14
+ """
15
+
16
+ return f"input_{random.randint(1000000, 9999999)}{extension}"
17
+
18
+
19
+ @validate_call(config=ConfigDict(arbitrary_types_allowed=True))
20
+ async def upload_file(
21
+ file: str | Path | bytes | io.BytesIO,
22
+ proxy: str | None = None,
23
+ filename: str | None = None,
24
+ ) -> str:
11
25
  """
12
26
  Upload a file to Google's server and return its identifier.
13
27
 
14
28
  Parameters
15
29
  ----------
16
- file : `str` | `Path`
17
- Path to the file to be uploaded.
30
+ file : `str` | `Path` | `bytes` | `io.BytesIO`
31
+ Path to the file or file content to be uploaded.
18
32
  proxy: `str`, optional
19
33
  Proxy URL.
34
+ filename: `str`, optional
35
+ Name of the file to be uploaded. Required if file is bytes or BytesIO.
20
36
 
21
37
  Returns
22
38
  -------
@@ -30,28 +46,43 @@ async def upload_file(file: str | Path, proxy: str | None = None) -> str:
30
46
  If the upload request failed.
31
47
  """
32
48
 
33
- with open(file, "rb") as f:
34
- file = f.read()
49
+ if isinstance(file, (str, Path)):
50
+ file_path = Path(file)
51
+ if not file_path.is_file():
52
+ raise ValueError(f"{file_path} is not a valid file.")
53
+ if not filename:
54
+ filename = file_path.name
55
+ file_content = file_path.read_bytes()
56
+ elif isinstance(file, io.BytesIO):
57
+ file_content = file.getvalue()
58
+ if not filename:
59
+ filename = _generate_random_name()
60
+ elif isinstance(file, bytes):
61
+ file_content = file
62
+ if not filename:
63
+ filename = _generate_random_name()
64
+ else:
65
+ raise ValueError(f"Unsupported file type: {type(file)}")
35
66
 
36
- async with AsyncClient(proxy=proxy) as client:
67
+ async with AsyncClient(http2=True, proxy=proxy) as client:
37
68
  response = await client.post(
38
- url=Endpoint.UPLOAD.value,
69
+ url=Endpoint.UPLOAD,
39
70
  headers=Headers.UPLOAD.value,
40
- files={"file": file},
71
+ files={"file": (filename, file_content)},
41
72
  follow_redirects=True,
42
73
  )
43
74
  response.raise_for_status()
44
75
  return response.text
45
76
 
46
77
 
47
- def parse_file_name(file: str | Path) -> str:
78
+ def parse_file_name(file: str | Path | bytes | io.BytesIO) -> str:
48
79
  """
49
- Parse the file name from the given path.
80
+ Parse the file name from the given path or generate a random one for in-memory data.
50
81
 
51
82
  Parameters
52
83
  ----------
53
- file : `str` | `Path`
54
- Path to the file.
84
+ file : `str` | `Path` | `bytes` | `io.BytesIO`
85
+ Path to the file or file content.
55
86
 
56
87
  Returns
57
88
  -------
@@ -59,8 +90,10 @@ def parse_file_name(file: str | Path) -> str:
59
90
  File name with extension.
60
91
  """
61
92
 
62
- file = Path(file)
63
- if not file.is_file():
64
- raise ValueError(f"{file} is not a valid file.")
93
+ if isinstance(file, (str, Path)):
94
+ file = Path(file)
95
+ if not file.is_file():
96
+ raise ValueError(f"{file} is not a valid file.")
97
+ return file.name
65
98
 
66
- return file.name
99
+ return _generate_random_name()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemini-webapi
3
- Version: 1.17.3
3
+ Version: 1.18.0
4
4
  Summary: ✨ An elegant async Python wrapper for Google Gemini web app
5
5
  Author: UZQueen
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -676,10 +676,10 @@ Classifier: Programming Language :: Python :: 3.12
676
676
  Requires-Python: >=3.10
677
677
  Description-Content-Type: text/markdown
678
678
  License-File: LICENSE
679
- Requires-Dist: httpx~=0.28.1
679
+ Requires-Dist: httpx[http2]~=0.28.1
680
680
  Requires-Dist: loguru~=0.7.3
681
- Requires-Dist: orjson~=3.11.1
682
- Requires-Dist: pydantic~=2.12.2
681
+ Requires-Dist: orjson~=3.11.7
682
+ Requires-Dist: pydantic~=2.12.5
683
683
  Dynamic: license-file
684
684
 
685
685
  <p align="center">
@@ -717,6 +717,7 @@ A reverse-engineered asynchronous python wrapper for [Google Gemini](https://gem
717
717
  - **System Prompt** - Supports customizing model's system prompt with [Gemini Gems](https://gemini.google.com/gems/view).
718
718
  - **Extension Support** - Supports generating contents with [Gemini extensions](https://gemini.google.com/extensions) on, like YouTube and Gmail.
719
719
  - **Classified Outputs** - Categorizes texts, thoughts, web images and AI generated images in the response.
720
+ - **Streaming Mode** - Supports stream generation, yielding partial outputs as they are generated.
720
721
  - **Official Flavor** - Provides a simple and elegant interface inspired by [Google Generative AI](https://ai.google.dev/tutorials/python_quickstart)'s official API.
721
722
  - **Asynchronous** - Utilizes `asyncio` to run generating tasks and return outputs efficiently.
722
723
 
@@ -732,6 +733,7 @@ A reverse-engineered asynchronous python wrapper for [Google Gemini](https://gem
732
733
  - [Generate contents with files](#generate-contents-with-files)
733
734
  - [Conversations across multiple turns](#conversations-across-multiple-turns)
734
735
  - [Continue previous conversations](#continue-previous-conversations)
736
+ - [Streaming mode](#streaming-mode)
735
737
  - [Select language model](#select-language-model)
736
738
  - [Apply system prompt with Gemini Gems](#apply-system-prompt-with-gemini-gems)
737
739
  - [Manage Custom Gems](#manage-custom-gems)
@@ -901,6 +903,28 @@ async def main():
901
903
  asyncio.run(main())
902
904
  ```
903
905
 
906
+ ### Streaming mode
907
+
908
+ For longer responses, you can use streaming mode to receive partial outputs as they are generated. This provides a more responsive user experience, especially for real-time applications like chatbots.
909
+
910
+ The `generate_content_stream` method yields `ModelOutput` objects where the `text_delta` attribute contains only the **new characters** received since the last yield, making it easy to display incremental updates.
911
+
912
+ ```python
913
+ async def main():
914
+ async for chunk in client.generate_content_stream(
915
+ "What's the difference between 'await' and 'async for'?"
916
+ ):
917
+ print(chunk.text_delta, end="", flush=True)
918
+
919
+ print()
920
+
921
+ asyncio.run(main())
922
+ ```
923
+
924
+ > [!TIP]
925
+ >
926
+ > You can also use streaming mode in multi-turn conversations with `ChatSession.send_message_stream`.
927
+
904
928
  ### Select language model
905
929
 
906
930
  You can specify which language model to use by passing `model` argument to `GeminiClient.generate_content` or `GeminiClient.start_chat`. The default value is `unspecified`.
@@ -909,8 +933,8 @@ Currently available models (as of November 20, 2025):
909
933
 
910
934
  - `unspecified` - Default model
911
935
  - `gemini-3.0-pro` - Gemini 3.0 Pro
912
- - `gemini-2.5-pro` - Gemini 2.5 Pro
913
- - `gemini-2.5-flash` - Gemini 2.5 Flash
936
+ - `gemini-3.0-flash` - Gemini 3.0 Flash
937
+ - `gemini-3.0-flash-thinking` - Gemini 3.0 Flash Thinking
914
938
 
915
939
  ```python
916
940
  from gemini_webapi.constants import Model
@@ -918,9 +942,9 @@ from gemini_webapi.constants import Model
918
942
  async def main():
919
943
  response1 = await client.generate_content(
920
944
  "What's you language model version? Reply version number only.",
921
- model=Model.G_2_5_FLASH,
945
+ model=Model.G_3_0_FLASH,
922
946
  )
923
- print(f"Model version ({Model.G_2_5_FLASH.model_name}): {response1.text}")
947
+ print(f"Model version ({Model.G_3_0_FLASH.model_name}): {response1.text}")
924
948
 
925
949
  chat = client.start_chat(model="gemini-2.5-pro")
926
950
  response2 = await chat.send_message("What's you language model version? Reply version number only.")
@@ -968,7 +992,7 @@ async def main():
968
992
 
969
993
  response1 = await client.generate_content(
970
994
  "what's your system prompt?",
971
- model=Model.G_2_5_FLASH,
995
+ model=Model.G_3_0_FLASH,
972
996
  gem=coding_partner,
973
997
  )
974
998
  print(response1.text)
@@ -0,0 +1,25 @@
1
+ gemini_webapi/__init__.py,sha256=7ELCiUoI10ea3daeJxnv0UwqLVKpM7rxsgOZsPMstO8,150
2
+ gemini_webapi/client.py,sha256=qZtedP9sNU48LEPv4fAPPQTbkwgMscsViu9uAwV-EN0,41669
3
+ gemini_webapi/constants.py,sha256=5VfdSCoWqq5dMjzrFXdBoF-ishctux6jdhyWJtqNadg,3519
4
+ gemini_webapi/exceptions.py,sha256=qkXrIpr0L7LtGbq3VcTO8D1xZ50pJtt0dDRp5I3uDSg,1038
5
+ gemini_webapi/components/__init__.py,sha256=wolxuAJJ32-jmHOKgpsesexP7hXea1JMo5vI52wysTI,48
6
+ gemini_webapi/components/gem_mixin.py,sha256=QyXNjUspcB4Kr4FAFDIwVagFG9JKJ8V1ZQ6n5J0-vpI,9477
7
+ gemini_webapi/types/__init__.py,sha256=1DU4JEw2KHQJbtOgOuvoEXtzQCMwAkyo0koSFVdT9JY,192
8
+ gemini_webapi/types/candidate.py,sha256=5Gy2UiOgwlsrFZ7wQsuHyS2jmKsgQLGkSHbqbPTEnqg,1549
9
+ gemini_webapi/types/gem.py,sha256=3Ppjq9V22Zp4Lb9a9ZnDviDKQpfSQf8UZxqOEjeEWd4,4070
10
+ gemini_webapi/types/grpc.py,sha256=S64h1oeC7ZJC50kmS_C2CQ7WVTanhJ4kqTFx5ZYayXI,917
11
+ gemini_webapi/types/image.py,sha256=DYHSSwKxd8-ipaeFeyHuHt95eh1D681irCq0oIqspYA,6240
12
+ gemini_webapi/types/modeloutput.py,sha256=L-qI1a9sPTFFNbxBgDm6FKTKjMJQxiRxNLthr7owUy8,1422
13
+ gemini_webapi/utils/__init__.py,sha256=w_OwRm9gD8j2DP7ReomhGWx3sbnPGyRC-8DXt5DZXhc,376
14
+ gemini_webapi/utils/decorators.py,sha256=iTTVIgG1fxOVMluy9fKeVrTmj4_K1h1q4lVLCjFaLhc,3462
15
+ gemini_webapi/utils/get_access_token.py,sha256=6EcLhcpS8_-hxodBvQwu76PECVBnQQtzt9AYqSi0ps8,8162
16
+ gemini_webapi/utils/load_browser_cookies.py,sha256=OHCfe27DpV_rloIDgW9Xpeb0mkfzbYONNiholw0ElXU,1791
17
+ gemini_webapi/utils/logger.py,sha256=0VcxhVLhHBRDQutNCpapP1y_MhPoQ2ud1uIFLqxC3Z8,958
18
+ gemini_webapi/utils/parsing.py,sha256=AHENFsQxLJFswUw7eOsKHnkytvUk0X7S850GNVSsOos,7279
19
+ gemini_webapi/utils/rotate_1psidts.py,sha256=7W_D5NGJ9_dpF0EgVaU_ykL1ebvQK7P28bQN2HVlX5M,2359
20
+ gemini_webapi/utils/upload_file.py,sha256=TiLnXdNvY8NRFZih-wp_BpDA6LxAVQDQakzmq2Ur9Ak,2781
21
+ gemini_webapi-1.18.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
22
+ gemini_webapi-1.18.0.dist-info/METADATA,sha256=JIP7x5zMUIJn-c7YsEraDhH3G2JNZN0jE49HzB0gCD0,62719
23
+ gemini_webapi-1.18.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
24
+ gemini_webapi-1.18.0.dist-info/top_level.txt,sha256=dtWtug_ZrmnUqCYuu8NmGzTgWglHeNzhHU_hXmqZGWE,14
25
+ gemini_webapi-1.18.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,25 +0,0 @@
1
- gemini_webapi/__init__.py,sha256=7ELCiUoI10ea3daeJxnv0UwqLVKpM7rxsgOZsPMstO8,150
2
- gemini_webapi/client.py,sha256=OJSScOnl6rUbDOJ7hz-jn_1u13_HdjczPJsstOgNTGw,29254
3
- gemini_webapi/constants.py,sha256=-UHHwSoY4pzEfRTGpfb8UMgCNVbgSc3RLSOZpVOyNpM,3409
4
- gemini_webapi/exceptions.py,sha256=qkXrIpr0L7LtGbq3VcTO8D1xZ50pJtt0dDRp5I3uDSg,1038
5
- gemini_webapi/components/__init__.py,sha256=wolxuAJJ32-jmHOKgpsesexP7hXea1JMo5vI52wysTI,48
6
- gemini_webapi/components/gem_mixin.py,sha256=WPJkYDS4yQpLMBNQ94LQo5w59RgkllWaSiHsFG1k5GU,8795
7
- gemini_webapi/types/__init__.py,sha256=1DU4JEw2KHQJbtOgOuvoEXtzQCMwAkyo0koSFVdT9JY,192
8
- gemini_webapi/types/candidate.py,sha256=67BhY75toE5mVuB21cmHcTFtw332V_KmCjr3-9VTbJo,1477
9
- gemini_webapi/types/gem.py,sha256=3Ppjq9V22Zp4Lb9a9ZnDviDKQpfSQf8UZxqOEjeEWd4,4070
10
- gemini_webapi/types/grpc.py,sha256=S64h1oeC7ZJC50kmS_C2CQ7WVTanhJ4kqTFx5ZYayXI,917
11
- gemini_webapi/types/image.py,sha256=9miYAu3htBjNScCWfBJ49b_RtWDW9XFWO1NRJDvLgM8,6173
12
- gemini_webapi/types/modeloutput.py,sha256=h07kQOkL5r-oPLvZ59uVtO1eP4FGy5ZpzuYQzAeQdr8,1196
13
- gemini_webapi/utils/__init__.py,sha256=k8hV2zn6tD_BEpd1Xya6ED0deijsmzb1e9XxdFhJzIE,418
14
- gemini_webapi/utils/decorators.py,sha256=uzIXoZOC0_Om19bbVXf_nw2w2NhI2qJL1o45FU6o6fI,1780
15
- gemini_webapi/utils/get_access_token.py,sha256=twZtTtvOnGxHhikCOj1HGErj2r5dT_BNcvqv5A0dtXg,7194
16
- gemini_webapi/utils/load_browser_cookies.py,sha256=OHCfe27DpV_rloIDgW9Xpeb0mkfzbYONNiholw0ElXU,1791
17
- gemini_webapi/utils/logger.py,sha256=0VcxhVLhHBRDQutNCpapP1y_MhPoQ2ud1uIFLqxC3Z8,958
18
- gemini_webapi/utils/parsing.py,sha256=wDQsK1RcfrPr7c6w1JpxoPjLtkFIkcPmTX8jjfleJf0,2080
19
- gemini_webapi/utils/rotate_1psidts.py,sha256=SIqkqjZrEAyXB38qTEY9apxHVwczpFut4I0A77eWIFk,1790
20
- gemini_webapi/utils/upload_file.py,sha256=z5p03l6oQP74wzF0MF-Kbnav5k1nb7uYyzx-f8pxSVI,1456
21
- gemini_webapi-1.17.3.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
22
- gemini_webapi-1.17.3.dist-info/METADATA,sha256=Ld5hDcwuox-uqslhP83-gzyYVEZa7wgOs5hX2NcG7Y4,61756
23
- gemini_webapi-1.17.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
- gemini_webapi-1.17.3.dist-info/top_level.txt,sha256=dtWtug_ZrmnUqCYuu8NmGzTgWglHeNzhHU_hXmqZGWE,14
25
- gemini_webapi-1.17.3.dist-info/RECORD,,