g4f 7.0.0__py3-none-any.whl → 7.0.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.
g4f/Provider/Yupp.py CHANGED
@@ -11,16 +11,24 @@ from typing import Optional, Dict, Any, List
11
11
  try:
12
12
  import cloudscraper
13
13
  from cloudscraper import CloudScraper
14
+
14
15
  has_cloudscraper = True
15
16
  except ImportError:
16
17
  from typing import Type as CloudScraper
18
+
17
19
  has_cloudscraper = False
18
20
 
19
21
  from .helper import get_last_user_message
20
22
  from .yupp.models import YuppModelManager
23
+ from .yupp.token_extractor import get_token_extractor
21
24
  from ..cookies import get_cookies
22
25
  from ..debug import log
23
- from ..errors import RateLimitError, ProviderException, MissingAuthError, MissingRequirementsError
26
+ from ..errors import (
27
+ RateLimitError,
28
+ ProviderException,
29
+ MissingAuthError,
30
+ MissingRequirementsError,
31
+ )
24
32
  from ..image import is_accepted_format, to_bytes
25
33
  from ..providers.base_provider import AsyncGeneratorProvider, ProviderModelMixin
26
34
  from ..providers.response import (
@@ -49,25 +57,27 @@ MAX_CACHE_SIZE = 1000
49
57
  def create_scraper():
50
58
  scraper = cloudscraper.create_scraper(
51
59
  browser={
52
- 'browser': 'chrome',
53
- 'platform': 'windows',
54
- 'desktop': True,
55
- 'mobile': False
60
+ "browser": "chrome",
61
+ "platform": "windows",
62
+ "desktop": True,
63
+ "mobile": False,
56
64
  },
57
65
  delay=10,
58
- interpreter='nodejs'
66
+ interpreter="nodejs",
67
+ )
68
+ scraper.headers.update(
69
+ {
70
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0",
71
+ "Accept": "text/x-component, */*",
72
+ "Accept-Language": "en-US,en;q=0.9",
73
+ "Sec-Fetch-Dest": "empty",
74
+ "Sec-Fetch-Mode": "cors",
75
+ "Sec-Fetch-Site": "same-origin",
76
+ "Sec-Ch-Ua": '"Microsoft Edge";v="137", "Chromium";v="137", "Not/A)Brand";v="24"',
77
+ "Sec-Ch-Ua-Mobile": "?0",
78
+ "Sec-Ch-Ua-Platform": '"Windows"',
79
+ }
59
80
  )
60
- scraper.headers.update({
61
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0",
62
- "Accept": "text/x-component, */*",
63
- "Accept-Language": "en-US,en;q=0.9",
64
- "Sec-Fetch-Dest": "empty",
65
- "Sec-Fetch-Mode": "cors",
66
- "Sec-Fetch-Site": "same-origin",
67
- "Sec-Ch-Ua": '"Microsoft Edge";v="137", "Chromium";v="137", "Not/A)Brand";v="24"',
68
- "Sec-Ch-Ua-Mobile": "?0",
69
- "Sec-Ch-Ua-Platform": '"Windows"'
70
- })
71
81
  return scraper
72
82
 
73
83
 
@@ -77,14 +87,9 @@ def load_yupp_accounts(tokens_str: str):
77
87
  return
78
88
  if not tokens_str:
79
89
  return
80
- tokens = [token.strip() for token in tokens_str.split(',') if token.strip()]
90
+ tokens = [token.strip() for token in tokens_str.split(",") if token.strip()]
81
91
  YUPP_ACCOUNTS = [
82
- {
83
- "token": token,
84
- "is_valid": True,
85
- "error_count": 0,
86
- "last_used": 0.0
87
- }
92
+ {"token": token, "is_valid": True, "error_count": 0, "last_used": 0.0}
88
93
  for token in tokens
89
94
  ]
90
95
  _accounts_loaded = True
@@ -122,12 +127,14 @@ async def get_best_yupp_account() -> Optional[Dict[str, Any]]:
122
127
  return account
123
128
 
124
129
 
125
- def sync_claim_yupp_reward(scraper: CloudScraper, account: Dict[str, Any], eval_id: str):
130
+ def sync_claim_yupp_reward(
131
+ scraper: CloudScraper, account: Dict[str, Any], eval_id: str
132
+ ):
126
133
  try:
127
134
  log_debug(f"Claiming reward {eval_id}...")
128
135
  url = "https://yupp.ai/api/trpc/reward.claim?batch=1"
129
136
  payload = {"0": {"json": {"evalId": eval_id}}}
130
- scraper.cookies.set("__Secure-yupp.session-token", account['token'])
137
+ scraper.cookies.set("__Secure-yupp.session-token", account["token"])
131
138
  response = scraper.post(url, json=payload)
132
139
  response.raise_for_status()
133
140
  data = response.json()
@@ -139,9 +146,13 @@ def sync_claim_yupp_reward(scraper: CloudScraper, account: Dict[str, Any], eval_
139
146
  return None
140
147
 
141
148
 
142
- async def claim_yupp_reward(scraper: CloudScraper, account: Dict[str, Any], eval_id: str):
149
+ async def claim_yupp_reward(
150
+ scraper: CloudScraper, account: Dict[str, Any], eval_id: str
151
+ ):
143
152
  loop = asyncio.get_event_loop()
144
- return await loop.run_in_executor(_executor, sync_claim_yupp_reward, scraper, account, eval_id)
153
+ return await loop.run_in_executor(
154
+ _executor, sync_claim_yupp_reward, scraper, account, eval_id
155
+ )
145
156
 
146
157
 
147
158
  def sync_record_model_feedback(
@@ -149,7 +160,7 @@ def sync_record_model_feedback(
149
160
  account: Dict[str, Any],
150
161
  turn_id: str,
151
162
  left_message_id: str,
152
- right_message_id: str
163
+ right_message_id: str,
153
164
  ) -> Optional[str]:
154
165
  try:
155
166
  log_debug(f"Recording model feedback for turn {turn_id}...")
@@ -163,20 +174,16 @@ def sync_record_model_feedback(
163
174
  {
164
175
  "messageId": right_message_id,
165
176
  "rating": "GOOD",
166
- "reasons": ["Fast"]
177
+ "reasons": ["Fast"],
167
178
  },
168
- {
169
- "messageId": left_message_id,
170
- "rating": "BAD",
171
- "reasons": []
172
- }
179
+ {"messageId": left_message_id, "rating": "BAD", "reasons": []},
173
180
  ],
174
181
  "comment": "",
175
- "requireReveal": False
182
+ "requireReveal": False,
176
183
  }
177
184
  }
178
185
  }
