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

XFEstudio/gpt4free

Move providers to pa providers

bf804659
hlohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

14 个文件 +415 -1574
Deleted g4f/Provider/EasyChat.py +0 -112
@@ -1,112 +0,0 @@
1 from __future__ import annotations
2
3 import json
4 import base64
5 import hashlib
6 from aiohttp import ClientSession
7
8 from ..typing import AsyncResult, Messages
9 from ..config import DEFAULT_MODEL
10 from ..providers.base_provider import ProviderModelMixin
11 from .template import OpenaiTemplate
12 from .. import debug
13
14 class EasyChat(OpenaiTemplate, ProviderModelMixin):
15 url = "https://chat3.eqing.tech"
16 base_url = f"{url}/api/openai/v1"
17 api_endpoint = f"{base_url}/chat/completions"
18 working = True
19 active_by_default = True
20 use_model_names = True
21
22 default_model = DEFAULT_MODEL.split("/")[-1]
23 model_aliases = {
24 DEFAULT_MODEL: f"{default_model}-free",
25 }
26
27 captchaToken: str = None
28
29 @classmethod
30 def get_models(cls, **kwargs) -> list[str]:
31 if not cls.models:
32 models = super().get_models(**kwargs)
33 models = {m.replace("-free", ""): m for m in models if m.endswith("-free")}
34 cls.model_aliases.update(models)
35 cls.models = list(models)
36 return cls.models
37
38 @classmethod
39 async def _solve_altcha(cls, proxy: str = None) -> str:
40 debug.log("EasyChat: Solving Altcha...")
41 async with ClientSession() as session:
42 async with session.get(f"{cls.url}/api/altcaptcha/challenge", proxy=proxy, headers={
43 "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36",
44 "Accept": "application/json"
45 }) as response:
46 response.raise_for_status()
47 data = await response.json()
48
49 salt = data['salt']
50 challenge = data['challenge']
51 maxnumber = data['maxnumber']
52 algorithm = data['algorithm']
53 signature = data['signature']
54
55 for n in range(maxnumber + 1):
56 text = f"{salt}{n}".encode('utf-8')
57 if algorithm == "SHA-512":
58 h = hashlib.sha512(text).hexdigest()
59 elif algorithm == "SHA-256":
60 h = hashlib.sha256(text).hexdigest()
61 else:
62 raise ValueError(f"Unknown Altcha algorithm: {algorithm}")
63
64 if h == challenge:
65 payload = {
66 "algorithm": algorithm,
67 "challenge": challenge,
68 "number": n,
69 "salt": salt,
70 "signature": signature
71 }
72 token = base64.b64encode(json.dumps(payload).encode()).decode()
73 debug.log(f"EasyChat: Altcha solved (n={n})")
74 return token
75 raise ValueError("Failed to solve Altcha")
76
77 @classmethod
78 async def create_async_generator(
79 cls,
80 model: str,
81 messages: Messages,
82 stream: bool = True,
83 proxy: str = None,
84 extra_body: dict = None,
85 **kwargs
86 ) -> AsyncResult:
87 model = cls.get_model(model.replace("-free", ""))
88
89 # Always solve Altcha fresh to avoid expiration
90 cls.captchaToken = await cls._solve_altcha(proxy=proxy)
91
92 if extra_body is None:
93 extra_body = {}
94 extra_body["captchaToken"] = cls.captchaToken
95
96 try:
97 last_chunk = None
98 async for chunk in super().create_async_generator(
99 model=model,
100 messages=messages,
101 stream=stream,
102 extra_body=extra_body,
103 proxy=proxy,
104 **kwargs
105 ):
106 # Remove provided by
107 if last_chunk == "\n" and chunk == "\n":
108 break
109 last_chunk = chunk
110 yield chunk
111 except Exception as e:
112 raise e
Deleted g4f/Provider/Felo.py +0 -150
@@ -1,150 +0,0 @@
1 from __future__ import annotations
2
3 import json
4 import uuid
5 import re
6
7 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
8 from ..typing import AsyncResult, Messages
9 from ..requests.raise_for_status import raise_for_status
10 from ..requests import StreamSession
11
12 class Felo(AsyncGeneratorProvider, ProviderModelMixin):
13 """
14 Provider for felo.ai.
15 """
16 label = "Felo"
17 url = "https://felo.ai"
18 working = True
19 needs_auth = False
20 supports_stream = True
21 supports_system_message = False
22 supports_message_history = False
23
24 default_model = "felo-chat"
25
26 # Mapping of g4f model names to Felo categories
27 model_aliases = {
28 "felo-chat": "chat",
29 "felo-search": "google",
30 "felo-scholar": "scholar",
31 "felo-social": "social",
32 "felo-document": "document"
33 }
34
35 models = list(model_aliases.keys())
36
37 @classmethod
38 async def create_async_generator(
39 cls,
40 model: str,
41 messages: Messages,
42 proxy: str = None,
43 **kwargs
44 ) -> AsyncResult:
45 prompt = messages[-1]["content"]
46 search_uuid = str(uuid.uuid4())
47
48 # Determine the category based on the requested model
49 category = cls.model_aliases.get(model, "chat")
50
51 headers = {
52 "Accept": "*/*",
53 "Content-Type": "application/json",
54 "Origin": cls.url,
55 "Referer": f"{cls.url}/search?q=hello",
56 "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"
57 }
58
59 payload = {
60 "query": prompt,
61 "search_uuid": search_uuid,
62 "lang": "",
63 "agent_lang": "en",
64 "search_options": {"langcode": "en-US"},
65 "search_video": True,
66 "query_from": "default",
67 "category": category,
68 "model": "",
69 "auto_routing": True,
70 "mode": "concise",
71 "device_id": str(uuid.uuid4().hex),
72 "source_message_rid": "",
73 "documents": [],
74 "document_action": "",
75 "slides_source": {"type": "ask_question", "files": {}},
76 "slide_template_uid": "",
77 "selected_resource_ids": [],
78 "process_id": search_uuid,
79 "stream_protocol": "message_center_v1",
80 "enable_task_state": True
81 }
82
83 async with StreamSession(
84 headers=headers,
85 impersonate="chrome",
86 proxy=proxy,
87 timeout=120
88 ) as session:
89 # 1. Start the thread and get the stream key
90 threads_url = f"{cls.url}/api-proxy/main/search/threads"
91 async with session.post(threads_url, json=payload) as response:
92 await raise_for_status(response)
93 try:
94 res_json = await response.json()
95 except Exception:
96 raise RuntimeError(f"Failed to parse JSON response from {threads_url}")
97
98 stream_key = res_json.get("stream_key")
99 if not stream_key:
100 raise RuntimeError("Failed to get stream_key from Felo response")
101
102 # 2. Connect to the SSE stream
103 stream_url = f"{cls.url}/api/message/v1/stream/{stream_key}?offset=0"
104 async with session.get(stream_url) as stream_res:
105 await raise_for_status(stream_res)
106
107 previous_text = ""
108 sources_to_yield = None
109 async for line in stream_res.iter_lines():
110 line = line.decode("utf-8").strip()
111 if line.startswith("data:{"):
112 try:
113 # Safely load the JSON inside the data block
114 data = json.loads(line[5:])
115 if "content" in data:
116 content_str = data["content"]
117 if isinstance(content_str, str):
118 content_json = json.loads(content_str)
119
120 # Handle answer types
121 if content_json.get("data", {}).get("type") == "answer":
122 text = content_json.get("data", {}).get("data", {}).get("text", "")
123
124 if text.startswith(previous_text):
125 # Yield only the new part of the text
126 new_part = text[len(previous_text):]
127 if new_part:
128 yield new_part
129 previous_text = text
130 else:
131 # If it doesn't strictly start with previous text, yield the whole text and update
132 yield text
133 previous_text = text
134
135 # Handle sources for Web UI formatting
136 elif content_json.get("data", {}).get("type") == "final_contexts":
137 sources_list = content_json.get("data", {}).get("data", {}).get("sources", [])
138 if sources_list:
139 # format to match g4f standard sources
140 formatted_sources = [{"url": s.get("link"), "title": s.get("title")} for s in sources_list if s.get("link")]
141 if formatted_sources:
142 from ..providers.response import Sources
143 sources_to_yield = Sources(formatted_sources)
144 except Exception:
145 # Ignore malformed JSON or other parsing errors in the stream
146 continue
147
148 # Yield sources at the very end so they appear at the bottom of the response
149 if sources_to_yield:
150 yield sources_to_yield
Deleted g4f/Provider/GptFree.py +0 -135
@@ -1,135 +0,0 @@
1 import json
2 from aiohttp import ClientSession
3 from typing import AsyncGenerator
4
5 from ..typing import AsyncResult, Messages
6 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
7 from ..image import to_data_uri
8
9 class GptFree(AsyncGeneratorProvider, ProviderModelMixin):
10 url = "https://gptfree.com"
11 api_endpoint = "https://us-central1-gptfree-2.cloudfunctions.net/agent_stream"
12 working = True
13 supports_message_history = True
14 supports_system_message = False
15
16 default_model = ""
17
18 _id_token: str = None
19
20 @classmethod
21 async def create_async_generator(
22 cls,
23 model: str,
24 messages: Messages,
25 proxy: str = None,
26 **kwargs
27 ) -> AsyncGenerator:
28 headers = {
29 "accept": "*/*",
30 "accept-language": "en-US,en;q=0.9",
31 "cache-control": "no-cache",
32 "content-type": "application/json",
33 "origin": "https://gptfree.com",
34 "pragma": "no-cache",
35 "referer": "https://gptfree.com/",
36 "sec-ch-ua": '"Not;A=Brand";v="8", "Chromium";v="150", "Google Chrome";v="150"',
37 "sec-ch-ua-mobile": "?0",
38 "sec-ch-ua-platform": '"Linux"',
39 "sec-fetch-dest": "empty",
40 "sec-fetch-mode": "cors",
41 "sec-fetch-site": "cross-site",
42 "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36"
43 }
44
45 history = []
46 current_message = ""
47 images = []
48
49 for msg in messages:
50 content = msg["content"]
51
52 # Handle multimodal messages (where content is a list of dicts)
53 if isinstance(content, list):
54 text_parts = []
55 for part in content:
56 if part.get("type") == "text":
57 text_parts.append(part.get("text", ""))
58 elif part.get("type") == "image_url":
59 img_url = part.get("image_url", {}).get("url")
60 if img_url:
61 images.append(img_url)
62 content = "\n".join(text_parts)
63
64 if msg["role"] == "system":
65 history.append({"type": "user", "content": content})
66 elif msg["role"] == "user":
67 current_message = content
68 history.append({"type": "user", "content": content})
69 elif msg["role"] == "assistant":
70 history.append({"type": "agent", "content": content})
71
72 # Process images from kwargs
73 if "image" in kwargs and kwargs["image"]:
74 images.append(to_data_uri(kwargs["image"]))
75 if "images" in kwargs and kwargs["images"]:
76 for img in kwargs["images"]:
77 images.append(to_data_uri(img[0] if isinstance(img, tuple) else img))
78 if "media" in kwargs and kwargs["media"]:
79 for m in kwargs["media"]:
80 images.append(to_data_uri(m[0]))
81
82 # The last message is the current one
83 if history and history[-1]["type"] == "user":
84 current_message = history.pop()["content"]
85
86 if not current_message.strip():
87 if history and history[-1]["type"] == "user":
88 current_message = history.pop()["content"]
89 else:
90 current_message = "Analyze this image" if images else "Hello"
91
92 payload = {
93 "message": current_message,
94 "images": images,
95 "history": history
96 }
97 firebase_api_key = "AIzaSyBdU-Np8RSh1tPSsPOWg3qIm6PnVK5PQb4"
98
99 async with ClientSession() as session:
100 for attempt in range(2):
101 if not cls._id_token:
102 auth_url = f"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key={firebase_api_key}"
103 async with session.post(auth_url, json={"returnSecureToken": True}, proxy=proxy) as auth_resp:
104 auth_resp.raise_for_status()
105 auth_data = await auth_resp.json()
106 cls._id_token = auth_data["idToken"]
107
108 headers["Authorization"] = f"Bearer {cls._id_token}"
109
110 async with session.post(
111 cls.api_endpoint,
112 headers=headers,
113 json=payload,
114 proxy=proxy
115 ) as response:
116 if response.status == 401:
117 # Token expired or invalid, clear it and retry
118 cls._id_token = None
119 continue
120
121 response.raise_for_status()
122
123 async for line in response.content:
124 line = line.decode('utf-8').strip()
125 if line.startswith('data: '):
126 data_str = line[6:]
127 if data_str == '{}':
128 continue
129 try:
130 data = json.loads(data_str)
131 if "response" in data:
132 yield data["response"]
133 except json.JSONDecodeError:
134 pass
135 break
Deleted g4f/Provider/GradientNetwork.py +0 -107
@@ -1,107 +0,0 @@
1 from __future__ import annotations
2
3 import json
4
5 from ..typing import AsyncResult, Messages
6 from ..providers.response import Reasoning, JsonResponse
7 from ..requests import StreamSession
8 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
9
10
11 class GradientNetwork(AsyncGeneratorProvider, ProviderModelMixin):
12 """
13 Provider for chat.gradient.network
14 Supports streaming text generation with Qwen and GPT OSS models.
15 """
16 label = "Gradient Network"
17 url = "https://chat.gradient.network"
18 api_endpoint = "https://chat.gradient.network/api/generate"
19
20 working = False
21 needs_auth = False
22 supports_stream = True
23 supports_system_message = True
24 supports_message_history = True
25
26 default_model = "GPT OSS 120B"
27 models = [
28 default_model,
29 "Qwen3 235B",
30 ]
31 model_aliases = {
32 "qwen-3-235b": "Qwen3 235B",
33 "qwen3-235b": "Qwen3 235B",
34 "gpt-oss-120b": "GPT OSS 120B",
35 }
36
37 @classmethod
38 async def create_async_generator(
39 cls,
40 model: str,
41 messages: Messages,
42 proxy: str = None,
43 enable_thinking: bool = True,
44 **kwargs
45 ) -> AsyncResult:
46 """
47 Create an async generator for streaming chat responses.
48
49 Args:
50 model: The model name to use
51 messages: List of message dictionaries
52 proxy: Optional proxy URL
53 enable_thinking: Enable the thinking/analysis channel (maps to enableThinking in API)
54 **kwargs: Additional arguments
55
56 Yields:
57 str: Content chunks from the response
58 Reasoning: Reasoning content when enable_thinking is True
59 """
60 model = cls.get_model(model)
61
62 headers = {
63 "Accept": "application/x-ndjson",
64 "Content-Type": "application/json",
65 "Origin": cls.url,
66 "Referer": f"{cls.url}/",
67 }
68
69 payload = {
70 "clusterMode": "nvidia" if "GPT OSS" in model else "hybrid",
71 "model": model,
72 "messages": messages,
73 }
74 if enable_thinking:
75 payload["enableThinking"] = enable_thinking
76 async with StreamSession(headers=headers, proxy=proxy, impersonate="chrome") as session:
77 async with session.post(
78 cls.api_endpoint,
79 json=payload,
80 ) as response:
81 response.raise_for_status()
82
83 async for line in response.iter_lines():
84 if not line:
85 continue
86
87 try:
88 data = json.loads(line)
89 yield JsonResponse.from_dict(data)
90 msg_type = data.get("type")
91
92 if msg_type == "reply":
93 # Response chunks with content or reasoningContent
94 reply_data = data.get("data", {})
95 content = reply_data.get("content")
96 reasoning_content = reply_data.get("reasoningContent")
97
98 if reasoning_content:
99 yield Reasoning(reasoning_content)
100 if content:
101 yield content
102
103 # Skip clusterInfo and blockUpdate GPU visualization messages
104
105 except json.JSONDecodeError:
106 # Skip non-JSON lines (may be partial data or empty)
107 raise
Deleted g4f/Provider/Miklium.py +0 -59
@@ -1,59 +0,0 @@
1 from __future__ import annotations
2
3 from typing import Any
4
5 from ..typing import AsyncResult, Messages
6 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
7 from .helper import format_prompt
8 from ..requests import StreamSession
9
10 class Miklium(AsyncGeneratorProvider, ProviderModelMixin):
11 label = "Miklium"
12 url = "https://miklium.vercel.app"
13 api_endpoint = "/api/chatbot"
14
15 working = True
16 needs_auth = False
17 supports_stream = False
18 supports_system_message = True
19 supports_message_history = True
20
21 default_model = 'miklium'
22 models = ['miklium', 'personalityless', 'male', 'female', 'all']
23
24 @classmethod
25 async def create_async_generator(
26 cls,
27 model: str,
28 messages: Messages,
29 proxy: str | None = None,
30 **kwargs: Any
31 ) -> AsyncResult:
32 model = cls.get_model(model)
33
34 headers = {
35 "accept": "*/*",
36 "accept-language": "en-US,en;q=0.9",
37 "content-type": "application/json",
38 "origin": cls.url,
39 "referer": f"{cls.url}/"
40 }
41
42 prompt = format_prompt(messages)
43
44 data_payload = {
45 "message": prompt,
46 "response_stacking": kwargs.get("response_stacking", 4),
47 "personality": model
48 }
49
50 async with StreamSession(headers=headers, impersonate="chrome") as session:
51 async with session.post(f"{cls.url}{cls.api_endpoint}", json=data_payload, proxy=proxy) as response:
52 response.raise_for_status()
53 json_data = await response.json()
54
55 if str(json_data.get("success")).lower() == "true":
56 yield json_data.get("response", "")
57 else:
58 error_msg = json_data.get("error", "Unknown error")
59 raise RuntimeError(f"Miklium error: {error_msg}")
Modified g4f/Provider/OperaAria.py +0 -6
@@ -72,12 +72,6 @@ class OperaAria(AsyncGeneratorProvider, ProviderModelMixin):
72 72 _user_agent_v1 = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Mobile Safari/537.36 OPR/89.0.0.0"
73 73 _user_agent_v2 = "Mozilla/5.0 (Linux; U; Android 14; Pixel 8 Pro Build/UQ1A.240205.004; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/138.0.7204.179 Mobile Safari/537.36 OPR/99.0.2254.81922"
74 74
75 @classmethod
76 def get_model(cls, model: str) -> str:
77 if not model:
78 return cls.default_model
79 return cls.model_aliases.get(model, model if model in cls.models else cls.default_model)
80
81 75 @classmethod
82 76 def _get_api_version(cls, model: str) -> str:
83 77 return cls._model_to_version.get(model, 'v2')
Deleted g4f/Provider/Perchance.py +0 -237
@@ -1,237 +0,0 @@
1 from __future__ import annotations
2
3 import asyncio
4 import random
5 import json
6 from aiohttp import ClientSession
7 from g4f.requests.cdp import CDPSession
8 from ..typing import AsyncResult, Messages
9 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
10 from .helper import format_prompt, format_media_prompt
11 from ..requests import StreamSession
12 from ..providers.response import ImageResponse
13
14 class Perchance(AsyncGeneratorProvider, ProviderModelMixin):
15 url = "https://perchance.org/ai-chat"
16 api_endpoint = "https://text-generation.perchance.org/api/generate"
17 image_api_endpoint = "https://image-generation.perchance.org/api/generate"
18 verify_url = "https://text-generation.perchance.org/embed?thread=0"
19 working = True
20 supports_stream = True
21 supports_system_message = True
22 supports_message_history = True
23 default_model = "perchance"
24 default_image_model = "perchance-image"
25 image_models = ["perchance-image"]
26 models = [default_model] + image_models
27
28 # Class-level cache for credentials to avoid restarting browser for every request
29 _user_key: str | None = None
30 _cookies: dict | None = None
31 _headers: dict | None = None
32
33 _image_user_key: str | None = None
34 _image_cookies: dict | None = None
35 _image_headers: dict | None = None
36
37 @classmethod
38 async def _get_user_key(cls, is_image: bool = False, proxy: str = None) -> tuple[str, dict, dict]:
39 """Runs CDP to solve Turnstile and extract the userKey and session cookies."""
40 session = CDPSession(headless=False)
41 await session.start()
42 try:
43 if is_image:
44 import urllib.parse
45 hash_data = {"prompt": "a", "resolution": "512x512", "guidanceScale": 7, "channel": "ai-text-to-image-generator", "subChannel": "public", "seed": -1, "requestId": "a"}
46 hash_str = urllib.parse.quote(json.dumps(hash_data))
47 verify_url = f"https://image-generation.perchance.org/embed#{hash_str}"
48 origin = "https://image-generation.perchance.org"
49 else:
50 verify_url = cls.verify_url
51 origin = "https://text-generation.perchance.org"
52
53 await session.navigate(verify_url)
54
55 # Setup message capture in page context
56 setup_js = """
57 window.collectedMessages = [];
58 window.addEventListener("message", (e) => {
59 if (e.data && (e.data.type.startsWith("stream") || e.data.type === "verified")) {
60 window.collectedMessages.push(e.data);
61 }
62 });
63 """
64 await session.evaluate_js(setup_js)
65
66 # Always trigger verifyUser to ensure Turnstile solving/checking is executed
67 if is_image:
68 verify_js = "if(typeof start === 'function') start({reloadPageOnFail: false});"
69 else:
70 verify_js = 'window.postMessage({type: "verifyUser"}, window.location.origin);'
71 await session.evaluate_js(verify_js)
72
73 # Anti-detect: warm up the browser and disable debugger overhead DURING Turnstile check
74 await session.bypass_turnstile()
75
76 # Poll for userKey-0
77 user_key = None
78 for _ in range(30):
79 user_key = await session.evaluate_js("localStorage.getItem('userKey-0')")
80 if user_key:
81 break
82 await asyncio.sleep(1)
83
84 if not user_key:
85 raise RuntimeError("Failed to verify user/solve Turnstile on Perchance.")
86
87 cookies = await session.get_cookies()
88 user_agent = await session.get_user_agent()
89
90 headers = {
91 "user-agent": user_agent,
92 "Accept": "*/*",
93 "Origin": origin,
94 "Referer": verify_url,
95 }
96
97 return user_key, cookies, headers
98 finally:
99 await session.close()
100
101 @classmethod
102 async def get_ad_access_code(cls, proxy: str = None) -> str:
103 cache_bust = random.randint(1000000, 9999999)
104 url = f"https://perchance.org/api/getAccessCodeForAdPoweredStuff?__cacheBust={cache_bust}"
105 async with ClientSession() as session:
106 async with session.get(url, proxy=proxy) as response:
107 if response.status == 200:
108 return await response.text()
109 return ""
110
111 @classmethod
112 async def create_async_generator(
113 cls,
114 model: str,
115 messages: Messages,
116 prompt: str = None,
117 proxy: str = None,
118 **kwargs
119 ) -> AsyncResult:
120 # Check if we need to fetch credentials (cold start)
121 if not cls._user_key or not cls._cookies or not cls._headers:
122 cls._user_key, cls._cookies, cls._headers = await cls._get_user_key(proxy=proxy)
123
124 # Image Generation Flow
125 if model in cls.image_models or kwargs.get("image"):
126 if not cls._image_user_key or not cls._image_cookies or not cls._image_headers:
127 cls._image_user_key, cls._image_cookies, cls._image_headers = await cls._get_user_key(is_image=True, proxy=proxy)
128
129 if not prompt:
130 prompt = format_media_prompt(messages)
131
132 ad_access_code = await cls.get_ad_access_code(proxy=proxy)
133 req_id = random.random()
134
135 payload = {
136 "prompt": prompt,
137 "negativePrompt": "",
138 "seed": -1,
139 "resolution": "512x512",
140 "guidanceScale": 7,
141 "channel": "ai-text-to-image-generator",
142 "subChannel": "public",
143 "userKey": cls._image_user_key,
144 "adAccessCode": ad_access_code,
145 "requestId": str(req_id)
146 }
147
148 gen_url = f"{cls.image_api_endpoint}?userKey={cls._image_user_key}&requestId={req_id}&adAccessCode={ad_access_code}&__cacheBust={random.random()}&bdf={random.random()}"
149
150 headers = cls._image_headers.copy()
151 headers["Content-Type"] = "text/plain;charset=UTF-8"
152
153 async with StreamSession(
154 headers=headers,
155 cookies=cls._image_cookies,
156 proxies={"all": proxy} if proxy else None,
157 impersonate="chrome"
158 ) as session:
159 async with session.post(gen_url, data=json.dumps(payload), proxy=proxy) as response:
160 if response.status in (401, 403):
161 cls._image_user_key = cls._image_cookies = cls._image_headers = None
162 raise RuntimeError("auth_failed")
163 if response.status != 200:
164 text = await response.text()
165 if "invalid_key" in text or "failed_verification" in text:
166 cls._image_user_key = cls._image_cookies = cls._image_headers = None
167 raise RuntimeError("auth_failed")
168 raise RuntimeError(f"image generation failed: {response.status} {text}")
169
170 data = await response.json()
171 if data.get("status") == "success":
172 image_url = data.get("imageDownloadUrl")
173 if image_url:
174 if not image_url.startswith("http"):
175 image_url = "https://image-generation.perchance.org" + image_url
176 yield ImageResponse(image_url, prompt)
177 return
178 raise RuntimeError(f"Failed to generate image: {data}")
179 return
180
181 # Text Generation Flow
182 instruction = format_prompt(messages)
183 instruction_token_count = max(1, len(instruction) // 4)
184
185 payload = {
186 "instruction": instruction,
187 "startWith": "",
188 "stopSequences": ["\n\n", "\nAnon:", "\nBot:"],
189 "generatorName": "ai-chat",
190 "startWithTokenCount": 0,
191 "instructionTokenCount": instruction_token_count
192 }
193
194 try:
195 async with StreamSession(
196 headers=cls._headers,
197 cookies=cls._cookies,
198 proxies={"all": proxy} if proxy else None,
199 impersonate="chrome"
200 ) as session:
201 req_id = f"aiTextCompletion{random.random()}"
202 gen_url = f"{cls.api_endpoint}?userKey={cls._user_key}&thread=0&requestId={req_id}&__cacheBust={random.random()}"
203
204 async with session.post(gen_url, json=payload, proxy=proxy) as response:
205 # If Cloudflare blocks or token has expired, raise auth_failed to trigger a refresh
206 if response.status in (401, 403):
207 raise RuntimeError("auth_failed")
208
209 if response.status != 200:
210 text = await response.text()
211 if "invalid_key" in text or "failed_verification" in text:
212 raise RuntimeError("auth_failed")
213 raise RuntimeError(f"generate failed: {response.status} {text}")
214
215 # Parse custom SSE format
216 async for chunk_bytes in response.iter_content():
217 if chunk_bytes:
218 chunk_str = chunk_bytes.decode(errors="ignore")
219 for line in chunk_str.split("\n"):
220 line = line.strip()
221 if line.startswith('t:'):
222 try:
223 # Parse the text chunk safely via JSON loads (handles escapes)
224 text_val = json.loads(line[2:])
225 if text_val:
226 yield text_val
227 except Exception:
228 pass
229
230 except RuntimeError as e:
231 if str(e) == "auth_failed":
232 # Clear cached credentials and retry once with fresh verification
233 cls._user_key = cls._cookies = cls._headers = None
234 async for chunk in cls.create_async_generator(model, messages, prompt, proxy, **kwargs):
235 yield chunk
236 else:
237 raise e
Deleted g4f/Provider/Surfsense.py +0 -73
@@ -1,73 +0,0 @@
1 from __future__ import annotations
2
3 import json
4 from typing import Any
5
6 from ..typing import AsyncResult, Messages
7 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
8 from ..requests import StreamSession
9
10 class Surfsense(AsyncGeneratorProvider, ProviderModelMixin):
11 label = "Surfsense"
12 url = "https://www.surfsense.com"
13 api_endpoint = "https://api.surfsense.com/api/v1/public/anon-chat/stream"
14
15 working = True
16 needs_auth = False
17 supports_stream = True
18 supports_system_message = True
19 supports_message_history = True
20
21 default_model = 'gpt-o4-mini-no-login'
22 models = ['gpt-o4-mini-no-login', 'gpt-5.4-mini-no-login']
23
24 model_aliases = {
25 "o4-mini": "gpt-o4-mini-no-login",
26 "gpt-4o-mini": "gpt-o4-mini-no-login",
27 "gpt-4.5-mini": "gpt-5.4-mini-no-login"
28 }
29
30 @classmethod
31 async def create_async_generator(
32 cls,
33 model: str,
34 messages: Messages,
35 proxy: str | None = None,
36 **kwargs: Any
37 ) -> AsyncResult:
38 model = cls.get_model(model)
39
40 headers = {
41 "accept": "*/*",
42 "accept-language": "en-US,en;q=0.9",
43 "content-type": "application/json",
44 "origin": cls.url,
45 "referer": f"{cls.url}/",
46 "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"
47 }
48
49 data_payload = {
50 "model_slug": model,
51 "messages": messages
52 }
53
54 async with StreamSession(headers=headers, impersonate="safari15_3") as session:
55 async with session.post(cls.api_endpoint, json=data_payload, proxy=proxy) as response:
56 response.raise_for_status()
57
58 async for chunk in response.iter_lines():
59 if not chunk:
60 continue
61 chunk = chunk.decode("utf-8")
62 if chunk.startswith("data: "):
63 chunk = chunk[6:]
64 if chunk == "[DONE]":
65 break
66 try:
67 json_data = json.loads(chunk)
68 if json_data.get("type") == "text-delta":
69 delta = json_data.get("delta")
70 if delta:
71 yield delta
72 except json.JSONDecodeError:
73 pass
Modified g4f/Provider/__init__.py +0 -17
@@ -52,12 +52,8 @@ def _resolve_provider(name: str) -> ProviderType:
52 52 from .DeepInfra import DeepInfra; return DeepInfra
53 53 elif name == "DeepSeek" or name == "DeepSeekAPI":
54 54 from g4f.Provider.needs_auth.DeepSeek import DeepSeek; return DeepSeek
55 elif name == "EasyChat":
56 from g4f.Provider.EasyChat import EasyChat; return EasyChat
57 55 elif name == "EdgeTTS":
58 56 from g4f.Provider.audio.EdgeTTS import EdgeTTS; return EdgeTTS
59 elif name == "Felo":
60 from g4f.Provider.Felo import Felo; return Felo
61 57 elif name == "FenayAI":
62 58 from g4f.Provider.needs_auth.FenayAI import FenayAI; return FenayAI
63 59 elif name == "GLM":
@@ -78,9 +74,6 @@ def _resolve_provider(name: str) -> ProviderType:
78 74 from g4f.Provider.needs_auth.GlhfChat import GlhfChat; return GlhfChat
79 75 elif name == "GoogleSearch":
80 76 from g4f.Provider.search.GoogleSearch import GoogleSearch; return GoogleSearch
81
82 elif name == "GradientNetwork":
83 from .GradientNetwork import GradientNetwork; return GradientNetwork
84 77 elif name == "Grok":
85 78 from g4f.Provider.needs_auth.Grok import Grok; return Grok
86 79 elif name == "Groq":
@@ -111,20 +104,14 @@ def _resolve_provider(name: str) -> ProviderType:
111 104 from g4f.Provider.needs_auth.MetaAIAccount import MetaAIAccount; return MetaAIAccount
112 105 elif name == "MicrosoftDesigner":
113 106 from g4f.Provider.needs_auth.MicrosoftDesigner import MicrosoftDesigner; return MicrosoftDesigner
114 elif name == "Miklium":
115 from g4f.Provider.Miklium import Miklium; return Miklium
116 107 elif name == "MiniMax":
117 108 from g4f.Provider.needs_auth.mini_max.MiniMax import MiniMax; return MiniMax
118 109 elif name == "Nvidia":
119 110 from g4f.Provider.needs_auth.Nvidia import Nvidia; return Nvidia
120 111 elif name == "Ollama":
121 112 from g4f.Provider.local.Ollama import Ollama; return Ollama
122 elif name == "OllamaSwarm":
123 from g4f.Provider.OllamaSwarm import OllamaSwarm; return OllamaSwarm
124 113 elif name == "OpenAIFM":
125 114 from g4f.Provider.audio.OpenAIFM import OpenAIFM; return OpenAIFM
126 elif name == "OllamaSwarm":
127 from g4f.Provider.OllamaSwarm import OllamaSwarm; return OllamaSwarm
128 115 elif name == "OpenRouter":
129 116 from g4f.Provider.needs_auth.OpenRouter import OpenRouter; return OpenRouter
130 117 elif name == "OpenRouterFree":
@@ -139,8 +126,6 @@ def _resolve_provider(name: str) -> ProviderType:
139 126 from g4f.Provider.template.OpenaiTemplate import OpenaiTemplate; return OpenaiTemplate
140 127 elif name == "OperaAria":
141 128 from g4f.Provider.OperaAria import OperaAria; return OperaAria
142 elif name == "Perchance":
143 from g4f.Provider.Perchance import Perchance; return Perchance
144 129 elif name == "Perplexity":
145 130 from g4f.Provider.Perplexity import Perplexity; return Perplexity
146 131 elif name == "PerplexityApi":
@@ -169,8 +154,6 @@ def _resolve_provider(name: str) -> ProviderType:
169 154 from g4f.Provider.search.SearXNG import SearXNG; return SearXNG
170 155 elif name == "StabilityAI_SD35Large":
171 156 from g4f.Provider.hf_space.StabilityAI_SD35Large import StabilityAI_SD35Large; return StabilityAI_SD35Large
172 elif name == "Surfsense":
173 from g4f.Provider.Surfsense import Surfsense; return Surfsense
174 157 elif name == "TeachAnything":
175 158 from g4f.Provider.TeachAnything import TeachAnything; return TeachAnything
176 159 elif name == "ThebApi":
Modified g4f/cli/__init__.py +115 -4
Added g4f/mcp/pa_downloader.py +292 -0
Modified g4f/providers/any_provider.py +7 -4
@@ -10,7 +10,7 @@ from ..providers.retry_provider import RotatedProvider
10 10 from ..providers.config_provider import RouterConfig, ConfigModelProvider
11 11 from ..client.factory import AbstractClientFactory
12 12 from ..Provider import __getattr__
13 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
13 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin, get_async_provider_method
14 14 from .. import Provider
15 15 from .. import models
16 16 from .. import debug
@@ -462,9 +462,12 @@ class AnyProvider(AsyncGeneratorProvider, AnyModelProviderMixin):
462 462 provider, submodel = model.split(":", maxsplit=1)
463 463 if hasattr(Provider, provider):
464 464 provider = getattr(Provider, provider)
465 if provider.working and provider.get_parent() not in ignored:
466 providers.append(provider)
467 model = submodel
465 method = get_async_provider_method(provider)
466 async for chunk in method(
467 submodel, messages, stream=stream, media=media, api_key=api_key, **kwargs
468 ):
469 yield chunk
470 return
468 471 else:
469 472 if model not in cls.model_map:
470 473 if model in cls.model_aliases:
Modified g4f/tools/run_tools.py +1 -1
@@ -75,7 +75,7 @@ def _messages_cache_key(messages: Messages, model: str) -> Optional[str]:
75 75 exclude = {idx for idx in (last_user_idx, last_assistant_idx) if idx is not None}
76 76 if len(exclude) >= len(messages):
77 77 return None
78 parts = [model]
78 parts = [model] if model else []
79 79 for i, msg in enumerate(messages):
80 80 if i in exclude:
81 81 continue