gemini-webapi 1.14.4__py3-none-any.whl → 1.15.1__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.
gemini_webapi/client.py CHANGED
@@ -292,7 +292,9 @@ class GeminiClient(GemMixin):
292
292
  model = Model.from_name(model)
293
293
 
294
294
  if isinstance(gem, Gem):
295
- gem = gem.id
295
+ gem_id = gem.id
296
+ else:
297
+ gem_id = gem
296
298
 
297
299
  if self.auto_close:
298
300
  await self.reset_close_task()
@@ -325,7 +327,7 @@ class GeminiClient(GemMixin):
325
327
  None,
326
328
  chat and chat.metadata,
327
329
  ]
328
- + (gem and [None] * 16 + [gem] or [])
330
+ + (gem_id and [None] * 16 + [gem_id] or [])
329
331
  ).decode(),
330
332
  ]
331
333
  ).decode(),
@@ -551,12 +553,10 @@ class GeminiClient(GemMixin):
551
553
  "consider setting a higher `timeout` value when initializing GeminiClient."
552
554
  )
553
555
 
556
+ # ? Seems like batch execution will immediately invalidate the current access token,
557
+ # ? causing the next request to fail with 401 Unauthorized.
554
558
  if response.status_code != 200:
555
- logger.debug(
556
- f"Batch execution failed with status code {response.status_code}. "
557
- f"RPC: {', '.join({payload.rpcid.name for payload in payloads})}; "
558
- f"Invalid response: {response.text}"
559
- )
559
+ await self.close()
560
560
  raise APIError(
561
561
  f"Batch execution failed with status code {response.status_code}"
562
562
  )
@@ -131,10 +131,145 @@ class GemMixin:
131
131
 
132
132
  return self._gems
133
133
 
134
+ @running(retry=2)
135
+ async def create_gem(self, name: str, prompt: str, description: str = "") -> Gem:
136
+ """
137
+ Create a new custom gem.
138
+
139
+ Parameters
140
+ ----------
141
+ name: `str`
142
+ Name of the custom gem.
143
+ prompt: `str`
144
+ System instructions for the custom gem.
145
+ description: `str`, optional
146
+ Description of the custom gem (has no effect on the model's behavior).
147
+
148
+ Returns
149
+ -------
150
+ :class:`Gem`
151
+ The created gem.
152
+ """
153
+
154
+ response = await self._batch_execute(
155
+ [
156
+ RPCData(
157
+ rpcid=GRPC.CREATE_GEM,
158
+ payload=json.dumps(
159
+ [
160
+ [
161
+ name,
162
+ description,
163
+ prompt,
164
+ None,
165
+ None,
166
+ None,
167
+ None,
168
+ None,
169
+ 0,
170
+ None,
171
+ 1,
172
+ None,
173
+ None,
174
+ None,
175
+ [],
176
+ ]
177
+ ]
178
+ ).decode(),
179
+ )
180
+ ]
181
+ )
182
+
183
+ try:
184
+ response_json = json.loads(response.text.split("\n")[2])
185
+ gem_id = json.loads(response_json[0][2])[0]
186
+ except Exception:
187
+ await self.close()
188
+ logger.debug(f"Invalid response: {response.text}")
189
+ raise APIError(
190
+ "Failed to create gem. Invalid response data received. Client will try to re-initialize on next request."
191
+ )
192
+
193
+ return Gem(
194
+ id=gem_id,
195
+ name=name,
196
+ description=description,
197
+ prompt=prompt,
198
+ predefined=False,
199
+ )
200
+
201
+ @running(retry=2)
202
+ async def update_gem(
203
+ self, gem: Gem | str, name: str, prompt: str, description: str = ""
204
+ ) -> Gem:
205
+ """
206
+ Update an existing custom gem.
207
+
208
+ Parameters
209
+ ----------
210
+ gem: `Gem | str`
211
+ Gem to update, can be either a `gemini_webapi.types.Gem` object or a gem id string.
212
+ name: `str`
213
+ New name for the custom gem.
214
+ prompt: `str`
215
+ New system instructions for the custom gem.
216
+ description: `str`, optional
217
+ New description of the custom gem (has no effect on the model's behavior).
218
+
219
+ Returns
220
+ -------
221
+ :class:`Gem`
222
+ The updated gem.
223
+ """
224
+
225
+ if isinstance(gem, Gem):
226
+ gem_id = gem.id
227
+ else:
228
+ gem_id = gem
229
+
230
+ await self._batch_execute(
231
+ [
232
+ RPCData(
233
+ rpcid=GRPC.UPDATE_GEM,
234
+ payload=json.dumps(
235
+ [
236
+ gem_id,
237
+ [
238
+ name,
239
+ description,
240
+ prompt,
241
+ None,
242
+ None,
243
+ None,
244
+ None,
245
+ None,
246
+ 0,
247
+ None,
248
+ 1,
249
+ None,
250
+ None,
251
+ None,
252
+ [],
253
+ 0,
254
+ ],
255
+ ]
256
+ ).decode(),
257
+ )
258
+ ]
259
+ )
260
+
261
+ return Gem(
262
+ id=gem_id,
263
+ name=name,
264
+ description=description,
265
+ prompt=prompt,
266
+ predefined=False,
267
+ )
268
+
134
269
  @running(retry=2)