179
- scraper.cookies.set("__Secure-yupp.session-token", account['token'])
186
+ scraper.cookies.set("__Secure-yupp.session-token", account["token"])
180
187
  response = scraper.post(url, json=payload)
181
188
  response.raise_for_status()
182
189
  data = response.json()
@@ -200,25 +207,34 @@ async def record_model_feedback(
200
207
  account: Dict[str, Any],
201
208
  turn_id: str,
202
209
  left_message_id: str,
203
- right_message_id: str
210
+ right_message_id: str,
204
211
  ) -> Optional[str]:
205
212
  loop = asyncio.get_event_loop()
206
213
  return await loop.run_in_executor(
207
- _executor, sync_record_model_feedback, scraper, account, turn_id, left_message_id, right_message_id
214
+ _executor,
215
+ sync_record_model_feedback,
216
+ scraper,
217
+ account,
218
+ turn_id,
219
+ left_message_id,
220
+ right_message_id,
208
221
  )
209
222
 
210
223
 
211
- def sync_delete_chat(scraper: CloudScraper, account: Dict[str, Any], chat_id: str) -> bool:
224
+ def sync_delete_chat(
225
+ scraper: CloudScraper, account: Dict[str, Any], chat_id: str
226
+ ) -> bool:
212
227
  try:
213
228
  log_debug(f"Deleting chat {chat_id}...")
214
229
  url = "https://yupp.ai/api/trpc/chat.deleteChat?batch=1"
215
230
  payload = {"0": {"json": {"chatId": chat_id}}}
216
- scraper.cookies.set("__Secure-yupp.session-token", account['token'])
231
+ scraper.cookies.set("__Secure-yupp.session-token", account["token"])
217
232
  response = scraper.post(url, json=payload)
218
233
  response.raise_for_status()
219
234
  data = response.json()
220
235
  if (
221
- isinstance(data, list) and len(data) > 0
236
+ isinstance(data, list)
237
+ and len(data) > 0
222
238
  and data[0].get("result", {}).get("data", {}).get("json") is None
223
239
  ):
224
240
  log_debug(f"Chat {chat_id} deleted successfully")
