g4f 6.9.8__py3-none-any.whl → 6.9.10__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.
@@ -44,9 +44,9 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
44
44
  gen_text_api_endpoint = "https://gen.pollinations.ai/v1/chat/completions"
45
45
  image_models_endpoint = "https://gen.pollinations.ai/image/models"
46
46
  text_models_endpoint = "https://gen.pollinations.ai/text/models"
47
- balance_endpoint = "https://api.gpt4free.workers.dev/api/pollinations/account/balance"
48
- worker_api_endpoint = "https://api.gpt4free.workers.dev/api/pollinations/chat/completions"
49
- worker_models_endpoint = "https://api.gpt4free.workers.dev/api/pollinations/text/models"
47
+ balance_endpoint = "https://g4f.space/api/pollinations/account/balance"
48
+ worker_api_endpoint = "https://g4f.space/api/pollinations/chat/completions"
49
+ worker_models_endpoint = "https://g4f.space/api/pollinations/models"
50
50
 
51
51
  # Models configuration
52
52
  default_model = "openai"
@@ -263,6 +263,8 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
263
263
  has_audio = True
264
264
  break
265
265
  model = "openai-audio" if has_audio else cls.default_model
266
+ if not api_key:
267
+ api_key = AuthManager.load_api_key(cls)
266
268
  if cls.get_models(api_key=api_key, timeout=kwargs.get("timeout", 15)):
267
269
  if model in cls.model_aliases:
268
270
  model = cls.model_aliases[model]
@@ -379,7 +381,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
379
381
  return f"{url}&seed={seed}" if seed else url
380
382
 
381
383
  headers = None
382
- if api_key and api_key.startswith("g4f_") or api_key.startswith("gfs_"):
384
+ if api_key and (api_key.startswith("g4f_") or api_key.startswith("gfs_")):
383
385
  headers = {"authorization": f"Bearer {api_key}"}
384
386
  async with ClientSession(
385
387
  headers=DEFAULT_HEADERS,
g4f/Provider/Yupp.py CHANGED
@@ -6,12 +6,15 @@ import re
6
6
  import time
7
7
  import uuid
8
8
  from concurrent.futures import ThreadPoolExecutor
9
+ from typing import Optional, Dict, Any, List
9
10
 
10
11
  try:
11
12
  import cloudscraper
12
13
  from cloudscraper import CloudScraper
14
+ has_cloudscraper = True
13
15
  except ImportError:
14
16
  from typing import Type as CloudScraper
17
+ has_cloudscraper = False
15
18
 
16
19
  from .helper import get_last_user_message
17
20
  from .yupp.models import YuppModelManager
@@ -20,17 +23,27 @@ from ..debug import log
20
23
  from ..errors import RateLimitError, ProviderException, MissingAuthError, MissingRequirementsError
21
24
  from ..image import is_accepted_format, to_bytes
22
25
  from ..providers.base_provider import AsyncGeneratorProvider, ProviderModelMixin
23
- from ..providers.response import Reasoning, PlainTextResponse, PreviewResponse, JsonConversation, ImageResponse, \
24
- ProviderInfo, FinishReason, JsonResponse, VariantResponse
26
+ from ..providers.response import (
27
+ Reasoning,
28
+ PlainTextResponse,
29
+ PreviewResponse,
30
+ JsonConversation,
31
+ ImageResponse,
32
+ ProviderInfo,
33
+ FinishReason,
34
+ JsonResponse,
35
+ VariantResponse,
36
+ )
25
37
  from ..tools.auth import AuthManager
26
38
  from ..tools.media import merge_media
27
- from ..typing import AsyncResult, Messages, Optional, Dict, Any, List
39
+ from ..typing import AsyncResult, Messages
28
40
 
29
41
  YUPP_ACCOUNTS: List[Dict[str, Any]] = []
30
42
  account_rotation_lock = asyncio.Lock()
31
43
  ImagesCache: Dict[str, dict] = {}
32
44
  _accounts_loaded = False
33
- _executor = ThreadPoolExecutor(max_workers=10)
45
+ _executor = ThreadPoolExecutor(max_workers=32)
46
+ MAX_CACHE_SIZE = 1000
34
47
 
35
48
 
36
49
  def create_scraper():
@@ -87,10 +100,10 @@ async def get_best_yupp_account() -> Optional[Dict[str, Any]]:
87
100
  acc
88
101
  for acc in YUPP_ACCOUNTS
89
102
  if acc["is_valid"]
90
- and (
91
- acc["error_count"] < max_error_count
92
- or now - acc["last_used"] > error_cooldown
93
- )
103
+ and (
104
+ acc["error_count"] < max_error_count
105
+ or now - acc["last_used"] > error_cooldown
106
+ )
94
107
  ]
95
108
 
96
109
  if not valid_accounts:
@@ -98,8 +111,8 @@ async def get_best_yupp_account() -> Optional[Dict[str, Any]]:
98
111
 
99
112
  for acc in valid_accounts:
100
113
  if (
101
- acc["error_count"] >= max_error_count
102
- and now - acc["last_used"] > error_cooldown
114
+ acc["error_count"] >= max_error_count
115
+ and now - acc["last_used"] > error_cooldown
103
116
  ):
104
117
  acc["error_count"] = 0
105
118
 
@@ -109,11 +122,11 @@ async def get_best_yupp_account() -> Optional[Dict[str, Any]]:
109
122
  return account
110
123
 
111
124
 
112
- def sync_claim_yupp_reward(scraper: CloudScraper, account: Dict[str, Any], reward_id: str):
125
+ def sync_claim_yupp_reward(scraper: CloudScraper, account: Dict[str, Any], eval_id: str):
113
126
  try:
114
- log_debug(f"Claiming reward {reward_id}...")
127
+ log_debug(f"Claiming reward {eval_id}...")
115
128
  url = "https://yupp.ai/api/trpc/reward.claim?batch=1"
116
- payload = {"0": {"json": {"rewardId": reward_id}}}
129
+ payload = {"0": {"json": {"evalId": eval_id}}}
117
130
  scraper.cookies.set("__Secure-yupp.session-token", account['token'])
118
131
  response = scraper.post(url, json=payload)
119
132
  response.raise_for_status()