135
270
  async def delete_gem(self, gem: Gem | str, **kwargs) -> None:
136
271
  """
137
- Delete a custom gem from gemini.google.com.
272
+ Delete a custom gem.
138
273
 
139
274
  Parameters
140
275
  ----------
@@ -143,9 +278,11 @@ class GemMixin:
143
278
  """
144
279
 
145
280
  if isinstance(gem, Gem):
146
- gem = gem.id
281
+ gem_id = gem.id
282
+ else:
283
+ gem_id = gem
147
284
 
148
285
  await self._batch_execute(
149
- [RPCData(rpcid=GRPC.DELETE_GEM, payload=[gem])],
286
+ [RPCData(rpcid=GRPC.DELETE_GEM, payload=json.dumps([gem_id]).decode())],
150
287
  **kwargs,
151
288
  )
@@ -20,6 +20,8 @@ class GRPC(StrEnum):
20
20
 
21
21
  # Gem methods
22
22
  LIST_GEMS = "CNgdBe"
23
+ CREATE_GEM = "oMH3Zd"
24
+ UPDATE_GEM = "kHv0Vd"
23
25
  DELETE_GEM = "UXcSJb"
24
26
 
25
27
 
@@ -42,12 +44,12 @@ class Model(Enum):
42
44
  UNSPECIFIED = ("unspecified", {}, False)
43
45
  G_2_5_FLASH = (
44
46
  "gemini-2.5-flash",
45
- {"x-goog-ext-525001261-jspb": '[1,null,null,null,"71c2d248d3b102ff"]'},
47
+ {"x-goog-ext-525001261-jspb": '[1,null,null,null,"71c2d248d3b102ff",null,null,0,[4]]'},
46
48
  False,
47
49
  )
48
50
  G_2_5_PRO = (
49
51
  "gemini-2.5-pro",
50
- {"x-goog-ext-525001261-jspb": '[1,null,null,null,"2525e3954d185b3c"]'},
52
+ {"x-goog-ext-525001261-jspb": '[1,null,null,null,"4af6c7f5da75d65d",null,null,0,[4]]'},
51
53
  False,
52
54
  )