@@ -230,29 +246,29 @@ def sync_delete_chat(scraper: CloudScraper, account: Dict[str, Any], chat_id: st
230
246
  return False
231
247
 
232
248
 
233
- async def delete_chat(scraper: CloudScraper, account: Dict[str, Any], chat_id: str) -> bool:
249
+ async def delete_chat(
250
+ scraper: CloudScraper, account: Dict[str, Any], chat_id: str
251
+ ) -> bool:
234
252
  loop = asyncio.get_event_loop()
235
- return await loop.run_in_executor(_executor, sync_delete_chat, scraper, account, chat_id)
253
+ return await loop.run_in_executor(
254
+ _executor, sync_delete_chat, scraper, account, chat_id
255
+ )
236
256
 
237
257
 
238
- def sync_make_chat_private(scraper: CloudScraper, account: Dict[str, Any], chat_id: str) -> bool:
258
+ def sync_make_chat_private(
259
+ scraper: CloudScraper, account: Dict[str, Any], chat_id: str
260
+ ) -> bool:
239
261
  try:
240
262
  log_debug(f"Setting chat {chat_id} to PRIVATE...")
241
263
  url = "https://yupp.ai/api/trpc/chat.updateSharingSettings?batch=1"
242
- payload = {
243
- "0": {
244
- "json": {
245
- "chatId": chat_id,
246
- "status": "PRIVATE"
247
- }
248
- }
249
- }
250
- scraper.cookies.set("__Secure-yupp.session-token", account['token'])
264
+ payload = {"0": {"json": {"chatId": chat_id, "status": "PRIVATE"}}}
265
+ scraper.cookies.set("__Secure-yupp.session-token", account["token"])
251
266
  response = scraper.post(url, json=payload)
252
267
  response.raise_for_status()
253
268
  data = response.json()
254
269
  if (
255
- isinstance(data, list) and len(data) > 0
270
+ isinstance(data, list)
271
+ and len(data) > 0
256
272
  and "json" in data[0].get("result", {}).get("data", {})
257
273
  ):
258
274
  log_debug(f"Chat {chat_id} is now PRIVATE")
@@ -264,9 +280,13 @@ def sync_make_chat_private(scraper: CloudScraper, account: Dict[str, Any], chat_
264
280
  return False
265
281
 
266
282
 
267
- async def make_chat_private(scraper: CloudScraper, account: Dict[str, Any], chat_id: str) -> bool:
283
+ async def make_chat_private(
284
+ scraper: CloudScraper, account: Dict[str, Any], chat_id: str
285
+ ) -> bool:
268
286
  loop = asyncio.get_event_loop()
269
- return await loop.run_in_executor(_executor, sync_make_chat_private, scraper, account, chat_id)
287
+ return await loop.run_in_executor(
288
+ _executor, sync_make_chat_private, scraper, account, chat_id
289
+ )
270
290
 
271
291
 
272
292
  def log_debug(message: str):
@@ -285,13 +305,17 @@ def format_messages_for_yupp(messages: Messages) -> str:
285
305
 
286
306
  formatted = []
287
307
 
288
- system_messages = [msg for msg in messages if msg.get("role") in ["developer", "system"]]
308
+ system_messages = [
309
+ msg for msg in messages if msg.get("role") in ["developer", "system"]
310
+ ]
289
311
  if system_messages:
290
312
  for sys_msg in system_messages:
291
313
  content = sys_msg.get("content", "")
292
314
  formatted.append(content)
293
315
 
294
- user_assistant_msgs = [msg for msg in messages if msg.get("role") in ["user", "assistant"]]
316
+ user_assistant_msgs = [
317
+ msg for msg in messages if msg.get("role") in ["user", "assistant"]
318
+ ]
295
319
  for msg in user_assistant_msgs:
296
320
  role = "Human" if msg.get("role") == "user" else "Assistant"
297
321
  content = msg.get("content", "")
@@ -312,7 +336,9 @@ def format_messages_for_yupp(messages: Messages) -> str:
312
336
  def evict_cache_if_needed():
313
337
  global ImagesCache
314
338
  if len(ImagesCache) > MAX_CACHE_SIZE:
315
- keys_to_remove = list(ImagesCache.keys())[:len(ImagesCache) - MAX_CACHE_SIZE + 100]
339
+ keys_to_remove = list(ImagesCache.keys())[
340
+ : len(ImagesCache) - MAX_CACHE_SIZE + 100
341
+ ]
316
342
  for key in keys_to_remove:
317
343
  del ImagesCache[key]
318
344
 
@@ -331,23 +357,37 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
331
357
  if not api_key:
332
358
  api_key = AuthManager.load_api_key(cls)
333
359
  if not api_key:
334
- api_key = get_cookies("yupp.ai", False).get("__Secure-yupp.session-token")
360
+ api_key = get_cookies("yupp.ai", False).get(
361
+ "__Secure-yupp.session-token"
362
+ )
335
363
  if not api_key:
336
- raise MissingAuthError("No Yupp accounts configured. Set YUPP_API_KEY environment variable.")
364
+ raise MissingAuthError(
365
+ "No Yupp accounts configured. Set YUPP_API_KEY environment variable."
366
+ )
337
367
  manager = YuppModelManager(api_key=api_key, session=create_scraper())
338
368
  models = manager.client.fetch_models()
339
369
  if models:
340
- cls.models_tags = {model.get("name"): manager.processor.generate_tags(model) for model in models}
370
+ cls.models_tags = {
371
+ model.get("name"): manager.processor.generate_tags(model)
372
+ for model in models
373
+ }
341
374
  cls.models = [model.get("name") for model in models]
342
- cls.image_models = [model.get("name") for model in models if model.get("isImageGeneration")]
375
+ cls.image_models = [
376
+ model.get("name")
377
+ for model in models
378
+ if model.get("isImageGeneration")
379
+ ]
343
380
  cls.vision_models = [
344
- model.get("name") for model in models
381
+ model.get("name")
382
+ for model in models
345
383
  if "image/*" in model.get("supportedAttachmentMimeTypes", [])
346
384
  ]
347
385
  return cls.models
348
386
 
349
387
  @classmethod
350
- def sync_prepare_files(cls, media, scraper: CloudScraper, account: Dict[str, Any]) -> list:
388
+ def sync_prepare_files(
389
+ cls, media, scraper: CloudScraper, account: Dict[str, Any]
390
+ ) -> list:
351
391
  files = []
352
392
  if not media:
353
393
  return files
@@ -362,13 +402,19 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
362
402
  files.append(cached_file)
363
403
  continue
364
404
 
365
- scraper.cookies.set("__Secure-yupp.session-token", account['token'])
405
+ scraper.cookies.set("__Secure-yupp.session-token", account["token"])
366
406
  presigned_resp = scraper.post(
367
407
  "https://yupp.ai/api/trpc/chat.createPresignedURLForUpload?batch=1",
368
408
  json={
369
- "0": {"json": {"fileName": name, "fileSize": len(data), "contentType": is_accepted_format(data)}}
409
+ "0": {
410
+ "json": {
411
+ "fileName": name,
412
+ "fileSize": len(data),
413
+ "contentType": is_accepted_format(data),
414
+ }
415
+ }
370
416
  },
371
- headers={"Content-Type": "application/json"}
417
+ headers={"Content-Type": "application/json"},
372
418
  )
373
419
  presigned_resp.raise_for_status()
374
420
  upload_info = presigned_resp.json()[0]["result"]["data"]["json"]
@@ -379,8 +425,8 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
379
425
  data=data,
380
426
  headers={
381
427
  "Content-Type": is_accepted_format(data),
382
- "Content-Length": str(len(data))
383
- }
428
+ "Content-Length": str(len(data)),
429
+ },
384
430
  )