@@ -122,13 +135,104 @@ def sync_claim_yupp_reward(scraper: CloudScraper, account: Dict[str, Any], rewar
122
135
  log_debug(f"Reward claimed successfully. New balance: {balance}")
123
136
  return balance
124
137
  except Exception as e:
125
- log_debug(f"Failed to claim reward {reward_id}. Error: {e}")
138
+ log_debug(f"Failed to claim reward {eval_id}. Error: {e}")
139
+ return None
140
+
141
+
142
+ async def claim_yupp_reward(scraper: CloudScraper, account: Dict[str, Any], eval_id: str):
143
+ loop = asyncio.get_event_loop()
144
+ return await loop.run_in_executor(_executor, sync_claim_yupp_reward, scraper, account, eval_id)
145
+
146
+
147
+ def sync_record_model_feedback(
148
+ scraper: CloudScraper,
149
+ account: Dict[str, Any],
150
+ turn_id: str,
151
+ left_message_id: str,
152
+ right_message_id: str
153
+ ) -> Optional[str]:
154
+ try:
155
+ log_debug(f"Recording model feedback for turn {turn_id}...")
156
+ url = "https://yupp.ai/api/trpc/evals.recordModelFeedback?batch=1"
157
+ payload = {
158
+ "0": {
159
+ "json": {
160
+ "turnId": turn_id,
161
+ "evalType": "SELECTION",
162
+ "messageEvals": [
163
+ {
164
+ "messageId": right_message_id,
165
+ "rating": "GOOD",
166
+ "reasons": ["Fast"]
167
+ },
168
+ {
169
+ "messageId": left_message_id,
170
+ "rating": "BAD",
171
+ "reasons": []
172
+ }
173
+ ],
174
+ "comment": "",
175
+ "requireReveal": False
176
+ }
177
+ }
178
+ }
179
+ scraper.cookies.set("__Secure-yupp.session-token", account['token'])
180
+ response = scraper.post(url, json=payload)
181
+ response.raise_for_status()
182
+ data = response.json()
183
+
184
+ for result in data:
185
+ json_data = result.get("result", {}).get("data", {}).get("json", {})
186
+ eval_id = json_data.get("evalId")
187
+ final_reward = json_data.get("finalRewardAmount")
188
+ log_debug(f"Feedback recorded - evalId: {eval_id}, reward: {final_reward}")
189
+
190
+ if final_reward:
191
+ return eval_id
192
+ return None
193
+ except Exception as e:
194
+ log_debug(f"Failed to record model feedback. Error: {e}")
126
195
  return None
127
196
 
128
197
 
129
- async def claim_yupp_reward(scraper: CloudScraper, account: Dict[str, Any], reward_id: str):
198
+ async def record_model_feedback(
199
+ scraper: CloudScraper,
200
+ account: Dict[str, Any],
201
+ turn_id: str,
202
+ left_message_id: str,
203
+ right_message_id: str
204
+ ) -> Optional[str]:
205
+ loop = asyncio.get_event_loop()
206
+ return await loop.run_in_executor(
207
+ _executor, sync_record_model_feedback, scraper, account, turn_id, left_message_id, right_message_id
208
+ )
209
+
210
+
211
+ def sync_delete_chat(scraper: CloudScraper, account: Dict[str, Any], chat_id: str) -> bool:
212
+ try:
213
+ log_debug(f"Deleting chat {chat_id}...")
214
+ url = "https://yupp.ai/api/trpc/chat.deleteChat?batch=1"
215
+ payload = {"0": {"json": {"chatId": chat_id}}}
216
+ scraper.cookies.set("__Secure-yupp.session-token", account['token'])
217
+ response = scraper.post(url, json=payload)
218
+ response.raise_for_status()
219
+ data = response.json()
220
+ if (
221
+ isinstance(data, list) and len(data) > 0
222
+ and data[0].get("result", {}).get("data", {}).get("json") is None
223
+ ):
224
+ log_debug(f"Chat {chat_id} deleted successfully")
225
+ return True
226
+ log_debug(f"Unexpected response while deleting chat: {data}")
227
+ return False
228
+ except Exception as e:
229
+ log_debug(f"Failed to delete chat {chat_id}: {e}")
230
+ return False
231
+
232
+
233
+ async def delete_chat(scraper: CloudScraper, account: Dict[str, Any], chat_id: str) -> bool:
130
234
  loop = asyncio.get_event_loop()
131
- return await loop.run_in_executor(_executor, sync_claim_yupp_reward, scraper, account, reward_id)
235
+ return await loop.run_in_executor(_executor, sync_delete_chat, scraper, account, chat_id)
132
236
 
133
237
 
134
238
  def sync_make_chat_private(scraper: CloudScraper, account: Dict[str, Any], chat_id: str) -> bool:
@@ -148,8 +252,8 @@ def sync_make_chat_private(scraper: CloudScraper, account: Dict[str, Any], chat_
148
252
  response.raise_for_status()
149
253
  data = response.json()
150
254
  if (
151
- isinstance(data, list) and len(data) > 0
152
- and "json" in data[0].get("result", {}).get("data", {})
255
+ isinstance(data, list) and len(data) > 0
256
+ and "json" in data[0].get("result", {}).get("data", {})
153
257
  ):
154
258
  log_debug(f"Chat {chat_id} is now PRIVATE")
155
259
  return True
@@ -205,10 +309,18 @@ def format_messages_for_yupp(messages: Messages) -> str:
205
309
  return result
206
310
 
207
311
 
312
+ def evict_cache_if_needed():
313
+ global ImagesCache
314
+ if len(ImagesCache) > MAX_CACHE_SIZE:
315
+ keys_to_remove = list(ImagesCache.keys())[:len(ImagesCache) - MAX_CACHE_SIZE + 100]
316
+ for key in keys_to_remove:
317
+ del ImagesCache[key]
318
+
319
+
208
320
  class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
209
321
  url = "https://yupp.ai"
210
322
  login_url = "https://discord.gg/qXA4Wf4Fsm"
211
- working = CloudScraper is not None
323
+ working = has_cloudscraper
212
324
  active_by_default = True
213
325
  supports_stream = True
214
326
  image_cache = True
@@ -228,8 +340,10 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
228
340
  cls.models_tags = {model.get("name"): manager.processor.generate_tags(model) for model in models}
229
341
  cls.models = [model.get("name") for model in models]
230
342
  cls.image_models = [model.get("name") for model in models if model.get("isImageGeneration")]
231
- cls.vision_models = [model.get("name") for model in models if
232
- "image/*" in model.get("supportedAttachmentMimeTypes", [])]
343
+ cls.vision_models = [
344
+ model.get("name") for model in models
345
+ if "image/*" in model.get("supportedAttachmentMimeTypes", [])
346
+ ]
233
347
  return cls.models
234
348
 
235
349
  @classmethod
@@ -271,8 +385,15 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
271
385
 
272
386
  attachment_resp = scraper.post(
273
387
  "https://yupp.ai/api/trpc/chat.createAttachmentForUploadedFile?batch=1",
274
- json={"0": {"json": {"fileName": name, "contentType": is_accepted_format(data),
275
- "fileId": upload_info["fileId"]}}},
388
+ json={
389
+ "0": {
390
+ "json": {
391
+ "fileName": name,
392
+ "contentType": is_accepted_format(data),
393
+ "fileId": upload_info["fileId"]
394
+ }
395
+ }
396
+ },
276
397
  cookies={"__Secure-yupp.session-token": account["token"]}
277
398
  )
278
399
  attachment_resp.raise_for_status()
@@ -283,6 +404,7 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
283
404
  "attachmentId": attachment["attachment_id"],
284
405
  "chatMessageId": ""
285
406
  }
407
+ evict_cache_if_needed()
286
408
  ImagesCache[image_hash] = file_info
287
409
  files.append(file_info)
288
410
  return files
@@ -305,8 +427,8 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
305
427
  }
306
428
  )
307
429
  resp.raise_for_status()
308
- data = resp.json()
309
- return data[0]["result"]["data"]["json"]["signed_url"]
430
+ data = resp.json()[0]["result"]["data"]["json"]
431
+ return data.get("signed_url", data.get("signedURL"))
310
432
 
311
433
  @classmethod
312
434
  async def get_signed_image(cls, scraper: CloudScraper, image_id: str) -> str:
@@ -314,23 +436,26 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
314
436
  return await loop.run_in_executor(_executor, cls.sync_get_signed_image, scraper, image_id)
315
437
 
316
438
  @classmethod
317
- def sync_stream_request(cls, scraper: CloudScraper, url: str, payload: list, headers: dict,
318
- timeout: int):
439
+ def sync_stream_request(cls, scraper: CloudScraper, url: str, payload: list, headers: dict, timeout: int):
319
440
  response = scraper.post(url, json=payload, headers=headers, stream=True, timeout=timeout)
320
441
  response.raise_for_status()
321
442
  return response
322
443
 
323
444
  @classmethod
324
445
  async def create_async_generator(
325
- cls,
326
- model: str,
327
- messages: Messages,
328
- proxy: str = None,
329
- **kwargs,
446
+ cls,
447
+ model: str,
448
+ messages: Messages,
449
+ proxy: str = None,
450
+ api_key: str = None,
451
+ **kwargs,
330
452
  ) -> AsyncResult:
331
- if CloudScraper is None:
332
- raise MissingRequirementsError("cloudscraper library is required for Yupp provider | install it via 'pip install cloudscraper'")
333
- api_key = kwargs.get("api_key")
453
+ if not has_cloudscraper:
454
+ raise MissingRequirementsError(
455
+ "cloudscraper library is required for Yupp provider | install it via 'pip install cloudscraper'"
456
+ )
457
+ if not api_key:
458
+ api_key = AuthManager.load_api_key(cls)
334
459
  if not api_key:
335
460
  api_key = get_cookies("yupp.ai", False).get("__Secure-yupp.session-token")
336
461
  if api_key:
@@ -351,7 +476,8 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
351
476
  prompt = get_last_user_message(messages, prompt)
352
477
 
353
478
  log_debug(
354
- f"Use url_uuid: {url_uuid}, Formatted prompt length: {len(prompt)}, Is new conversation: {is_new_conversation}")
479
+ f"Use url_uuid: {url_uuid}, Formatted prompt length: {len(prompt)}, Is new conversation: {is_new_conversation}"
480
+ )
355
481
 