53
55
  G_2_0_FLASH = (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemini-webapi
3
- Version: 1.14.4
3
+ Version: 1.15.1
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
@@ -734,6 +734,10 @@ A reverse-engineered asynchronous python wrapper for [Google Gemini](https://gem
734
734
  - [Continue previous conversations](#continue-previous-conversations)
735
735
  - [Select language model](#select-language-model)
736
736
  - [Apply system prompt with Gemini Gems](#apply-system-prompt-with-gemini-gems)
737
+ - [Manage Custom Gems](#manage-custom-gems)
738
+ - [Create a custom gem](#create-a-custom-gem)
739
+ - [Update an existing gem](#update-an-existing-gem)
740
+ - [Delete a custom gem](#delete-a-custom-gem)
737
741
  - [Retrieve model's thought process](#retrieve-models-thought-process)
738
742
  - [Retrieve images in response](#retrieve-images-in-response)
739
743
  - [Generate images with Imagen4](#generate-images-with-imagen4)
@@ -963,6 +967,75 @@ async def main():
963
967
  print(response2)
964
968
  ```
965
969
 
970
+ ### Manage Custom Gems
971
+
972
+ You can create, update, and delete your custom gems programmatically with the API. Note that predefined system gems cannot be modified or deleted.
973
+
974
+ #### Create a custom gem
975
+
976
+ Create a new custom gem with a name, system prompt (instructions), and optional description:
977
+
978
+ ```python
979
+ async def main():
980
+ # Create a new custom gem
981
+ new_gem = await client.create_gem(
982
+ name="Python Tutor",
983
+ prompt="You are a helpful Python programming tutor.",
984
+ description="A specialized gem for Python programming"
985
+ )
986
+
987
+ print(f"Custom gem created: {new_gem}")
988
+
989
+ # Use the newly created gem in a conversation
990
+ response = await client.generate_content(
991
+ "Explain how list comprehensions work in Python",
992
+ gem=new_gem
993
+ )
994
+ print(response.text)
995
+
996
+ asyncio.run(main())
997
+ ```
998
+
999
+ #### Update an existing gem
1000
+
1001
+ > [!NOTE]
1002
+ >
1003
+ > When updating a gem, you must provide all parameters (name, prompt, description) even if you only want to change one of them.
1004
+
1005
+ ```python
1006
+ async def main():
1007
+ # Get a custom gem (assuming you have one named "Python Tutor")
1008
+ await client.fetch_gems()
1009
+ python_tutor = client.gems.get(name="Python Tutor")
1010
+
1011
+ # Update the gem with new instructions
1012
+ updated_gem = await client.update_gem(
1013
+ gem=python_tutor, # Can also pass gem ID string
1014
+ name="Advanced Python Tutor",
1015
+ prompt="You are an expert Python programming tutor.",
1016
+ description="An advanced Python programming assistant"
1017
+ )
1018
+
1019
+ print(f"Custom gem updated: {updated_gem}")
1020
+
1021
+ asyncio.run(main())
1022
+ ```
1023
+
1024
+ #### Delete a custom gem
1025
+
1026
+ ```python
1027
+ async def main():
1028
+ # Get the gem to delete
1029
+ await client.fetch_gems()
1030
+ gem_to_delete = client.gems.get(name="Advanced Python Tutor")
1031
+
1032
+ # Delete the gem
1033
+ await client.delete_gem(gem_to_delete) # Can also pass gem ID string
1034
+ print(f"Custom gem deleted: {gem_to_delete.name}")
1035
+
1036
+ asyncio.run(main())
1037
+ ```
1038
+
966
1039
  ### Retrieve model's thought process
967
1040
 
968
1041
  When using models with thinking capabilities, the model's thought process will be populated in `ModelOutput.thoughts`.
@@ -1,9 +1,9 @@
1
1
  gemini_webapi/__init__.py,sha256=7ELCiUoI10ea3daeJxnv0UwqLVKpM7rxsgOZsPMstO8,150
2
- gemini_webapi/client.py,sha256=nnInjBRsBLLLcc07f2oFv4HjwMXXCBx5Y_RMwo8NYtM,26891
3
- gemini_webapi/constants.py,sha256=Ulj38JVv8lPW7GZRTb25sLM2utuY-q7aDTrgm-bXbrU,2545
2
+ gemini_webapi/client.py,sha256=G0nmRCeEmT0BpqgYY4zxb_ozFDmb39JqtddKpIN6JlA,26870
3
+ gemini_webapi/constants.py,sha256=z8Jr6NQk79mgZOgmwx8vbgzyORHQVBjoJ4ZhUdDWWXw,2629
4
4
  gemini_webapi/exceptions.py,sha256=qkXrIpr0L7LtGbq3VcTO8D1xZ50pJtt0dDRp5I3uDSg,1038
5
5
  gemini_webapi/components/__init__.py,sha256=wolxuAJJ32-jmHOKgpsesexP7hXea1JMo5vI52wysTI,48
6
- gemini_webapi/components/gem_mixin.py,sha256=HUrTBHQC9Lzc53Le2droi_UoFxTOZMRLMbn6MRdcHfI,4727
6
+ gemini_webapi/components/gem_mixin.py,sha256=WPJkYDS4yQpLMBNQ94LQo5w59RgkllWaSiHsFG1k5GU,8795
7
7
  gemini_webapi/types/__init__.py,sha256=1DU4JEw2KHQJbtOgOuvoEXtzQCMwAkyo0koSFVdT9JY,192
8
8
  gemini_webapi/types/candidate.py,sha256=67BhY75toE5mVuB21cmHcTFtw332V_KmCjr3-9VTbJo,1477
9
9
  gemini_webapi/types/gem.py,sha256=3Ppjq9V22Zp4Lb9a9ZnDviDKQpfSQf8UZxqOEjeEWd4,4070
@@ -17,8 +17,8 @@ gemini_webapi/utils/load_browser_cookies.py,sha256=A5n_VsB7Rm8ck5lpy856UNJEhv30l
17
17
  gemini_webapi/utils/logger.py,sha256=0VcxhVLhHBRDQutNCpapP1y_MhPoQ2ud1uIFLqxC3Z8,958
18
18
  gemini_webapi/utils/rotate_1psidts.py,sha256=NyQ9OYPLBOcvpc8bodvEYDIVFrsYN0kdfc831lPEctM,1680
19
19
  gemini_webapi/utils/upload_file.py,sha256=SJOMr6kryK_ClrKmqI96fqZBNFOMPsyAvFINAGAU3rk,1468
20
- gemini_webapi-1.14.4.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
21
- gemini_webapi-1.14.4.dist-info/METADATA,sha256=-d9j4G1z2fh6ohTAOeNW0I2O2HzlG0D48TKH67KCXFE,59156
22
- gemini_webapi-1.14.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- gemini_webapi-1.14.4.dist-info/top_level.txt,sha256=dtWtug_ZrmnUqCYuu8NmGzTgWglHeNzhHU_hXmqZGWE,14
24
- gemini_webapi-1.14.4.dist-info/RECORD,,
20
+ gemini_webapi-1.15.1.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
21
+ gemini_webapi-1.15.1.dist-info/METADATA,sha256=baIL2RVMPLqKe1l_lMxi5QDpTEJbNdehoF4uk2-467Q,61279
22
+ gemini_webapi-1.15.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ gemini_webapi-1.15.1.dist-info/top_level.txt,sha256=dtWtug_ZrmnUqCYuu8NmGzTgWglHeNzhHU_hXmqZGWE,14
24
+ gemini_webapi-1.15.1.dist-info/RECORD,,