385
431
 
386
432
  attachment_resp = scraper.post(
@@ -390,11 +436,11 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
390
436
  "json": {
391
437
  "fileName": name,
392
438
  "contentType": is_accepted_format(data),
393
- "fileId": upload_info["fileId"]
439
+ "fileId": upload_info["fileId"],
394
440
  }
395
441
  }
396
442
  },
397
- cookies={"__Secure-yupp.session-token": account["token"]}
443
+ cookies={"__Secure-yupp.session-token": account["token"]},
398
444
  )
399
445
  attachment_resp.raise_for_status()
400
446
  attachment = attachment_resp.json()[0]["result"]["data"]["json"]
@@ -402,7 +448,7 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
402
448
  "fileName": attachment["file_name"],
403
449
  "contentType": attachment["content_type"],
404
450
  "attachmentId": attachment["attachment_id"],
405
- "chatMessageId": ""
451
+ "chatMessageId": "",
406
452
  }
407
453
  evict_cache_if_needed()
408
454
  ImagesCache[image_hash] = file_info
@@ -410,9 +456,13 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
410
456
  return files
411
457
 
412
458
  @classmethod
413
- async def prepare_files(cls, media, scraper: CloudScraper, account: Dict[str, Any]) -> list:
459
+ async def prepare_files(
460
+ cls, media, scraper: CloudScraper, account: Dict[str, Any]
461
+ ) -> list:
414
462
  loop = asyncio.get_event_loop()
415
- return await loop.run_in_executor(_executor, cls.sync_prepare_files, media, scraper, account)
463
+ return await loop.run_in_executor(
464
+ _executor, cls.sync_prepare_files, media, scraper, account
465
+ )
416
466
 
417
467
  @classmethod
418
468
  def sync_get_signed_image(cls, scraper: CloudScraper, image_id: str) -> str:
@@ -421,10 +471,8 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
421
471
  url,
422
472
  params={
423
473
  "batch": "1",
424
- "input": json.dumps(
425
- {"0": {"json": {"imageId": image_id}}}
426
- )
427
- }
474
+ "input": json.dumps({"0": {"json": {"imageId": image_id}}}),
475
+ },
428
476
  )
429
477
  resp.raise_for_status()
430
478
  data = resp.json()[0]["result"]["data"]["json"]
@@ -433,11 +481,17 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
433
481
  @classmethod
434
482
  async def get_signed_image(cls, scraper: CloudScraper, image_id: str) -> str:
435
483
  loop = asyncio.get_event_loop()
436
- return await loop.run_in_executor(_executor, cls.sync_get_signed_image, scraper, image_id)
484
+ return await loop.run_in_executor(
485
+ _executor, cls.sync_get_signed_image, scraper, image_id
486
+ )
437
487
 
438
488
  @classmethod