356
482
  max_attempts = len(YUPP_ACCOUNTS)
357
483
  for attempt in range(max_attempts):
@@ -437,10 +563,13 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
437
563
  timeout
438
564
  )
439
565
 
440
- asyncio.create_task(make_chat_private(scraper, account, url_uuid))
441
-
442
- async for chunk in cls._process_stream_response(response, account, scraper, prompt, model):
443
- yield chunk
566
+ try:
567
+ async for chunk in cls._process_stream_response(response, account, scraper, prompt, model):
568
+ yield chunk
569
+ finally:
570
+ response.close()
571
+ if not kwargs.get("conversation"):
572
+ asyncio.create_task(delete_chat(scraper, account, url_uuid))
444
573
  return
445
574
 
446
575
  except RateLimitError:
@@ -462,7 +591,8 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
462
591
  log_debug(f"Unexpected error with account ...{account['token'][-4:]}: {str(e)}")
463
592
  error_str = str(e).lower()
464
593
  if "500" in error_str or "internal server error" in error_str:
465
- account["is_valid"] = False
594
+ async with account_rotation_lock:
595
+ account["error_count"] += 1
466
596
  continue
467
597
  async with account_rotation_lock:
468
598
  account["error_count"] += 1
@@ -472,12 +602,12 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
472
602
 
473
603
  @classmethod
474
604
  async def _process_stream_response(
475
- cls,
476
- response,
477
- account: Dict[str, Any],
478
- scraper: CloudScraper,
479
- prompt: str,
480
- model_id: str
605
+ cls,
606
+ response,
607
+ account: Dict[str, Any],
608
+ scraper: CloudScraper,
609
+ prompt: str,
610
+ model_id: str
481
611
  ) -> AsyncResult:
482
612
  line_pattern = re.compile(b"^([0-9a-fA-F]+):(.*)")
483
613
  target_stream_id = None
@@ -562,6 +692,7 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
562
692
  is_started: bool = False
563
693
  variant_image: Optional[ImageResponse] = None
564
694
  reward_id = "a"
695
+ reward_kw = {}
565
696
  routing_id = "e"
566
697
  turn_id = None
567
698
  persisted_turn_id = None
@@ -642,7 +773,8 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
642
773
  right_stream = data.get("rightStream", {})
643
774
  if data.get("quickResponse", {}) != "$undefined":
644
775
  quick_response_id = extract_ref_id(
645
- data.get("quickResponse", {}).get("stream", {}).get("next"))
776
+ data.get("quickResponse", {}).get("stream", {}).get("next")
777
+ )
646
778
 
647
779
  if data.get("turnId", {}) != "$undefined":
648
780
  turn_id = extract_ref_id(data.get("turnId", {}).get("next"))
@@ -693,20 +825,20 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
693
825
  if ref_name in image_blocks:
694
826
  img_block_text = image_blocks[ref_name]
695
827
  async for chunk in process_content_chunk(
696
- img_block_text,
697
- ref_name,
698
- line_count,
699
- for_target=True
828
+ img_block_text,
829
+ ref_name,
830
+ line_count,
831
+ for_target=True
700
832
  ):
701
833
  stream["target"].append(chunk)
702
834
  is_started = True
703
835
  yield chunk
704
836
  else:
705
837
  async for chunk in process_content_chunk(
706
- content,
707
- chunk_id,
708
- line_count,
709
- for_target=True
838
+ content,
839
+ chunk_id,
840
+ line_count,
841
+ for_target=True
710
842
  ):
711
843
  stream["target"].append(chunk)
712
844
  is_started = True
@@ -719,10 +851,10 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
719
851
  content = data.get("curr", "")
720
852
  if content:
721
853
  async for chunk in process_content_chunk(
722
- content,
723
- chunk_id,
724
- line_count,
725
- for_target=False
854
+ content,
855
+ chunk_id,
856
+ line_count,
857
+ for_target=False
726
858
  ):
727
859
  stream["variant"].append(chunk)
728
860
  if isinstance(chunk, ImageResponse):
@@ -738,32 +870,35 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
738
870
  content = data.get("curr", "")
739
871
  if content:
740
872
  async for chunk in process_content_chunk(
741
- content,
742
- chunk_id,
743
- line_count,
744
- for_target=False
873
+ content,
874
+ chunk_id,
875
+ line_count,
876
+ for_target=False
745
877
  ):
746
878
  stream["quick"].append(chunk)
747
879
  quick_content += content
748
880
  yield PreviewResponse(content)
749
881
 
750
- elif chunk_id in [turn_id, persisted_turn_id]:
882
+ elif chunk_id == turn_id:
883
+ reward_kw["turn_id"] = data.get("curr", "")
884
+
885
+ elif chunk_id == persisted_turn_id:
751
886
  pass
752
887
 
753
888
  elif chunk_id == right_message_id:
754
- pass
889
+ reward_kw["right_message_id"] = data.get("curr", "")
755
890
 
756
891
  elif chunk_id == left_message_id:
757
- pass
892
+ reward_kw["left_message_id"] = data.get("curr", "")
758
893
 
759
894
  elif isinstance(data, dict) and "curr" in data:
760
895
  content = data.get("curr", "")
761
896
  if content:
762
897
  async for chunk in process_content_chunk(
763
- content,
764
- chunk_id,
765
- line_count,
766
- for_target=False
898
+ content,
899
+ chunk_id,
900
+ line_count,
901
+ for_target=False
767
902
  ):
768
903
  stream["extra"].append(chunk)
769
904
  if isinstance(chunk, str) and "<streaming stopped unexpectedly" in chunk:
@@ -779,7 +914,14 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
779
914
  log_debug(f"Finished processing {line_count} lines")
780
915
 
781
916
  finally:
782
- if reward_info and "unclaimedRewardInfo" in reward_info:
783
- rid = reward_info["unclaimedRewardInfo"].get("rewardId")
784
- if rid:
785
- await claim_yupp_reward(scraper, account, rid)
917
+ 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"):
919
+ eval_id = await record_model_feedback(
920
+ scraper,
921
+ account,
922
+ reward_kw["turn_id"],
923
+ reward_kw["left_message_id"],
924
+ reward_kw["right_message_id"]
925
+ )
926
+ if eval_id:
927
+ await claim_yupp_reward(scraper, account, eval_id)
g4f/Provider/__init__.py CHANGED
@@ -49,9 +49,7 @@ from .OperaAria import OperaAria
49
49
  from .Perplexity import Perplexity
50
50
  from .PollinationsAI import PollinationsAI
51
51
  from .PollinationsImage import PollinationsImage
52
- from .Startnest import Startnest
53
52
  from .Qwen import Qwen
54
- from .StringableInference import StringableInference
55
53
  from .TeachAnything import TeachAnything
56
54
  from .WeWordle import WeWordle
57
55
  from .Yqcloud import Yqcloud
@@ -13,6 +13,7 @@ from ...typing import AsyncResult, Messages
13
13
  class Ollama(OpenaiTemplate):
14
14
  label = "Ollama 🦙"
15
15
  url = "https://ollama.com"
16
+ base_url = "https://g4f.space/api/ollama"
16
17
  login_url = "https://ollama.com/settings/keys"
17
18
  needs_auth = False
18
19
  working = True
@@ -34,7 +35,7 @@ class Ollama(OpenaiTemplate):
34
35
  cls.live += 1
35
36
  cls.models = [model["name"] for model in models]
36
37
  if base_url is None:
37
- host = os.getenv("OLLAMA_HOST", "127.0.0.1")
38
+ host = os.getenv("OLLAMA_HOST", "localhost")
38
39
  port = os.getenv("OLLAMA_PORT", "11434")
39
40
  url = f"http://{host}:{port}/api/tags"
40
41
  else:
@@ -66,7 +67,7 @@ class Ollama(OpenaiTemplate):
66
67
  base_url: str = f"http://{host}:{port}/v1"
67
68
  if model in cls.local_models:
68
69
  async with StreamSession(headers={"Authorization": f"Bearer {api_key}"}, proxy=proxy) as session:
69
- async with session.post(f"{base_url}/api/chat", json={
70
+ async with session.post(f"{base_url.replace('/v1', '')}/api/chat", json={
70
71
  "model": model,
71
72
  "messages": messages,
72
73
  }) as response:
@@ -14,7 +14,7 @@ from ..helper import format_media_prompt
14
14
  class Azure(OpenaiTemplate):
15
15
  label = "Azure ☁️"
16
16
  url = "https://ai.azure.com"
17
- base_url = "https://g4f.dev/api/azure"
17
+ base_url = "https://g4f.space/api/azure"
18
18
  working = True
19
19
  active_by_default = False
20
20
  login_url = "https://discord.gg/qXA4Wf4Fsm"
@@ -9,7 +9,7 @@ from ..template import OpenaiTemplate
9
9
  class Claude(OpenaiTemplate):
10
10
  label = "Claude 💥"
11
11
  url = "https://claude.ai"
12
- base_url = "https://g4f.dev/api/claude"
12
+ base_url = "https://g4f.space/api/claude"
13
13
  working = True
14
14
  active_by_default = True
15
15
  login_url = "https://discord.gg/qXA4Wf4Fsm"
@@ -7,7 +7,7 @@ class GeminiPro(OpenaiTemplate):
7
7
  url = "https://ai.google.dev"
8
8
  login_url = "https://aistudio.google.com/u/0/apikey"
9
9
  base_url = "https://generativelanguage.googleapis.com/v1beta/openai"
10
- backup_url = "https://g4f.dev/custom/srv_mjnryskw9fe0567fa267"
10
+ backup_url = "https://g4f.space/api/gemini-v1beta"
11
11
  active_by_default = True
12
12
  working = True
13
13
  default_model = "models/gemini-2.5-flash"
@@ -7,6 +7,7 @@ class Groq(OpenaiTemplate):
7
7
  url = "https://console.groq.com/playground"
8
8
  login_url = "https://console.groq.com/keys"
9
9
  base_url = "https://api.groq.com/openai/v1"
10
+ backup_url = "https://g4f.space/api/groq"
10
11
  working = True
11
12
  active_by_default = True
12
13
  default_model = DEFAULT_MODEL
@@ -6,6 +6,7 @@ from ...config import DEFAULT_MODEL
6
6
  class Nvidia(OpenaiTemplate):
7
7
  label = "Nvidia"
8
8
  base_url = "https://integrate.api.nvidia.com/v1"
9
+ backup_url = "https://g4f.space/api/nvidia"
9
10
  login_url = "https://google.com"
10
11
  url = "https://build.nvidia.com"
11
12
  working = True
@@ -13,6 +13,7 @@ class OpenRouter(OpenaiTemplate):
13
13
 
14
14
  class OpenRouterFree(OpenRouter):
15
15
  label = "OpenRouter (free)"
16
+ base_url = "https://g4f.space/api/openrouter"
16
17
  max_tokens = 4096
17
18
  active_by_default = True
18
19
 
g4f/client/__init__.py CHANGED
@@ -821,7 +821,7 @@ class ClientFactory:
821
821
  elif provider.startswith("custom:"):
822
822
  if provider.startswith("custom:"):
823
823
  serverId = provider[7:]
824
- base_url = f"https://api.gpt4free.workers.dev/custom/{serverId}"
824
+ base_url = f"https://g4f.space/custom/{serverId}"
825
825
  if not base_url:
826
826
  raise ValueError("base_url is required for custom providers")
827
827
  provider = create_custom_provider(base_url, api_key, name=name, **kwargs)
@@ -331,7 +331,7 @@ class Backend_Api(Api):
331
331
  for chunk in response:
332
332
  if isinstance(chunk, FinishReason):
333
333
  yield f"[{chunk.reason}]" if chunk.reason != "stop" else ""
334
- elif not isinstance(chunk, Exception):
334
+ elif not isinstance(chunk, (Exception, JsonResponse)):
335
335
  chunk = str(chunk)
336
336
  if chunk:
337
337
  yield chunk
g4f/mcp/server.py CHANGED
@@ -10,13 +10,21 @@ The server exposes tools for:
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import os
14
+ import re
13
15
  import sys
14
16
  import json
15
17
  import asyncio
18
+ import hashlib
19
+ from email.utils import formatdate
16
20
  from typing import Any, Dict, List, Optional, Union
17
21
  from dataclasses import dataclass
22
+ from urllib.parse import unquote_plus
18
23
 
19
24
  from ..debug import enable_logging
25
+ from ..cookies import read_cookie_files
26
+ from ..image import EXTENSIONS_MAP
27
+ from ..image.copy_images import get_media_dir, copy_media, get_source_url
20
28
 
21
29
  from .tools import MarkItDownTool, TextToAudioTool, WebSearchTool, WebScrapeTool, ImageGenerationTool
22
30
  from .tools import WebSearchTool, WebScrapeTool, ImageGenerationTool
@@ -214,6 +222,7 @@ class MCPServer:
214
222
  sys.exit(1)
215
223
 
216
224
  enable_logging()
225
+ read_cookie_files()
217
226
 
218
227
  async def handle_mcp_request(request: web.Request) -> web.Response:
219
228
  nonlocal origin
@@ -273,17 +282,120 @@ class MCPServer:
273
282
  "server": self.server_info
274
283
  })
275
284
 
285
+ async def handle_media(request: web.Request) -> web.Response:
286
+ """Serve media files from generated_media directory"""
287
+ filename = request.match_info.get('filename', '')
288
+ if not filename:
289
+ return web.Response(status=404, text="File not found")
290
+
291
+ def get_timestamp(s):
292
+ m = re.match("^[0-9]+", s)
293
+ return int(m.group(0)) if m else 0
294
+
295
+ target = os.path.join(get_media_dir(), os.path.basename(filename))
296
+
297
+ # Try URL-decoded filename if not found
298
+ if not os.path.isfile(target):
299
+ other_name = os.path.join(get_media_dir(), os.path.basename(unquote_plus(filename)))
300
+ if os.path.isfile(other_name):
301
+ target = other_name
302
+
303
+ # Get file extension and mime type
304
+ ext = os.path.splitext(filename)[1][1:].lower()
305
+ mime_type = EXTENSIONS_MAP.get(ext, "application/octet-stream")
306
+
307
+ # Try to fetch from backend if file doesn't exist
308
+ if not os.path.isfile(target) and mime_type != "application/octet-stream":
309
+ source_url = get_source_url(str(request.query_string))
310
+ ssl = None
311
+ if source_url is not None:
312
+ try:
313
+ await copy_media([source_url], target=target, ssl=ssl)
314
+ sys.stderr.write(f"File copied from {source_url}\n")
315
+ except Exception as e:
316
+ sys.stderr.write(f"Download failed: {source_url} - {e}\n")
317
+ raise web.HTTPFound(location=source_url)
318
+
319
+ if not os.path.isfile(target):
320
+ return web.Response(status=404, text="File not found")
321
+
322
+ # Build response headers
323
+ stat_result = os.stat(target)
324
+ headers = {
325
+ "cache-control": "public, max-age=31536000",
326
+ "last-modified": formatdate(get_timestamp(filename), usegmt=True),
327
+ "etag": f'"{hashlib.md5(filename.encode()).hexdigest()}"',
328
+ "content-length": str(stat_result.st_size),
329
+ "content-type": mime_type,
330
+ "access-control-allow-origin": "*",
331
+ }
332
+
333
+ # Check for conditional request
334
+ if_none_match = request.headers.get("if-none-match")
335
+ if if_none_match:
336
+ etag = headers["etag"]
337
+ if etag in [tag.strip(" W/") for tag in if_none_match.split(",")]:
338
+ return web.Response(status=304, headers=headers)
339
+
340
+ # Serve the file
341
+ return web.FileResponse(target, headers=headers)
342
+
343
+ async def handle_synthesize(request: web.Request) -> web.Response:
344
+ """Handle synthesize requests for text-to-speech"""
345
+ provider_name = request.match_info.get('provider', '')
346
+ if not provider_name:
347
+ return web.Response(status=400, text="Provider not specified")
348
+
349
+ try:
350
+ from ..Provider import ProviderUtils
351
+ provider_handler = ProviderUtils.convert.get(provider_name)
352
+ if provider_handler is None:
353
+ return web.Response(status=404, text=f"Provider not found: {provider_name}")
354
+ except Exception as e:
355
+ return web.Response(status=404, text=f"Provider not found: {provider_name}")
356
+
357
+ if not hasattr(provider_handler, "synthesize"):
358
+ return web.Response(status=500, text=f"Provider doesn't support synthesize: {provider_name}")
359
+
360
+ # Get query parameters
361
+ params = dict(request.query)
362
+
363
+ try:
364
+ # Call the synthesize method
365
+ response_data = provider_handler.synthesize(params)
366
+
367
+ # Handle async generator
368
+ async def generate():
369
+ async for chunk in response_data:
370
+ yield chunk
371
+
372
+ content_type = getattr(provider_handler, "synthesize_content_type", "application/octet-stream")
373
+ return web.Response(
374
+ body=b"".join([chunk async for chunk in generate()]),
375
+ content_type=content_type,
376
+ headers={
377
+ "cache-control": "max-age=604800",
378
+ "access-control-allow-origin": "*",
379
+ }
380
+ )
381
+ except Exception as e:
382
+ sys.stderr.write(f"Synthesize error: {e}\n")
383
+ return web.Response(status=500, text=f"Synthesize error: {str(e)}")
384
+
276
385
  # Create aiohttp application
