返回提交历史
Modified
g4f/Provider/__init__.py
+6
-0
Modified
g4f/Provider/needs_auth/AIBadgr.py
+1
-0
Modified
g4f/Provider/needs_auth/Anthropic.py
+1
-0
Modified
g4f/Provider/needs_auth/Antigravity.py
+2
-1
Modified
g4f/Provider/needs_auth/Cerebras.py
+1
-1
Modified
g4f/Provider/needs_auth/Puter.py
+1
-0
Modified
g4f/Provider/needs_auth/You.py
+4
-186
Modified
g4f/gui/server/website.py
+10
-10
Modified
g4f/mcp/pa_provider.py
+35
-3
XFEstudio/gpt4free
Update provider urls
94ba86d2
代码差异
9 个文件
+61
-201
@@ -218,6 +218,7 @@ class ProviderLoader:
218
218
None, "default"
219
219
)
220
220
cls.loaded[name].__name__ = name
221
cls.loaded[name].url = "https://g4f.dev"
221
222
cls.loaded[name].active_by_default = True
222
223
return cls.loaded[name]
223
224
elif name == "GLM":
@@ -322,6 +323,7 @@ class ProviderLoader:
322
323
None, "custom:srv_mt1wbaxgf9c946af0c58",
323
324
)
324
325
cls.loaded[name].__name__ = name
326
cls.loaded[name].url = "https://relayrouter.org"
325
327
cls.loaded[name].active_by_default = True
326
328
return cls.loaded[name]
327
329
elif name == "KiloCode":
@@ -330,6 +332,7 @@ class ProviderLoader:
330
332
None, "https://api.kilo.ai/api/gateway"
331
333
)
332
334
cls.loaded[name].__name__ = name
335
cls.loaded[name].url = "https://kilo.ai"
333
336
cls.loaded[name].active_by_default = True
334
337
cls.loaded[name].default_model = "kilo-auto/free"
335
338
return cls.loaded[name]
@@ -339,6 +342,7 @@ class ProviderLoader:
339
342
None, "https://api.llm7.io/v1"
340
343
)
341
344
cls.loaded[name].__name__ = name
345
cls.loaded[name].url = "https://llm7.io"
342
346
cls.loaded[name].active_by_default = True
343
347
cls.loaded[name].default_model = "default"
344
348
cls.loaded[name].models = ["default"]
@@ -358,6 +362,7 @@ class ProviderLoader:
358
362
None, "https://opencode.ai/zen/v1"
359
363
)
360
364
cls.loaded[name].__name__ = name
365
cls.loaded[name].url = "https://opencode.ai"
361
366
cls.loaded[name].active_by_default = True
362
367
cls.loaded[name].default_model = "big-pickle"
363
368
return cls.loaded[name]
@@ -376,6 +381,7 @@ class ProviderLoader:
376
381
None, name.lower()
377
382
)
378
383
cls.loaded[name].__name__ = name
384
cls.loaded[name].url = "https://orcarouter.ai"
379
385
cls.loaded[name].active_by_default = True
380
386
cls.loaded[name].supports_native_tools = True
381
387
return cls.loaded[name]
@@ -6,6 +6,7 @@ from ..template import OpenaiTemplate
6
6
class AIBadgr(OpenaiTemplate):
7
7
label = "AI Badgr"
8
8
url = "https://aibadgr.com"
9
screenshot_url = "https://aibadgr.com";
9
10
login_url = "https://aibadgr.com/api-keys"
10
11
base_url = "https://aibadgr.com/api/v1"
11
12
working = True
@@ -17,6 +17,7 @@ from .OpenaiAPI import OpenaiAPI
17
17
class Anthropic(OpenaiAPI):
18
18
label = "Anthropic API"
19
19
url = "https://console.anthropic.com"
20
screenshot_url = "https://anthropic.com"
20
21
login_url = "https://console.anthropic.com/settings/keys"
21
22
working = True
22
23
base_url = "https://api.anthropic.com/v1"
@@ -1445,8 +1445,9 @@ class Antigravity(AsyncGeneratorProvider, ProviderModelMixin):
1445
1445
"""
1446
1446
1447
1447
label = "Google Antigravity"
1448
login_url = "https://cloud.google.com/code-assist"
1449
1448
url = "https://antigravity.google"
1449
screenshot_url = "https://antigravity.google"
1450
login_url = "https://cloud.google.com/code-assist"
1450
1451
1451
1452
default_model = "gemini-3-flash"
1452
1453
fallback_models = [
@@ -10,7 +10,7 @@ from ...cookies import get_cookies, get_cookies_async
10
10
11
11
class Cerebras(OpenaiAPI):
12
12
label = "Cerebras Inference"
13
url = "https://inference.cerebras.ai/"
13
url = "https://chat.cerebras.ai/"
14
14
login_url = "https://cloud.cerebras.ai"
15
15
base_url = "https://api.cerebras.ai/v1"
16
16
working = True
@@ -17,6 +17,7 @@ from ... import debug
17
17
class Puter(AsyncGeneratorProvider, ProviderModelMixin):
18
18
label = "Puter.js"
19
19
url = "https://docs.puter.com/playground"
20
screenshot_url = "https://puter.com"
20
21
login_url = "https://github.com/HeyPuter/puter-cli"
21
22
models_endpoint = "https://api.puter.com/puterai/chat/models/"
22
23
api_endpoint = "https://api.puter.com/drivers/call"
@@ -1,191 +1,9 @@
1
1
from __future__ import annotations
2
2
3
import re
4
import json
5
import uuid
3
from ..template import OpenaiTemplate
6
4
7
from ...requests.cdp_browser import cdp
8
from ...typing import AsyncResult, Messages, ImageType, Cookies
9
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
10
from ..helper import format_prompt
11
from ...image import MEDIA_TYPE_MAP, to_bytes, is_accepted_format
12
from ...requests import StreamSession, FormData, raise_for_status, get_nodriver
13
from ...providers.response import ImagePreview, ImageResponse
14
from ...cookies import get_cookies
15
from ...errors import MissingRequirementsError, ResponseError
16
from ... import debug
17
18
19
class You(AsyncGeneratorProvider, ProviderModelMixin):
5
class You(OpenaiTemplate):
20
6
label = "You.com"
21
7
url = "https://you.com"
22
working = False
23
needs_auth = True
24
default_model = "gpt-4o-mini"
25
default_vision_model = "agent"
26
image_models = ["dall-e"]
27
models = [
28
default_model,
29
"gpt-4o",
30
"gpt-4o-mini",
31
"gpt-4-turbo",
32
"grok-2",
33
"claude-3.5-sonnet",
34
"claude-3.5-haiku",
35
"claude-3-opus",
36
"claude-3-sonnet",
37
"claude-3-haiku",
38
"llama-3.3-70b",
39
"llama-3.1-70b",
40
"llama-3",
41
"gemini-1-5-flash",
42
"gemini-1-5-pro",
43
"databricks-dbrx-instruct",
44
"command-r",
45
"command-r-plus",
46
"dolphin-2.5",
47
default_vision_model,
48
*image_models,
49
]
50
_cookies = None
51
_cookies_used = 0
52
_telemetry_ids = []
53
54
@classmethod
55
async def create_async_generator(
56
cls,
57
model: str,
58
messages: Messages,
59
stream: bool = True,
60
image: ImageType = None,
61
image_name: str = None,
62
proxy: str = None,
63
timeout: int = 240,
64
chat_mode: str = "default",
65
cookies: Cookies = None,
66
**kwargs,
67
) -> AsyncResult:
68
if image is not None or model == cls.default_vision_model:
69
chat_mode = "agent"
70
elif not model or model == cls.default_model:
71
...
72
elif model.startswith("dall-e"):
73
chat_mode = "create"
74
messages = [messages[-1]]
75
else:
76
chat_mode = "custom"
77
model = cls.get_model(model)
78
if cookies is None and chat_mode != "default":
79
try:
80
cookies = get_cookies(".you.com")
81
except MissingRequirementsError:
82
pass
83
if not cookies or "afUserId" not in cookies:
84
browser, stop_browser = await get_nodriver(proxy=proxy)
85
try:
86
page = await browser.get(cls.url)
87
await page.wait_for(
88
'[data-testid="user-profile-button"]', timeout=900
89
)
90
cookies = {}
91
result = await page.send(
92
cdp.network.get_cookies([cls.url])
93
)
94
for c in result.get("cookies", []):
95
cookies[c["name"]] = c["value"]
96
await page.close()
97
finally:
98
await stop_browser()
99
async with StreamSession(
100
proxy=proxy, impersonate="chrome", timeout=(30, timeout)
101
) as session:
102
upload = ""
103
if image is not None:
104
upload_file = await cls.upload_file(
105
session, cookies, to_bytes(image), image_name
106
)
107
upload = json.dumps([upload_file])
108
headers = {
109
"Accept": "text/event-stream",
110
"Referer": f"{cls.url}/api/streamingSearch",
111
}
112
data = {
113
"userFiles": upload,
114
"q": format_prompt(messages),
115
"domain": "youchat",
116
"selectedChatMode": chat_mode,
117
"conversationTurnId": str(uuid.uuid4()),
118
"chatId": str(uuid.uuid4()),
119
}
120
if chat_mode == "custom":
121
if debug.logging:
122
print(f"You model: {model}")
123
data["selectedAiModel"] = model.replace("-", "_")
124
125
async with session.get(
126
f"{cls.url}/api/streamingSearch",
127
params=data,
128
headers=headers,
129
cookies=cookies,
130
) as response:
131
await raise_for_status(response)
132
async for line in response.iter_lines():
133
if line.startswith(b"event: "):
134
event = line[7:].decode()
135
elif line.startswith(b"data: "):
136
if event == "error":
137
raise ResponseError(line[6:])
138
if event in ["youChatUpdate", "youChatToken"]:
139
data = json.loads(line[6:])
140
if event == "youChatToken" and event in data and data[event]:
141
if data[event].startswith(
142
"#### You've hit your free quota for the Model Agent. For more usage of the Model Agent, learn more at:"
143
):
144
continue
145
yield data[event]
146
elif event == "youChatUpdate" and "t" in data and data["t"]:
147
if chat_mode == "create":
148
match = re.search(r"!\[(.+?)\]\((.+?)\)", data["t"])
149
if match:
150
if match.group(1) == "fig":
151
yield ImagePreview(
152
match.group(2), messages[-1]["content"]
153
)
154
else:
155
yield ImageResponse(
156
match.group(2), match.group(1)
157
)
158
else:
159
yield data["t"]
160
else:
161
yield data["t"]
162
163
@classmethod
164
async def upload_file(
165
cls, client: StreamSession, cookies: Cookies, file: bytes, filename: str = None
166
) -> dict:
167
async with client.get(
168
f"{cls.url}/api/get_nonce",
169
cookies=cookies,
170
) as response:
171
await raise_for_status(response)
172
upload_nonce = await response.text()
173
data = FormData()
174
content_type = is_accepted_format(file)
175
filename = (
176
f"image.{MEDIA_TYPE_MAP[content_type]}" if filename is None else filename
177
)
178
data.add_field("file", file, content_type=content_type, filename=filename)
179
async with client.post(
180
f"{cls.url}/api/upload",
181
data=data,
182
headers={
183
"X-Upload-Nonce": upload_nonce,
184
},
185
cookies=cookies,
186
) as response:
187
await raise_for_status(response)
188
result = await response.json()
189
result["user_filename"] = filename
190
result["size"] = len(file)
191
return result
8
backend_url = "https://g4f.space/api/you.com"
9
working = True
@@ -192,6 +192,8 @@ class Website:
192
192
try:
193
193
provider = ProviderLoader.from_name(name)
194
194
url = getattr(provider, "url", None)
195
screenshot_url = getattr(provider, "screenshot_url", None)
196
login_url = getattr(provider, "login_url", None)
195
197
# Skip model list fetching here — it's slow for 108+ providers.
196
198
# Models are loaded lazily in _provider_detail() for a single provider.
197
199
models = getattr(provider, "models", None)
@@ -211,6 +213,8 @@ class Website:
211
213
providers.append({
212
214
"name": name,
213
215
"url": url,
216
"screenshot_url": screenshot_url,
217
"login_url": login_url,
214
218
"models": models if isinstance(models, list) else list(models) if models else [],
215
219
"needs_auth": needs_auth,
216
220
"working": working,
@@ -249,6 +253,8 @@ class Website:
249
253
<div class="providers-list">
250
254
"""
251
255
for p in providers:
256
if not p["working"]:
257
continue # Skip non-working providers
252
258
models_html = ""
253
259
if p["models"]:
254
260
models_list = ", ".join(p["models"][:5]) if isinstance(p["models"], list) else ""
@@ -361,15 +367,8 @@ class Website:
361
367
"""
362
368
363
369
# Screenshot / logo section
364
screenshot_url = f"{(p.get('url', (p.get('base_url', p.get('baseUrl', '')))) or "").replace('https://', '').replace('http://', '').replace('api.', '').replace('www.', '').replace('console.', '').replace('api.', '').replace('router.', '').split('/')[0]}"
365
mapping_urls = {
366
"airforce": "api.airforce",
367
"openai": "openrouter.ai",
368
}
369
screenshot_url = mapping_urls.get(screenshot_url, screenshot_url or "g4f.dev")
370
if p.get("name", "") == "OperaAria" or p.get("name", "") == "CopilotApp":
371
screenshot_url = p["url"].replace("https://", "")
372
create_url = f"/screenshot?url={quote_plus('https://' + screenshot_url)}"
370
screenshot_url = p.get("screenshot_url") or p.get("url") or ""
371
create_url = f"/screenshot?url={quote_plus(screenshot_url)}"
373
372
logo_url = "https://g4f.space/logo/" + p.get("name", "").replace(
374
373
'MetaAIAccount', 'Facebook AI').replace(
375
374
'MetaAI', 'Facebook AI').replace(
@@ -390,7 +389,7 @@ class Website:
390
389
'groq', 'Groq')
391
390
screenshot_html = f"""
392
391
<div class="screenshot-section">
393
<img src="/screenshot/{quote_plus(screenshot_url)}.webp" data-src="{create_url}" alt="{escape(p['name'])} logo" class="provider-logo"
392
<img src="/screenshot/{quote_plus(screenshot_url.replace('https://', ''))}.webp" data-src="{create_url}" alt="{escape(p['name'])} logo" class="provider-logo"
394
393
style="max-width:100%;border-radius:8px;border:1px solid var(--card-border)" />
395
394
<img src="{logo_url}" alt="{escape(p['name'])} logo" class="provider-logo" style="max-width:100%;border-radius:8px;border:1px solid var(--card-border)" />
396
395
<p class="screenshot-caption">Load screenshot from {escape(p['url'] or 'N/A')}</p>
@@ -420,6 +419,7 @@ class Website:
420
419
}}
421
420
}};
422
421
img.onmouseleave = img.onmouseenter;
422
img.ontouchstart = img.onmouseenter;
423
423
img.onerror = () => {{
424
424
input.placeholder = 'Ask {escape(p.get("label", p["name"]))}';
425
425
if (img.src.includes(createSrc)) {{
@@ -71,11 +71,15 @@ from pathlib import Path
71
71
from typing import Any, Dict, FrozenSet, List, Optional, Tuple, Type
72
72
from .. import debug
73
73
from ..files import secure_filename
74
from ..config import CONFIG_DIR
74
75
75
76
# ---------------------------------------------------------------------------
76
77
# Workspace directory
77
78
# ---------------------------------------------------------------------------
78
79
80
#: Private directory for secret data (outside the shared workspace).
81
SECRET_DIR: Path = CONFIG_DIR / "secret"
82
79
83
80
84
def get_workspace_dir() -> Path:
81
85
"""Return the workspace directory ``~/.g4f/workspace``, creating it if needed."""
@@ -84,6 +88,27 @@ def get_workspace_dir() -> Path:
84
88
return workspace
85
89
86
90
91
def _migrate_secret_dir(old_secret_ws: Path, new_secret_ws: Path) -> None:
92
"""Move any existing secret files from the workspace into the private dir."""
93
if not old_secret_ws.exists():
94
return
95
new_secret_ws.mkdir(parents=True, exist_ok=True)
96
for item in old_secret_ws.iterdir():
97
target = new_secret_ws / item.name
98
if target.exists():
99
continue
100
item.rename(target)
101
# Remove the old empty directory tree
102
try:
103
old_secret_ws.rmdir()
104
# Also remove the now-empty "secret" parent if it's empty
105
old_secret_parent = get_workspace_dir() / "secret"
106
if old_secret_parent.exists() and not any(old_secret_parent.iterdir()):
107
old_secret_parent.rmdir()
108
except OSError:
109
pass # Directory not empty — leave it
110
111
87
112
def get_user_workspace_dir(user_id: str) -> Path:
88
113
"""Return a per-user workspace subdirectory ``~/.g4f/workspace/users/<user_id>``.
89
114
@@ -100,16 +125,23 @@ def get_user_workspace_dir(user_id: str) -> Path:
100
125
101
126
102
127
def get_secret_workspace_dir(user_id: str) -> Path:
103
"""Return a per-user *secret* workspace ``~/.g4f/workspace/secret/<user_id>``.
128
"""Return a per-user *secret* directory ``~/.g4f/secret/<user_id>``.
104
129
105
130
This is used when a workspace secret is provided. Files are saved here
106
131
when the user is logged in; reads fall back to the root workspace when
107
132
the file does not exist in the secret workspace.
133
134
Secrets are stored **outside** the shared workspace (under
135
``~/.g4f/secret/``) so they are never synced or exposed alongside
136
workspace files — similar to how cookies live under ``~/.g4f/cookies/``.
108
137
"""
109
138
if not user_id:
110
139
return get_workspace_dir()
111
140
safe_id = re.sub(r"[^a-zA-Z0-9_\-]+", "_", user_id).strip("_") or "anonymous"
112
secret_workspace = get_workspace_dir() / "secret" / safe_id
141
secret_workspace = SECRET_DIR / safe_id
142
# Migrate any existing data from the old workspace-relative location
143
old_location = get_workspace_dir() / "secret" / safe_id
144
_migrate_secret_dir(old_location, secret_workspace)
113
145
secret_workspace.mkdir(parents=True, exist_ok=True)
114
146
return secret_workspace
115
147
@@ -269,7 +301,7 @@ def get_secret_conversation_dir(user_id: str) -> Path:
269
301
"""Return the directory used to store secret conversations for *user_id*.
270
302
271
303
Conversations are saved as individual JSON files under
272
``~/.g4f/workspace/secret/<user_id>/conversations/``. An index file
304
``~/.g4f/secret/<user_id>/conversations/``. An index file
273
305
``index.json`` lists all stored conversation IDs.
274
306
"""
275
307
secret_ws = get_secret_workspace_dir(user_id)