439
- def sync_stream_request(cls, scraper: CloudScraper, url: str, payload: list, headers: dict, timeout: int):
440
- response = scraper.post(url, json=payload, headers=headers, stream=True, timeout=timeout)
489
+ def sync_stream_request(
490
+ cls, scraper: CloudScraper, url: str, payload: list, headers: dict, timeout: int
491
+ ):
492
+ response = scraper.post(
493
+ url, json=payload, headers=headers, stream=True, timeout=timeout
494
+ )
441
495
  response.raise_for_status()
442
496
  return response
443
497
 
@@ -462,7 +516,9 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
462
516
  load_yupp_accounts(api_key)
463
517
  log_debug(f"Yupp provider initialized with {len(YUPP_ACCOUNTS)} accounts")
464
518
  else:
465
- raise MissingAuthError("No Yupp accounts configured. Set YUPP_API_KEY environment variable.")
519
+ raise MissingAuthError(
520
+ "No Yupp accounts configured. Set YUPP_API_KEY environment variable."
521
+ )
466
522
 
467
523
  conversation = kwargs.get("conversation")
468
524
  url_uuid = conversation.url_uuid if conversation else None
@@ -488,17 +544,21 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
488
544
  try:
489
545
  scraper = create_scraper()
490
546
  if proxy:
491
- scraper.proxies = {
492
- "http": proxy,
493
- "https": proxy
494
- }
547
+ scraper.proxies = {"http": proxy, "https": proxy}
548
+
549
+ # Initialize token extractor for automatic token swapping
550
+ token_extractor = get_token_extractor(
551
+ jwt_token=account["token"], scraper=scraper
552
+ )
495
553
 
496
554
  turn_id = str(uuid.uuid4())
497
555
 
498
556
  media = kwargs.get("media")
499
557
  if media:
500
558
  media_ = list(merge_media(media, messages))
501
- files = await cls.prepare_files(media_, scraper=scraper, account=account)
559
+ files = await cls.prepare_files(
560
+ media_, scraper=scraper, account=account
561
+ )
502
562
  else:
503
563
  files = []
504
564
 
@@ -514,14 +574,19 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
514
574
  "$undefined",
515
575
  files,
516
576
  "$undefined",
517
- [{"modelName": model, "promptModifierId": "$undefined"}] if model else "none",
577
+ [{"modelName": model, "promptModifierId": "$undefined"}]
578
+ if model
579
+ else "none",
518
580
  mode,
519
581
  True,
520
582
  "$undefined",
521
583
  ]
522
584
  url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
523
585
  yield JsonConversation(url_uuid=url_uuid)
524
- next_action = kwargs.get("next_action", "7f7de0a21bc8dc3cee8ba8b6de632ff16f769649dd")
586
+ next_action = kwargs.get(
587
+ "next_action",
588
+ await token_extractor.get_token("new_conversation"),
589
+ )
525
590
  else:
526
591
  payload = [
527
592
  url_uuid,
@@ -529,12 +594,17 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
529
594
  prompt,
530
595
  False,
531
596
  [],
532
- [{"modelName": model, "promptModifierId": "$undefined"}] if model else [],
597
+ [{"modelName": model, "promptModifierId": "$undefined"}]
598
+ if model
599
+ else [],
533
600
  mode,
534
- files
601
+ files,
535
602
  ]
536
603
  url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
537
- next_action = kwargs.get("next_action", "7f9ec99a63cbb61f69ef18c0927689629bda07f1bf")
604
+ next_action = kwargs.get(
605
+ "next_action",
606
+ await token_extractor.get_token("existing_conversation"),
607
+ )
538
608
 
539
609
  headers = {
540
610
  "accept": "text/x-component",
@@ -544,7 +614,9 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
544
614
  }
545
615
 
546
616
  log_debug(f"Sending request to: {url}")
547
- log_debug(f"Payload structure: {type(payload)}, length: {len(str(payload))}")
617
+ log_debug(
618
+ f"Payload structure: {type(payload)}, length: {len(str(payload))}"
619
+ )
548
620
 
549
621
  _timeout = kwargs.get("timeout")
550
622
  if isinstance(_timeout, (int, float)):
@@ -560,11 +632,13 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
560
632
  url,
561
633
  payload,
562
634
  headers,
563
- timeout
635
+ timeout,
564
636
  )
565
637
 
566
638
  try:
567
- async for chunk in cls._process_stream_response(response, account, scraper, prompt, model):
639
+ async for chunk in cls._process_stream_response(
640
+ response, account, scraper, prompt, model
641
+ ):
568
642
  yield chunk
569
643
  finally:
570
644
  response.close()
@@ -573,23 +647,66 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
573
647
  return
574
648
 
575
649
  except RateLimitError:
576
- log_debug(f"Account ...{account['token'][-4:]} hit rate limit, rotating")
650
+ log_debug(
651
+ f"Account ...{account['token'][-4:]} hit rate limit, rotating"
652
+ )
577
653
  async with account_rotation_lock:
578
654
  account["error_count"] += 1
579
655
  continue
580
656
 