277
386
  app = web.Application()
278
387
  app.router.add_options('/mcp', lambda request: web.Response(headers={"access-control-allow-origin": "*", "access-control-allow-methods": "POST, OPTIONS", "access-control-allow-headers": "Content-Type"}))
279
388
  app.router.add_post('/mcp', handle_mcp_request)
280
389
  app.router.add_get('/health', handle_health)
390
+ app.router.add_get('/media/{filename:.*}', handle_media)
391
+ app.router.add_get('/backend-api/v2/synthesize/{provider}', handle_synthesize)
281
392
 
282
393
  # Start server
283
394
  sys.stderr.write(f"Starting {self.server_info['name']} v{self.server_info['version']} (HTTP mode)\n")
284
395
  sys.stderr.write(f"Listening on http://{host}:{port}\n")
285
396
  sys.stderr.write(f"MCP endpoint: http://{host}:{port}/mcp\n")
286
397
  sys.stderr.write(f"Health check: http://{host}:{port}/health\n")
398
+ sys.stderr.write(f"Media files: http://{host}:{port}/media/{{filename}}\n")
287
399
  sys.stderr.flush()
288
400
 
289
401
  runner = web.AppRunner(app)
g4f/mcp/tools.py CHANGED
@@ -241,8 +241,7 @@ class ImageGenerationTool(MCPTool):
241
241
  model=model,
242
242
  prompt=prompt,
243
243
  width=width,
244
- height=height,
245
- response_format="url"
244
+ height=height
246
245
  )
247
246
 
248
247
  # Get the image data with proper validation
