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

XFEstudio/gpt4free

Updates

3810e9a7
H Lohaus <heiner.loh789@gmail.com>
提交于

代码差异

5 个文件 +139 -45
Modified g4f/Provider/github/GithubCopilot.py +15 -11
@@ -14,7 +14,6 @@ from .copilotTokenProvider import CopilotTokenProvider, EDITOR_VERSION, EDITOR_P
14 14 from .sharedTokenManager import TokenManagerError, SharedTokenManager
15 15 from .oauthFlow import launch_browser_for_oauth
16 16
17
18 17 class GithubCopilot(OpenaiTemplate):
19 18 """
20 19 GitHub Copilot provider with OAuth authentication.
@@ -113,8 +112,8 @@ class GithubCopilot(OpenaiTemplate):
113 112 cls,
114 113 model: str,
115 114 messages: Messages,
116 api_key: str = None,
117 base_url: str = None,
115 api_key: Optional[str] = None,
116 base_url: Optional[str] = None,
118 117 **kwargs
119 118 ) -> AsyncResult:
120 119 """
@@ -129,13 +128,18 @@ class GithubCopilot(OpenaiTemplate):
129 128 token_provider = cls._get_token_provider()
130 129 creds = await token_provider.get_valid_token()
131 130 api_key = creds.get("token")
131 if not api_key:
132 raise RuntimeError(
133 "GitHub Copilot OAuth not configured. "
134 "Please run 'g4f auth github-copilot' to authenticate."
135 )
132 136 if not base_url:
133 137 base_url = creds.get("endpoint", cls.base_url)
134 138 except TokenManagerError as e:
135 139 if "login" in str(e).lower() or "credentials" in str(e).lower():
136 140 raise RuntimeError(
137 141 "GitHub Copilot OAuth not configured. "
138 "Please run 'g4f-github-copilot login' to authenticate."
142 "Please run 'g4f auth github-copilot' to authenticate."
139 143 ) from e
140 144 raise
141 145
@@ -150,7 +154,7 @@ class GithubCopilot(OpenaiTemplate):
150 154 yield chunk
151 155
152 156 @classmethod
153 def get_models(cls, api_key = None, base_url = None, timeout = None):
157 def get_models(cls, api_key: Optional[str] = None, base_url: Optional[str] = None, timeout: Optional[int] = None):
154 158 # If no API key provided, use OAuth token
155 159 if api_key is None:
156 160 try:
@@ -164,24 +168,24 @@ class GithubCopilot(OpenaiTemplate):
164 168 if "login" in str(e).lower() or "credentials" in str(e).lower():
165 169 raise RuntimeError(
166 170 "GitHub Copilot OAuth not configured. "
167 "Please run 'g4f-github-copilot login' to authenticate."
171 "Please run 'g4f auth github-copilot' to authenticate."
168 172 ) from e
169 173 raise
170 174 return super().get_models(api_key, base_url, timeout)
171 175
172 176 @classmethod
173 def get_headers(cls, stream: bool, api_key: str = None, headers: dict = None) -> dict:
174 headers = super().get_headers(stream, api_key, headers)
177 def get_headers(cls, stream: bool, api_key: str | None = None, headers: dict[str, str] | None = None) -> dict[str, str]:
178 headers_result = super().get_headers(stream, api_key or "", headers or {})
175 179 # Add required Copilot headers
176 copilot_headers = {
180 copilot_headers: dict[str, str] = {
177 181 "Editor-Version": EDITOR_VERSION,
178 182 "Editor-Plugin-Version": EDITOR_PLUGIN_VERSION,
179 183 "Openai-Organization": "github-copilot",
180 184 "Copilot-Integration-Id": "vscode-chat",
181 185 "X-GitHub-Api-Version": "2024-12-15",
182 186 }
183 if headers:
184 copilot_headers.update(headers)
187 if headers_result:
188 copilot_headers.update(headers_result)
185 189 return copilot_headers
186 190
187 191 @classmethod
Modified g4f/Provider/needs_auth/Antigravity.py +75 -28
@@ -34,6 +34,7 @@ from ...errors import MissingAuthError
34 34 from ...image.copy_images import save_response_media
35 35 from ...image import to_bytes, is_data_an_media
36 36 from ...providers.response import Usage, ImageResponse, ToolCalls, Reasoning
37 from ...providers.asyncio import get_running_loop
37 38 from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin
38 39 from ..helper import get_connector, get_system_prompt, format_media_prompt
39 40 from ... import debug
@@ -650,7 +651,57 @@ class AntigravityAuthManager(AuthFileMixin):
650 651 except Exception as e:
651 652 debug.log(f"Project discovery failed at {base_url}: {e}")
652 653 continue
653
654 # If discovery failed, attempt to onboard a managed project for the user.
655 # Read optional configuration from environment
656 attempts = int(os.environ.get("ANTIGRAVITY_ONBOARD_ATTEMPTS", "10"))
657 delay_seconds = float(os.environ.get("ANTIGRAVITY_ONBOARD_DELAY_S", "5"))
658 tier_id = os.environ.get("ANTIGRAVITY_TIER_ID", "free-tier")
659 # Use any preconfigured project id as metadata if available
660 configured_project = os.environ.get("ANTIGRAVITY_PROJECT_ID", "")
661
662 if tier_id:
663 onboard_request_body = {"tierId": tier_id, "metadata": {}}
664 if configured_project:
665 # include requested project id in metadata
666 onboard_request_body["metadata"]["cloudaicompanionProject"] = configured_project
667
668 # Try onboarding across endpoints with retries
669 for base_url in BASE_URLS:
670 for attempt in range(attempts):
671 try:
672 url = f"{base_url}:onboardUser"
673 onboard_headers = {
674 "Authorization": f"Bearer {access_token}",
675 "Content-Type": "application/json",
676 **ANTIGRAVITY_HEADERS,
677 }
678 async with session.post(url, headers=onboard_headers, json=onboard_request_body, timeout=timeout) as resp:
679 if not resp.ok:
680 print(f"Onboarding attempt {attempt+1} at {base_url} failed with status {resp.status}")
681 print(await resp.text())
682 # Stop attempts on this endpoint and try next base_url
683 break
684
685 payload = await resp.json()
686 # payload.response?.cloudaicompanionProject?.id
687 response_obj = payload.get("response") or {}
688 managed = response_obj.get("cloudaicompanionProject")
689 if isinstance(managed, dict):
690 managed_id = managed.get("id")
691 else:
692 managed_id = None
693
694 done = bool(payload.get("done", False))
695 if done and managed_id:
696 return managed_id
697 if done and configured_project:
698 return configured_project
699 except Exception as e:
700 debug.log(f"Failed to onboard managed project at {base_url}: {e}")
701 break
702
703 await asyncio.sleep(delay_seconds)
704
654 705 return ""
655 706
656 707 @classmethod
@@ -857,24 +908,18 @@ class AntigravityProvider:
857 908
858 909 # Fall back to API discovery
859 910 try:
860 load_response = await self.auth_manager.call_endpoint(
861 "loadCodeAssist",
862 {
863 "cloudaicompanionProject": "default-project",
864 "metadata": {"duetProject": "default-project"},
865 },
866 use_auth_headers=True,
867 )
868
869 # Handle both string and object formats for cloudaicompanionProject
870 project = load_response.get("cloudaicompanionProject")
871 if isinstance(project, dict):
872 project = project.get("id")
911 access_token = self.auth_manager.get_access_token()
912 if not access_token:
913 raise RuntimeError("No valid access token available for project discovery")
873 914
915 async with aiohttp.ClientSession() as session:
916 project = await self.auth_manager._fetch_project_id(
917 session=session,
918 access_token=access_token
919 )
874 920 if project:
875 921 self._project_id = project
876 922 return project
877
878 923 raise RuntimeError(
879 924 "Project ID discovery failed - set ANTIGRAVITY_PROJECT_ID in environment."
880 925 )
@@ -1142,6 +1187,17 @@ class AntigravityProvider:
1142 1187
1143 1188 async with ClientSession(headers=headers, timeout=timeout, connector=connector) as session:
1144 1189 async with session.post(url, json=req_body) as resp:
1190 if not resp.ok:
1191 if resp.status == 503:
1192 try:
1193 max_retry_delay = int(max([d.get("retryDelay", 0) for d in (await resp.json(content_type=None)).get("error", {}).get("details", [])]))
1194 except ValueError:
1195 max_retry_delay = 30 # Default retry delay if not specified
1196 debug.log(f"Received 503 error, retrying after {max_retry_delay}")
1197 await asyncio.sleep(max_retry_delay)
1198 resp = await session.post(url, json=req_body)
1199 if not resp.ok:
1200 debug.error(f"Retry after 503 failed with status {resp.status}")
1145 1201 if not resp.ok:
1146 1202 if resp.status == 401:
1147 1203 raise MissingAuthError("Unauthorized (401) from Antigravity API")
@@ -1220,14 +1276,13 @@ class Antigravity(AsyncGeneratorProvider, ProviderModelMixin):
1220 1276 login_url = "https://cloud.google.com/code-assist"
1221 1277 url = "https://antigravity.google"
1222 1278
1223 default_model = "gemini-3-pro-preview"
1279 default_model = "gemini-3-flash"
1224 1280 fallback_models = [
1225 1281 # Gemini 2.5 models
1226 1282 "gemini-2.5-pro",
1227 1283 "gemini-2.5-flash",
1228 1284 "gemini-2.5-flash-lite",
1229 1285 # Gemini 3 models
1230 "gemini-3-pro-preview",
1231 1286 "gemini-3-flash",
1232 1287 # Claude models (via Antigravity proxy)
1233 1288 "claude-sonnet-4.5",
@@ -1247,7 +1302,6 @@ class Antigravity(AsyncGeneratorProvider, ProviderModelMixin):
1247 1302 active_by_default = True
1248 1303
1249 1304 auth_manager: AntigravityAuthManager = None
1250 _dynamic_models: List[str] = None
1251 1305
1252 1306 @classmethod
1253 1307 def get_models(cls, **kwargs) -> List[str]:
@@ -1255,16 +1309,9 @@ class Antigravity(AsyncGeneratorProvider, ProviderModelMixin):
1255 1309 # Try to fetch models dynamically if we have credentials
1256 1310 if not cls.models and cls.has_credentials():
1257 1311 try:
1258 import asyncio
1259 cls.models = asyncio.get_event_loop().run_until_complete(
1260 cls._fetch_models()
1261 )
1262 except RuntimeError:
1263 # No event loop running, try creating one
1264 try:
1265 cls.models = asyncio.run(cls._fetch_models())
1266 except Exception as e:
1267 debug.log(f"Failed to fetch dynamic models: {e}")
1312 # Try to reuse current loop, fallback to asyncio.run when required
1313 get_running_loop(check_nested=True)
1314 cls.models = asyncio.run(cls._fetch_models())
1268 1315 except Exception as e:
1269 1316 debug.log(f"Failed to fetch dynamic models: {e}")
1270 1317
Modified g4f/Provider/needs_auth/GeminiCLI.py +39 -3
@@ -9,6 +9,7 @@ import hashlib
9 9 import asyncio
10 10 import webbrowser
11 11 import threading
12 import platform
12 13 from pathlib import Path
13 14 from typing import Any, AsyncGenerator, Dict, List, Optional, Union, Tuple
14 15 from urllib.parse import urlencode, parse_qs, urlparse
@@ -22,6 +23,7 @@ from ...errors import MissingAuthError
22 23 from ...image.copy_images import save_response_media
23 24 from ...image import to_bytes, is_data_an_media
24 25 from ...providers.response import Usage, ImageResponse, ToolCalls, Reasoning
26 from ...providers.asyncio import get_running_loop
25 27 from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin
26 28 from ..helper import get_connector, get_system_prompt, format_media_prompt
27 29 from ... import debug
@@ -56,6 +58,24 @@ def encode_oauth_state(verifier: str) -> str:
56 58 return base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip('=')
57 59
58 60
61 def get_gemini_cli_user_agent(model: str = "gemini-2.5-pro") -> str:
62 """Generate Gemini CLI user-agent string with platform and architecture.
63
64 This user-agent is required to access CLI quota buckets instead of Antigravity buckets.
65 """
66 system_platform = platform.system().lower()
67 if system_platform == "darwin":
68 system_platform = "macos"
69
70 arch = platform.machine().lower()
71 if arch == "x86_64":
72 arch = "x64"
73 elif arch == "arm64" or arch == "aarch64":
74 arch = "arm64"
75
76 return f"GeminiCLI/1.0.0/{model} ({system_platform}; {arch})"
77
78
59 79 def decode_oauth_state(state: str) -> Dict[str, str]:
60 80 """Decode OAuth state parameter back to verifier."""
61 81 padded = state + '=' * (4 - len(state) % 4) if len(state) % 4 else state
@@ -369,11 +389,23 @@ class AuthManager(AuthFileMixin):
369 389 return self._access_token
370 390 return None
371 391
372 async def call_endpoint(self, method: str, body: Dict[str, Any], is_retry=False) -> Any:
392 async def call_endpoint(
393 self,
394 method: str,
395 body: Dict[str, Any],
396 is_retry=False,
397 user_agent: Optional[str] = None,
398 ) -> Any:
373 399 """
374 400 Call Google Code Assist API endpoint with JSON body.
375 401
376 402 Automatically retries once on 401 Unauthorized by refreshing auth.
403
404 Args:
405 method: API method name
406 body: Request body
407 is_retry: Whether this is a retry attempt
408 user_agent: Custom user-agent string (defaults to standard one)
377 409 """
378 410 if not self.get_access_token():
379 411 await self.initialize_auth()
@@ -383,6 +415,8 @@ class AuthManager(AuthFileMixin):
383 415 "Content-Type": "application/json",
384 416 "Authorization": f"Bearer {self.get_access_token()}",
385 417 }
418 if user_agent:
419 headers["User-Agent"] = user_agent
386 420
387 421 async with aiohttp.ClientSession() as session:
388 422 async with session.post(url, headers=headers, json=body) as resp:
@@ -390,7 +424,9 @@ class AuthManager(AuthFileMixin):
390 424 # Token likely expired, clear and retry once
391 425 await self.clear_token_cache()
392 426 await self.initialize_auth()
393 return await self.call_endpoint(method, body, is_retry=True)
427 return await self.call_endpoint(
428 method, body, is_retry=True, user_agent=user_agent
429 )
394 430 elif not resp.ok:
395 431 text = await resp.text()
396 432 raise RuntimeError(f"API call failed with status {resp.status}: {text}")
@@ -840,7 +876,7 @@ class GeminiCLI(AsyncGeneratorProvider, ProviderModelMixin):
840 876 needs_auth = True
841 877 active_by_default = True
842 878
843 auth_manager: AuthManager = None
879 auth_manager: Optional[AuthManager] = None
844 880
845 881 @classmethod
846 882 def get_models(cls, **kwargs):
Modified g4f/cli/client.py +9 -2
@@ -10,7 +10,7 @@ import requests
10 10
11 11 from pathlib import Path
12 12 from typing import Optional, List, Dict
13 from g4f.client import AsyncClient
13 from g4f.client import AsyncClient, ClientFactory
14 14 from g4f.providers.response import JsonConversation, MediaResponse, is_content
15 15 from g4f.cookies import set_cookies_dir, read_cookie_files
16 16 from g4f.Provider import ProviderUtils
@@ -253,7 +253,14 @@ async def run_args(input_val, args):
253 253 set_cookies_dir(str(args.cookies_dir))
254 254 read_cookie_files()
255 255
256 client = AsyncClient(provider=conv.provider)
256 client = ClientFactory.create_async_client(provider=conv.provider)
257
258 if input_val == "models":
259 models = client.models.get_all()
260 print("\nAvailable models:")
261 for m in models:
262 print(f"- {m}")
263 return
257 264
258 265 if isinstance(args.edit, Path):
259 266 file_to_edit = args.edit
Modified g4f/client/__init__.py +1 -1
@@ -929,7 +929,7 @@ class ClientFactory:
929 929 )
930 930 """
931 931 return AsyncClient(
932 provider=cls.create_provider(provider, base_url, api_key, **kwargs),
932 provider=cls.create_provider(None, provider, base_url, api_key, **kwargs),
933 933 media_provider=media_provider,
934 934 api_key=api_key,
935 935 base_url=base_url,