581
657
  except ProviderException as e:
582
658
  log_debug(f"Account ...{account['token'][-4:]} failed: {str(e)}")
659
+ error_msg = str(e).lower()
660
+
661
+ # Check if this is a token-related error
662
+ if any(
663
+ x in error_msg
664
+ for x in [
665
+ "auth",
666
+ "401",
667
+ "403",
668
+ "404",
669
+ "invalid action",
670
+ "action",
671
+ "next-action",
672
+ ]
673
+ ):
674
+ # Mark token as failed to trigger extraction
675
+ token_type = (
676
+ "new_conversation"
677
+ if is_new_conversation
678
+ else "existing_conversation"
679
+ )
680
+ await token_extractor.mark_token_failed(token_type, next_action)
681
+ log_debug(
682
+ f"Token failure detected, marked for extraction: {token_type}"
683
+ )
684
+
583
685
  async with account_rotation_lock:
584
- if "auth" in str(e).lower() or "401" in str(e) or "403" in str(e):
686
+ if "auth" in error_msg or "401" in error_msg or "403" in error_msg:
585
687
  account["is_valid"] = False
586
688
  else:
587
689
  account["error_count"] += 1
588
690
  continue
589
691
 
590
692
  except Exception as e:
591
- log_debug(f"Unexpected error with account ...{account['token'][-4:]}: {str(e)}")
693
+ log_debug(
694
+ f"Unexpected error with account ...{account['token'][-4:]}: {str(e)}"
695
+ )
592
696
  error_str = str(e).lower()
697
+
698
+ # Check for token-related errors in generic exceptions too
699
+ if any(x in error_str for x in ["404", "401", "403", "invalid action"]):
700
+ token_type = (
701
+ "new_conversation"
702
+ if is_new_conversation
703
+ else "existing_conversation"
704
+ )
705
+ await token_extractor.mark_token_failed(token_type, next_action)
706
+ log_debug(
707
+ f"Token failure detected in exception handler: {token_type}"
708
+ )
709
+
593
710
  if "500" in error_str or "internal server error" in error_str:
594
711
  async with account_rotation_lock:
595
712
  account["error_count"] += 1
@@ -607,7 +724,7 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
607
724
  account: Dict[str, Any],
608
725
  scraper: CloudScraper,
609
726
  prompt: str,
610
- model_id: str
727
+ model_id: str,
611
728
  ) -> AsyncResult:
612
729
  line_pattern = re.compile(b"^([0-9a-fA-F]+):(.*)")
613
730
  target_stream_id = None
@@ -617,13 +734,7 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
617
734
  normal_content = ""
618
735
  quick_content = ""
619
736
  variant_text = ""
620
- stream = {
621
- "target": [],
622
- "variant": [],
623
- "quick": [],
624
- "thinking": [],
625
- "extra": []
626
- }
737
+ stream = {"target": [], "variant": [], "quick": [], "thinking": [], "extra": []}
627
738
  select_stream = [None, None]
628
739
  capturing_ref_id: Optional[str] = None
629
740
  capturing_lines: List[bytes] = []
@@ -631,7 +742,11 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
631
742
  image_blocks: Dict[str, str] = {}
632
743
 
633
744
  def extract_ref_id(ref):
634
- return ref[2:] if ref and isinstance(ref, str) and ref.startswith("$@") else None
745
+ return (
746
+ ref[2:]
747
+ if ref and isinstance(ref, str) and ref.startswith("$@")
748
+ else None
749
+ )
635
750
 
636
751
  def extract_ref_name(ref: str) -> Optional[str]:
637
752
  if not isinstance(ref, str):
@@ -647,14 +762,18 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
647
762
  return False
648
763
  return True
649
764
 
650
- async def process_content_chunk(content: str, chunk_id: str, line_count: int, *, for_target: bool = False):
765
+ async def process_content_chunk(
766
+ content: str, chunk_id: str, line_count: int, *, for_target: bool = False
767
+ ):
651
768
  nonlocal normal_content
652
769
 
653
770
  if not is_valid_content(content):
654
771
  return
655
772
 
656
773
  if '<yapp class="image-gen">' in content:
657
- img_block = content.split('<yapp class="image-gen">').pop().split('</yapp>')[0]
774
+ img_block = (
775
+ content.split('<yapp class="image-gen">').pop().split("</yapp>")[0]
776
+ )
658
777
  image_id = json.loads(img_block).get("image_id")
659
778
  signed_url = await cls.get_signed_image(scraper, image_id)
660
779
  img = ImageResponse(signed_url, prompt)
@@ -674,7 +793,7 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
674
793
  think_start = text.find("<think>")
675
794
  think_end = text.find("</think>")
676
795
  if think_start != -1 and think_end != -1 and think_end > think_start:
677
- inner = text[think_start + len("<think>"):think_end].strip()
796
+ inner = text[think_start + len("<think>") : think_end].strip()
678
797
  if inner:
679
798
  think_blocks[ref_id] = inner
680
799
 
@@ -682,7 +801,7 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
682
801
  if yapp_start != -1:
683
802
  yapp_end = text.find("</yapp>", yapp_start)
684
803
  if yapp_end != -1:
685
- yapp_block = text[yapp_start:yapp_end + len("</yapp>")]
804
+ yapp_block = text[yapp_start : yapp_end + len("</yapp>")]
686
805
  image_blocks[ref_id] = yapp_block
687
806
 
688
807
  try:
@@ -712,7 +831,9 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
712
831
 
713
832
  while True:
714
833
  try:
715
- line = await loop.run_in_executor(_executor, lambda: next(lines_iterator, None))
834
+ line = await loop.run_in_executor(
835
+ _executor, lambda: next(lines_iterator, None)
836
+ )
716
837
  if line is None:
717
838
  break
718
839
  except StopIteration:
@@ -728,7 +849,7 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
728
849
 
729
850
  if b"</yapp>" in line:
730
851
  idx = line.find(b"</yapp>")
731
- suffix = line[idx + len(b"</yapp>"):]
852
+ suffix = line[idx + len(b"</yapp>") :]
732
853
  finalize_capture_block(capturing_ref_id, capturing_lines)
733
854
  capturing_ref_id = None
734
855
  capturing_lines = []
@@ -762,7 +883,11 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
762
883
  except json.JSONDecodeError:
763
884
  continue
764
885
 
765
- if chunk_id == reward_id and isinstance(data, dict) and "unclaimedRewardInfo" in data:
886
+ if (
887
+ chunk_id == reward_id
888
+ and isinstance(data, dict)
889
+ and "unclaimedRewardInfo" in data
890
+ ):
766
891
  reward_info = data
767
892
  log_debug(f"Found reward info")
768
893
 
@@ -773,39 +898,68 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
773
898
  right_stream = data.get("rightStream", {})
774
899
  if data.get("quickResponse", {}) != "$undefined":
775
900
  quick_response_id = extract_ref_id(
776
- data.get("quickResponse", {}).get("stream", {}).get("next")
901
+ data.get("quickResponse", {})
902
+ .get("stream", {})
903
+ .get("next")
777
904
  )
778
905
 
779
906
  if data.get("turnId", {}) != "$undefined":
780
907
  turn_id = extract_ref_id(data.get("turnId", {}).get("next"))
781
908
  if data.get("persistedTurn", {}) != "$undefined":
782
- persisted_turn_id = extract_ref_id(data.get("persistedTurn", {}).get("next"))
909
+ persisted_turn_id = extract_ref_id(
910
+ data.get("persistedTurn", {}).get("next")
911
+ )
783
912
  if data.get("leftMessageId", {}) != "$undefined":
784
- left_message_id = extract_ref_id(data.get("leftMessageId", {}).get("next"))
913
+ left_message_id = extract_ref_id(
914
+ data.get("leftMessageId", {}).get("next")
915
+ )
785
916
  if data.get("rightMessageId", {}) != "$undefined":
786
- right_message_id = extract_ref_id(data.get("rightMessageId", {}).get("next"))
917
+ right_message_id = extract_ref_id(
918
+ data.get("rightMessageId", {}).get("next")
919
+ )
787
920
 
788
- reward_id = extract_ref_id(data.get("pendingRewardActionResult", "")) or reward_id
789
- routing_id = extract_ref_id(data.get("routingResultPromise", "")) or routing_id
790
- nudge_new_chat_id = extract_ref_id(data.get("nudgeNewChatPromise", "")) or nudge_new_chat_id
921
+ reward_id = (
922
+ extract_ref_id(data.get("pendingRewardActionResult", ""))
923
+ or reward_id
924
+ )
925
+ routing_id = (
926
+ extract_ref_id(data.get("routingResultPromise", ""))
927
+ or routing_id
928
+ )
929
+ nudge_new_chat_id = (
930
+ extract_ref_id(data.get("nudgeNewChatPromise", ""))
931
+ or nudge_new_chat_id
932
+ )
791
933
  select_stream = [left_stream, right_stream]
792
934
 
793
935
  elif chunk_id == routing_id:
794
936
  yield PlainTextResponse(line.decode(errors="ignore"))
795
937
  if isinstance(data, dict):
796
938
  provider_info = cls.get_dict()
797
- provider_info['model'] = model_id
939
+ provider_info["model"] = model_id
798
940
  for i, selection in enumerate(data.get("modelSelections", [])):
799
941
  if selection.get("selectionSource") == "USER_SELECTED":
800
- target_stream_id = extract_ref_id(select_stream[i].get("next"))
801
- provider_info["modelLabel"] = selection.get("shortLabel")
942
+ target_stream_id = extract_ref_id(
943
+ select_stream[i].get("next")
944
+ )
945
+ provider_info["modelLabel"] = selection.get(
946
+ "shortLabel"
947
+ )
802
948
  provider_info["modelUrl"] = selection.get("externalUrl")