g4f/models.py CHANGED
@@ -19,7 +19,6 @@ from .Provider import (
19
19
  OIVSCodeSer0501,
20
20
  OperaAria,
21
21
  Perplexity,
22
- Startnest,
23
22
  OpenAIFM,
24
23
  PollinationsAI,
25
24
  PollinationsImage,
@@ -157,7 +156,6 @@ default = Model(
157
156
  Copilot,
158
157
  DeepInfra,
159
158
  OperaAria,
160
- Startnest,
161
159
  GLM,
162
160
  PollinationsAI,
163
161
  Qwen,
@@ -179,7 +177,6 @@ default_vision = VisionModel(
179
177
  OIVSCodeSer2,
180
178
  PollinationsAI,
181
179
  OperaAria,
182
- Startnest,
183
180
  Together,
184
181
  HuggingSpace,
185
182
  GeminiPro,
@@ -207,7 +204,7 @@ gpt_4o = VisionModel(
207
204
  gpt_4o_mini = Model(
208
205
  name = 'gpt-4o-mini',
209
206
  base_provider = 'OpenAI',
210
- best_provider = IterListProvider([Chatai, OIVSCodeSer2, Startnest, OpenaiChat])
207
+ best_provider = IterListProvider([Chatai, OIVSCodeSer2, OpenaiChat])
211
208
  )
212
209
 
213
210
  # gpt_4o_mini_audio = AudioModel(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: g4f
3
- Version: 6.9.8
3
+ Version: 6.9.10
4
4
  Summary: The official gpt4free repository | various collection of powerful language models
5
5
  Home-page: https://github.com/xtekky/gpt4free
6
6
  Author: Tekky
@@ -42,6 +42,7 @@ Requires-Dist: setuptools; extra == "all"
42
42
  Requires-Dist: markitdown[all]; extra == "all"
43
43
  Requires-Dist: python-dotenv; extra == "all"
44
44
  Requires-Dist: aiofile; extra == "all"
45
+ Requires-Dist: cloudscraper; extra == "all"
45
46
  Provides-Extra: slim
46
47
  Requires-Dist: curl_cffi>=0.6.2; extra == "slim"
47
48
  Requires-Dist: certifi; extra == "slim"
@@ -61,6 +62,7 @@ Requires-Dist: pypdf2; extra == "slim"
61
62
  Requires-Dist: python-docx; extra == "slim"
62
63
  Requires-Dist: python-dotenv; extra == "slim"
63
64
  Requires-Dist: aiofile; extra == "slim"
65
+ Requires-Dist: cloudscraper; extra == "slim"
64
66
  Provides-Extra: image
65
67
  Requires-Dist: pillow; extra == "image"
66
68
  Requires-Dist: cairosvg; extra == "image"
@@ -5,7 +5,7 @@ g4f/cookies.py,sha256=gx68OX5he2bYvsvvIQEESMUfwnVI18DOMJt9KvQrM8g,7915
5
5
  g4f/debug.py,sha256=Dh0cmlxUydMDfjJBiPWrO9dAuvv2sTElb3R036ax-34,1021
6
6
  g4f/errors.py,sha256=8Mp77UMW9_c8Flr1Z0Cvdrv0ixOF9CE0j1QX-cc5Zfc,2368
7
7
  g4f/files.py,sha256=JzlWe3V6Xc7FN4eag7Utc4P9dHN1s1sMKFvYSaM0NQg,907
8
- g4f/models.py,sha256=95ZV0HaQF7faPcEV7D_g9pjZc09tWU1oOF0ip78yKt8,24991
8
+ g4f/models.py,sha256=QZV4UAis0QYaWGVO4Nv6n_3wMrLQvWHJ9V11Lo1EmBQ,24927
9
9
  g4f/typing.py,sha256=fBBb74HkxtLa358vjcd1U5yr3ThlKzP2DfE8wdx01lA,2388
10
10
  g4f/version.py,sha256=Lt6aOvuLBfDOHUSAtmncRMuNoDtisb1m8a447o7DkoM,4382
11
11
  g4f/Provider/ApiAirforce.py,sha256=gYaq0MjLDVtzfXpnkhGWdKAZqnfC2UK1dji5u_EvhsQ,1148
@@ -23,16 +23,14 @@ g4f/Provider/Mintlify.py,sha256=3Bvy1oh6scg4xs9pw5RY4mmi8XEvQDelnNp664TefbU,7937
23
23
  g4f/Provider/OIVSCodeSer.py,sha256=clebON7Ssd9syewh3BWT59XOeB-WORXF6FPOwbzfRmo,1366
24
24
  g4f/Provider/OperaAria.py,sha256=sLnTOKzbW9BxTxNmHXJ-YDnhPU6Dj6MBdDfqDh-Zz-c,14662
25
25
  g4f/Provider/Perplexity.py,sha256=GUebbVin9hCg4FDei9RX2N6WboFcDDPm061WnnpPRaI,13236
26
- g4f/Provider/PollinationsAI.py,sha256=pbZSxqezkGma80OXno_bFecvrdD9EUCGQPkscPYI6ZU,21786
26
+ g4f/Provider/PollinationsAI.py,sha256=8LMR8wl3Lh-pzhUHJW71rObSHgBeo7p4LL5GD6IWe3Y,21814
27
27
  g4f/Provider/PollinationsImage.py,sha256=wdGY9kbPGlqAkyeJcjXgWOG3HLVPU4QK-JENwg3gmwk,2297
28
28
  g4f/Provider/Qwen.py,sha256=XPJHRlobijqjdDGVqA-aVyRx2SeM28zSWf-NmzxJtgE,23876
29
- g4f/Provider/Startnest.py,sha256=OocXEAK3pKG-tC_D_chGE7GD22dZr67J1m7JBhLxniM,9526
30
- g4f/Provider/StringableInference.py,sha256=ZohMZrVAn6G82zrYpLTvELkzfds4nxu09lx7wizFnbk,999
31
29
  g4f/Provider/TeachAnything.py,sha256=ST87YdOdxtIc5JMaKzGdVy9J9wlhdhYIchRtXBljIBU,2843
32
30
  g4f/Provider/WeWordle.py,sha256=ocKEfMYBKWPzv2WoDntn-WC-DkrSg5b_ipL9VYhTgsw,7007
33
31
  g4f/Provider/Yqcloud.py,sha256=xzX-G-Lv0pyOHTypSJdavmbm3GPxnL-wRoT3VmjzUIM,3213
34
- g4f/Provider/Yupp.py,sha256=flSbW_PDMqdnJU7jCyg1wWmHr-QV3DHIzdlEI5w6-TY,32365
35
- g4f/Provider/__init__.py,sha256=l6qxw1XlGgFqTTI2Q8YjlP-Yr17viOiPj1eaU8K9LEo,2782
32
+ g4f/Provider/Yupp.py,sha256=E3ft5HshW-IfGdE2KtdpNOmbBkBmBeyl-c8xM-BTpEo,36906
33
+ g4f/Provider/__init__.py,sha256=2azA2zjXZjkH3yDJSoWKeB3SCGB7SIa_0_DN44Lf39g,2684
36
34
  g4f/Provider/base_provider.py,sha256=lAd80-2hO1kzs9n9TUKv6fR4zHjSdZBqVErpm_gna9o,199
37
35
  g4f/Provider/helper.py,sha256=_4fO_b2U8BnpuF6kijUM4SaFbX2Pcz6mksxK9mVXhP4,111
38
36
  g4f/Provider/audio/EdgeTTS.py,sha256=VXcLCZDdf8DL0jIEO3tvduPK4NguUMEDJg9U5MkNvJk,2822
@@ -55,16 +53,16 @@ g4f/Provider/hf_space/StabilityAI_SD35Large.py,sha256=-VT4qa_K6MshG2TX6y7k01NMGH
55
53
  g4f/Provider/hf_space/__init__.py,sha256=licdlcuTs0yPwADmjBUA5uoN4ao-FnCUmlKiMWoHJ2g,3602
56
54
  g4f/Provider/hf_space/raise_for_status.py,sha256=xoVwrZSwJUvqQkSfeUAMUst8SyobpSKPux1iYu4SNH0,935
57
55
  g4f/Provider/local/Local.py,sha256=b6vSZcr5CfEXD5GMqNtwCfXNJtKyWKPg3jqBWJEZEwQ,1251
58
- g4f/Provider/local/Ollama.py,sha256=ukgvoCSDiXEqhPc6i-awylqiuSnFLpau0uMX9YfsJuA,3770
56
+ g4f/Provider/local/Ollama.py,sha256=MtB36eUcn6iyJ9XZWfEHIrkCGeZ0Z6Hysm7AgHQUwE0,3835
59
57
  g4f/Provider/local/__init__.py,sha256=cEFsdfkq0bgVAIM__Sxc6WfIoGa3_Ymol26kQb6x14k,73
60
58
  g4f/Provider/needs_auth/AIBadgr.py,sha256=Oc05Cs-x40sF7tcrAyUqyoZ-TbtmV4wnMIm5aEjQ0xw,420
61
59
  g4f/Provider/needs_auth/Anthropic.py,sha256=-8hRSpSGaC3-AzhkuIsf1K1w8ZVKKb_T6Pwy3N8JPak,10200
62
- g4f/Provider/needs_auth/Azure.py,sha256=XPgC4ffl8AitnzQB4K77v2eyXm_rLznz1VUiHIeRqaA,6107
60
+ g4f/Provider/needs_auth/Azure.py,sha256=q8Un1ouuG57-9WDyHnRdMfhTjfYM8ky0_8SIYOIkPck,6109
63
61
  g4f/Provider/needs_auth/BingCreateImages.py,sha256=_GrPx7KuttKrQ4IKX0fFwDPVsiCW9xTLycULNClZ6KQ,2066
64
62
  g4f/Provider/needs_auth/BlackboxPro.py,sha256=BA-eiaVGczyB7BHQ47jehq6dTkEslithw1nN2whceZo,270382
65
63
  g4f/Provider/needs_auth/CablyAI.py,sha256=113DsXPXAq3DO2QPHPJ0UJwMpXxomb7BM07RrcV6YQk,1253
66
64
  g4f/Provider/needs_auth/Cerebras.py,sha256=WcPgAVW9QRqoDJ9-KgWYDl08buEVRl1pWiwy7giAGlE,1753
67
- g4f/Provider/needs_auth/Claude.py,sha256=Mg9_g3S60WU-po4C1rYwLJuRPU9wq-9U0qp_hg8oufA,1398
65
+ g4f/Provider/needs_auth/Claude.py,sha256=Xk6kXNhygZ62DbTuDNDo2K8qD_8CZMqc_IwCQBuaoR0,1400
68
66
  g4f/Provider/needs_auth/Cohere.py,sha256=NhHzZrYD2uLxFtEsBKR1FR6UywT3hhie5jIh2Ppvbrg,5851
69
67
  g4f/Provider/needs_auth/CopilotAccount.py,sha256=VCjfUzlPZxU-SduLy3V-YFrUN_KOLZoT2b-OZmZD4rw,394
70
68
  g4f/Provider/needs_auth/Custom.py,sha256=bD6ao9IkIG_oW7bYAHCSEZrMEnDX9u2X96q9LkCMbWc,347
@@ -73,19 +71,19 @@ g4f/Provider/needs_auth/DeepSeekAPI.py,sha256=UaN4W5b_1nXsyB5MN81NzHkQLz1V6DOzte
73
71
  g4f/Provider/needs_auth/FenayAI.py,sha256=2NkDnhdEVDMD1Xk13rp78nRt7oGwbgsX0jlonWt-Ym8,519
74
72
  g4f/Provider/needs_auth/Gemini.py,sha256=p0idmlvIbjg3kMEGsOv5N9b-j5b-blNT2yxO2xnAmiQ,22853
75
73
  g4f/Provider/needs_auth/GeminiCLI.py,sha256=BNQv_Hqzu3E0GtVRbZDZdcq--ZHpJDdLRF5ssG4yX9o,23554
76
- g4f/Provider/needs_auth/GeminiPro.py,sha256=zZBvjc7N313aqp08s8OyNb0kZDn_YTpVdFvSo2GuqD8,767
74
+ g4f/Provider/needs_auth/GeminiPro.py,sha256=Q2G8tuZEZQOUSE-m3PoIs5NMyXuOO55784kXk8rFdsM,755
77
75
  g4f/Provider/needs_auth/GigaChat.py,sha256=2IqQlav_-g-4ZUMJ0tO8KGpvchg9_0ap6rzGP_5dLRI,6325
78
76
  g4f/Provider/needs_auth/GithubCopilot.py,sha256=AUoapqX6lwgC5WPIpKdKdhXcuXo4Stq7I0W5bVHMEbI,6093
79
77
  g4f/Provider/needs_auth/GithubCopilotAPI.py,sha256=O-8Bq6eWrVZTYFS5QufMDJ9SiSFsd0Pz91yejb6spOI,353
80
78
  g4f/Provider/needs_auth/GlhfChat.py,sha256=qSPKXnQ7igjF6_kiBnFhwq-YAqGmpZg_yu4OMRliSP4,1189
81
79
  g4f/Provider/needs_auth/Grok.py,sha256=3uwt0vjsSNHLZKS2DcUHTztiNqPIemwW82E2x0AQRTw,12884
82
- g4f/Provider/needs_auth/Groq.py,sha256=30XyhPJYnZG-_t_gZx8vkLmJK-sYyvUE03edEJjbPbE,548
80
+ g4f/Provider/needs_auth/Groq.py,sha256=zErhEO8-oo4dOZY1u2uz0Xcxb3N76epEBhbuiOjYbx0,594
83
81
  g4f/Provider/needs_auth/LMArena.py,sha256=dKwHEgdvEY9ctjNZzi-COdV8RHqV_z7qEoKiLGGhxus,121399
84
82
  g4f/Provider/needs_auth/MetaAI.py,sha256=Bz9pvJUVH7RtCAP1Huvay-EgO057fL362mhx3GtVAqM,10653
85
83
  g4f/Provider/needs_auth/MetaAIAccount.py,sha256=D4LnhAt2MuKx1a4uSgX2lUbQrzAkeIYm8JCnZieZiak,672
86
84
  g4f/Provider/needs_auth/MicrosoftDesigner.py,sha256=4sJdjBPgiW9TEh4CeplCTNPXv6ZtZtFh0SYAiVfnrqk,7178
87
- g4f/Provider/needs_auth/Nvidia.py,sha256=URMUSrLxUAA9YpI_DsdgQ3QlaOJ0JYRSClmYbTwXWM4,391
88
- g4f/Provider/needs_auth/OpenRouter.py,sha256=KxALUf-mdoHHdYxEU53Rviyw7DRooFz39UMupDl-9V8,850
85
+ g4f/Provider/needs_auth/Nvidia.py,sha256=RcVVSpTZM9VCMhLZ4z3g6j9yxHPQETeWQJb24SPfaII,439
86
+ g4f/Provider/needs_auth/OpenRouter.py,sha256=KKWKKrzw2BGscd05m3BVw21TOHWRMkFETzOPQ1o5YNA,900
89
87
  g4f/Provider/needs_auth/OpenaiAPI.py,sha256=KpJ6qvUlFsFqpQbcwikNYLVsFx6K9h393Pu-yf_uS3g,362
90
88
  g4f/Provider/needs_auth/OpenaiAccount.py,sha256=vSe6Je-DoNbdGGEE-gNaW0Sa81rL8FPMaNyYr1U4PcI,209
91
89
  g4f/Provider/needs_auth/OpenaiChat.py,sha256=dVSm-HAzlvb3KZqZNf0XYFxvIBTfXajKQfe8-JuO-vM,67285
@@ -148,7 +146,7 @@ g4f/api/stubs.py,sha256=9xoJDAmRyUtL_jlqhVI8Q8G-gF9zTse46iNBGEYvD9s,4252
148
146
  g4f/cli/__init__.py,sha256=GUr_JFjLQDmDHmEyafHPvjaPYCQ5oeJclSazoEfRcxA,9167
149
147
  g4f/cli/__main__.py,sha256=SYf3GG2ZCYygnpZ25muxdmT40Dfbx-J0ionEAnfBxuI,94
150
148
  g4f/cli/client.py,sha256=azUTVzdOtfwj8Z6twhODzBQsONiwBBGq90h61Wu_PyY,11859
151
- g4f/client/__init__.py,sha256=dji7F8Xx5YdzsDVR9Yc3LKQQPooLxkh72RZkoyvkOok,35516
149
+ g4f/client/__init__.py,sha256=Cx6-yGyo4rkeIJq6Q5ECA0GINkvdbXhIkkr0cpUQk-4,35501
152
150
  g4f/client/helper.py,sha256=SwdS7I7CkWBG05INo04eMuukLRPUTkX4awgMHGPLpPI,1852
153
151
  g4f/client/models.py,sha256=H8wu1UMFu8oCw7jy6x5B46GjPJPUa35h42Pq87Dp6lI,2318
154
152
  g4f/client/service.py,sha256=lElAhe0y0ZzllkKjX2CBzy_kqgkwYw8P9fZWHivgWqw,5330
@@ -161,7 +159,7 @@ g4f/gui/webview.py,sha256=OcXnxG41h4RFSAQqq7pUBUv2UL3rbLlU3_RsOydabgs,1338
161
159
  g4f/gui/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
162
160
  g4f/gui/server/api.py,sha256=a0Yt2r-JpL6roGEZUCGKO20v1x8dY2jQ6uqCVWzd4tA,16641
163
161
  g4f/gui/server/app.py,sha256=HwJeICAyMkrSQdrUiW2DhZGwe_l40Qb21dF_HiTgMv0,92
164
- g4f/gui/server/backend_api.py,sha256=ZQv3OOtvGstky5mcOYcdH-yI1J7feJqQSBHvTDIZGrI,29960
162
+ g4f/gui/server/backend_api.py,sha256=cfgwrKaEA-efmKKt3Lm8uVfngSzHDCKgpC4ZeRFHthk,29976
165
163
  g4f/gui/server/config.py,sha256=RCpWrnnfJ5Lmq63r6_rz2cxicCadLUgCxoRFZUgUs3s,15354
166
164
  g4f/gui/server/crypto.py,sha256=6irNKqjlKfwnk4MY3TOhFlBWM2mZ6zjZaDmnanub_rc,2786
167
165
  g4f/gui/server/internet.py,sha256=NSItPuwLwIegcUmfjw5SBOtBQroNQp7osd2pCNFGx6g,109
@@ -185,8 +183,8 @@ g4f/locals/models.py,sha256=9uzKnQwLBjtjGU6G3f13jAY5Y9aI2PD4hTi4_bFMqa4,1674
185
183
  g4f/locals/provider.py,sha256=2Wk2PKzB005kYjHEABF_5a4j3qgxxIzgKMK28ewi3sg,2858
186
184
  g4f/mcp/__init__.py,sha256=9S6ReeCAJIiWTpkLRvrk1KwfEsT1GKO7_chQoue0J5Q,573
187
185
  g4f/mcp/__main__.py,sha256=zmnb03P5of2g8IZymmrRrYpMo6kKgQWD-0QemIcguOc,184
188
- g4f/mcp/server.py,sha256=8JOumIkT-GEY89LFnsgYTMpmnlZUYIo958qnbBsrPaI,11580
189
- g4f/mcp/tools.py,sha256=H_DSRryz2zM7G_XOnXw0tUc-8ExJ9ZaAkSGIxO5UpTw,15348
186
+ g4f/mcp/server.py,sha256=zZq4moxcp0sI2w3t1kt3JAC529yfbClchgWK4Y0V7Rw,16900
187
+ g4f/mcp/tools.py,sha256=G09YfVemVsPbkMLIr5iiE0jHvIUj2Of8Rc02dUaHzzU,15309
190
188
  g4f/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
191
189
  g4f/providers/any_model_map.py,sha256=Go7AaJUyvI3NFA5AwjzT7sL8Suk7B0OhtqpzHb512g0,230475
192
190
  g4f/providers/any_provider.py,sha256=EnK9VvBGEUGEe66QsDXEaxL7tvrnNea1ZKukbnMX2uc,19250
@@ -210,9 +208,9 @@ g4f/tools/files.py,sha256=bUTbeNp8ujTZQiW3GLTtFcgl3-1AnH3cDyIKHfh6Mjc,23397
210
208
  g4f/tools/media.py,sha256=AE9hGVRxQBVZzQ_Ylzeoo2TJUGXSBXO5RbLwj1I2ZTE,4701
211
209
  g4f/tools/run_tools.py,sha256=Yb0osWdDt4wUnkvwln4R6qcLapdddC3n2giZMPXWkzM,16662
212
210
  g4f/tools/web_search.py,sha256=vAZJD-qy15llsgAbkXzoEltqdFB6TlKLbqDJ1DS-6vs,2086
213
- g4f-6.9.8.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
214
- g4f-6.9.8.dist-info/METADATA,sha256=MeXnOddLJO79rAsc_cNZWoW0GgQMNdRqPXTp9sIqaIA,23255
215
- g4f-6.9.8.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
216
- g4f-6.9.8.dist-info/entry_points.txt,sha256=J7Usl6dNjXJlvuzGAUEm6cuDXWpVdGq7SfK-tPoiZSI,67
217
- g4f-6.9.8.dist-info/top_level.txt,sha256=bMRlTupWYCcLWy80AnnKZkhpBsXsF8gI3BaMhSZSgRo,4
218
- g4f-6.9.8.dist-info/RECORD,,
211
+ g4f-6.9.10.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
212
+ g4f-6.9.10.dist-info/METADATA,sha256=sblmkzCLywyAXtzAFD9ORBJ4QAb4sGt6jylU28Vpa6I,23345
213
+ g4f-6.9.10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
214
+ g4f-6.9.10.dist-info/entry_points.txt,sha256=J7Usl6dNjXJlvuzGAUEm6cuDXWpVdGq7SfK-tPoiZSI,67
215
+ g4f-6.9.10.dist-info/top_level.txt,sha256=bMRlTupWYCcLWy80AnnKZkhpBsXsF8gI3BaMhSZSgRo,4
216
+ g4f-6.9.10.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
g4f/Provider/Startnest.py DELETED
@@ -1,215 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from aiohttp import ClientSession
4
- import json
5
- import time
6
- import hashlib
7
-
8
- from ..typing import AsyncResult, Messages, MediaListType
9
- from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
10
- from .helper import format_prompt
11
- from ..tools.media import merge_media
12
- from ..image import to_data_uri
13
- from ..providers.response import FinishReason
14
-
15
-
16
- class Startnest(AsyncGeneratorProvider, ProviderModelMixin):
17
- label = "Startnest"
18
- url = "https://play.google.com/store/apps/details?id=starnest.aitype.aikeyboard.chatbot.chatgpt"
19
- api_endpoint = "https://api.startnest.uk/api/completions/stream"
20
-
21
- working = False
22
- needs_auth = False
23
- supports_stream = True
24
- supports_system_message = True
25
- supports_message_history = True
26
-
27
- default_model = 'gpt-4o-mini'
28
- models = [default_model]
29
- vision_models = models
30
-
31
- @classmethod
32
- def generate_signature(cls, timestamp: int) -> str:
33
- """
34
- Generate signature for authorization header
35
- You may need to adjust this based on the actual signature algorithm
36
- """
37
- # This is a placeholder - the actual signature generation might involve:
38
- # - A secret key
39
- # - Specific string formatting
40
- # - Different hash input
41
-
42
- # Example implementation (adjust as needed):
43
- kid = "36ccfe00-78fc-4cab-9c5b-5460b0c78513"
44
- algorithm = "sha256"
45
- validity = 90
46
- user_id = ""
47
-
48
- # The actual signature generation logic needs to be determined
49
- # This is just a placeholder that creates a hash from timestamp
50
- signature_input = f"{kid}{timestamp}{validity}".encode()
51
- signature_value = hashlib.sha256(signature_input).hexdigest()
52
-
53
- return f"Signature kid={kid}&algorithm={algorithm}&timestamp={timestamp}&validity={validity}&userId={user_id}&value={signature_value}"
54
-
55
- @classmethod
56
- async def create_async_generator(
57
- cls,
58
- model: str,
59
- messages: Messages,
60
- proxy: str = None,
61
- media: MediaListType = None,
62
- stream: bool = True,
63
- max_tokens: int = None,
64
- **kwargs
65
- ) -> AsyncResult:
66
- model = cls.get_model(model)
67
-
68
- # Generate current timestamp
69
- timestamp = int(time.time())
70
-
71
- headers = {
72
- "Accept-Encoding": "gzip",
73
- "app_name": "AIKEYBOARD",
74
- "Authorization": cls.generate_signature(timestamp),
75
- "Connection": "Keep-Alive",
76
- "Content-Type": "application/json; charset=UTF-8",
77
- "Host": "api.startnest.uk",
78
- "User-Agent": "okhttp/4.9.0",
79
- }
80
-
81
- async with ClientSession() as session:
82
- # Merge media with messages
83
- media = list(merge_media(media, messages))
84
-
85
- # Convert messages to the required format
86
- formatted_messages = []
87
- for i, msg in enumerate(messages):
88
- if isinstance(msg, dict):
89
- role = msg.get("role", "user")
90
- content = msg.get("content", "")
91
-
92
- # Create content array
93
- content_array = []
94
-
95
- # Add images if this is the last user message and media exists
96
- if media and role == "user" and i == len(messages) - 1:
97
- for image, _ in media:
98
- image_data_uri = to_data_uri(image)
99
- content_array.append({
100
- "image_url": {
101
- "url": image_data_uri
102
- },
103
- "type": "image_url"
104
- })
105
-
106
- # Add text content
107
- if content:
108
- content_array.append({
109
- "text": content,
110
- "type": "text"
111
- })
112
-
113
- formatted_messages.append({
114
- "role": role,
115
- "content": content_array
116
- })
117
-
118
- # If only one message and no media, use format_prompt as requested
119
- if len(messages) == 1 and not media:
120
- prompt_text = format_prompt(messages)
121
- formatted_messages = [{
122
- "role": "user",
123
- "content": [{"text": prompt_text, "type": "text"}]
124
- }]
125
-
126
- data = {
127
- "isVip": True,
128
- "max_tokens": max_tokens,
129
- "messages": formatted_messages,
130
- "stream": stream
131
- }
132
-
133
- # Add advanceToolType if media is present
134
- if media:
135
- data["advanceToolType"] = "upload_and_ask"
136
-
137
- async with session.post(cls.api_endpoint, json=data, headers=headers, proxy=proxy) as response:
138
- response.raise_for_status()
139
-
140
- if stream:
141
- # Handle streaming response (SSE format)
142
- async for line in response.content:
143
- if line:
144
- line = line.decode('utf-8').strip()
145
- if line.startswith("data: "):
146
- data_str = line[6:]
147
- if data_str == "[DONE]":
148
- break
149
- try:
150
- json_data = json.loads(data_str)
151
- if "choices" in json_data and len(json_data["choices"]) > 0:
152
- choice = json_data["choices"][0]
153
-
154
- # Handle content
155
- delta = choice.get("delta", {})
156
- content = delta.get("content", "")
157
- if content:
158
- yield content
159
-
160
- # Handle finish_reason
161
- if "finish_reason" in choice and choice["finish_reason"] is not None:
162
- yield FinishReason(choice["finish_reason"])
163
- break
164
-
165
- except json.JSONDecodeError:
166
- continue
167
- else:
168
- # Handle non-streaming response (regular JSON)
169
- response_text = await response.text()
170
- try:
171
- json_data = json.loads(response_text)
172
- if "choices" in json_data and len(json_data["choices"]) > 0:
173
- choice = json_data["choices"][0]
174
- if "message" in choice and "content" in choice["message"]:
175
- content = choice["message"]["content"]
176
- if content:
177
- yield content.strip()
178
-
179
- # Handle finish_reason for non-streaming
180
- if "finish_reason" in choice and choice["finish_reason"] is not None:
181
- yield FinishReason(choice["finish_reason"])
182
- return
183
-
184
- except json.JSONDecodeError:
185
- # If it's still SSE format even when stream=False, handle it
186
- lines = response_text.strip().split('\n')
187
- full_content = []
188
- finish_reason_value = None
189
-
190
- for line in lines:
191
- if line.startswith("data: "):
192
- data_str = line[6:]
193
- if data_str == "[DONE]":
194
- break
195
- try:
196
- json_data = json.loads(data_str)
197
- if "choices" in json_data and len(json_data["choices"]) > 0:
198
- choice = json_data["choices"][0]
199
- delta = choice.get("delta", {})
200
- content = delta.get("content", "")
201
- if content:
202
- full_content.append(content)
203
-
204
- # Store finish_reason
205
- if "finish_reason" in choice and choice["finish_reason"] is not None:
206
- finish_reason_value = choice["finish_reason"]
207
-
208
- except json.JSONDecodeError:
209
- continue
210
-
211
- if full_content:
212
- yield ''.join(full_content)
213
-
214
- if finish_reason_value:
215
- yield FinishReason(finish_reason_value)
@@ -1,31 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import secrets
4
- import string
5
-
6
- from .template import OpenaiTemplate
7
-
8
- class StringableInference(OpenaiTemplate):
9
- label = "Stringable Inference"
10
- url = "https://stringable-inference.onrender.com"
11
- base_url = "https://stringableinf.com/api"
12
- api_endpoint = "https://stringableinf.com/api/v1/chat/completions"
13
-
14
- working = False
15
- active_by_default = True
16
- default_model = "deepseek-v3.2"
17
- default_vision_model = "gpt-oss-120b"
18
-
19
- @classmethod
20
- def get_headers(cls, stream: bool, api_key: str = None, headers: dict = None) -> dict:
21
- return {
22
- "Accept": "text/event-stream" if stream else "application/json",
23
- "Content-Type": "application/json",
24
- "HTTP-Referer": "https://g4f.dev/",
25
- "X-Title": "G4F Python",
26
- **(
27
- {"Authorization": f"Bearer {api_key}"}
28
- if api_key else {}
29
- ),
30
- **({} if headers is None else headers)
31
- }