803
949
  log_debug(f"Found target stream ID: {target_stream_id}")
804
950
  else:
805
- variant_stream_id = extract_ref_id(select_stream[i].get("next"))
806
- provider_info["variantLabel"] = selection.get("shortLabel")
807
- provider_info["variantUrl"] = selection.get("externalUrl")
808
- log_debug(f"Found variant stream ID: {variant_stream_id}")
951
+ variant_stream_id = extract_ref_id(
952
+ select_stream[i].get("next")
953
+ )
954
+ provider_info["variantLabel"] = selection.get(
955
+ "shortLabel"
956
+ )
957
+ provider_info["variantUrl"] = selection.get(
958
+ "externalUrl"
959
+ )
960
+ log_debug(
961
+ f"Found variant stream ID: {variant_stream_id}"
962
+ )
809
963
  yield ProviderInfo.from_dict(provider_info)
810
964
 
811
965
  elif target_stream_id and chunk_id == target_stream_id:
@@ -815,7 +969,9 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
815
969
  content = data.get("curr", "")
816
970
  if content:
817
971
  ref_name = extract_ref_name(content)
818
- if ref_name and (ref_name in think_blocks or ref_name in image_blocks):
972
+ if ref_name and (
973
+ ref_name in think_blocks or ref_name in image_blocks
974
+ ):
819
975
  if ref_name in think_blocks:
820
976
  t_text = think_blocks[ref_name]
821
977
  if t_text:
@@ -828,17 +984,14 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
828
984
  img_block_text,
829
985
  ref_name,
830
986
  line_count,
831
- for_target=True
987
+ for_target=True,
832
988
  ):
833
989
  stream["target"].append(chunk)
834
990
  is_started = True
835
991
  yield chunk
836
992
  else:
837
993
  async for chunk in process_content_chunk(
838
- content,
839
- chunk_id,
840
- line_count,
841
- for_target=True
994
+ content, chunk_id, line_count, for_target=True
842
995
  ):
843
996
  stream["target"].append(chunk)
844
997
  is_started = True
@@ -851,10 +1004,7 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
851
1004
  content = data.get("curr", "")
852
1005
  if content:
853
1006
  async for chunk in process_content_chunk(
854
- content,
855
- chunk_id,
856
- line_count,
857
- for_target=False
1007
+ content, chunk_id, line_count, for_target=False
858
1008
  ):
859
1009
  stream["variant"].append(chunk)
860
1010
  if isinstance(chunk, ImageResponse):
@@ -870,10 +1020,7 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
870
1020
  content = data.get("curr", "")
871
1021
  if content:
872
1022
  async for chunk in process_content_chunk(
873
- content,
874
- chunk_id,
875
- line_count,
876
- for_target=False
1023
+ content, chunk_id, line_count, for_target=False
877
1024
  ):
878
1025
  stream["quick"].append(chunk)
879
1026
  quick_content += content
@@ -895,16 +1042,18 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
895
1042
  content = data.get("curr", "")
896
1043
  if content:
897
1044
  async for chunk in process_content_chunk(
898
- content,
899
- chunk_id,
900
- line_count,
901
- for_target=False
1045
+ content, chunk_id, line_count, for_target=False
902
1046
  ):
903
1047
  stream["extra"].append(chunk)
904
- if isinstance(chunk, str) and "<streaming stopped unexpectedly" in chunk:
1048
+ if (
1049
+ isinstance(chunk, str)
1050
+ and "<streaming stopped unexpectedly" in chunk
1051
+ ):
905
1052
  yield FinishReason(chunk)
906
1053
 
907
- yield PlainTextResponse("[Extra] " + line.decode(errors="ignore"))
1054
+ yield PlainTextResponse(
1055
+ "[Extra] " + line.decode(errors="ignore")
1056
+ )
908
1057
 
909
1058
  if variant_image is not None:
910
1059
  yield variant_image
@@ -915,13 +1064,17 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
915
1064
 
916
1065
  finally:
917
1066
  log_debug(f"Get Reward: {reward_kw}")
918
- if reward_kw.get("turn_id") and reward_kw.get("left_message_id") and reward_kw.get("right_message_id"):
1067
+ if (
1068
+ reward_kw.get("turn_id")
1069
+ and reward_kw.get("left_message_id")
1070
+ and reward_kw.get("right_message_id")
1071
+ ):
919
1072
  eval_id = await record_model_feedback(
920
1073
  scraper,
921
1074
  account,
922
1075
  reward_kw["turn_id"],
923
1076
  reward_kw["left_message_id"],
924
- reward_kw["right_message_id"]
1077
+ reward_kw["right_message_id"],
925
1078
  )
926
1079
  if eval_id:
927
1080
  await claim_yupp_reward(scraper, account, eval_id)