webscout 8.3.1__py3-none-any.whl → 8.3.2__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.

Potentially problematic release.


This version of webscout might be problematic. Click here for more details.

Files changed (77) hide show
  1. webscout/AIutel.py +46 -53
  2. webscout/Bing_search.py +418 -0
  3. webscout/Extra/gguf.py +706 -177
  4. webscout/Provider/AISEARCH/genspark_search.py +7 -7
  5. webscout/Provider/GeminiProxy.py +140 -0
  6. webscout/Provider/MCPCore.py +78 -75
  7. webscout/Provider/OPENAI/BLACKBOXAI.py +1 -4
  8. webscout/Provider/OPENAI/GeminiProxy.py +328 -0
  9. webscout/Provider/OPENAI/README.md +2 -0
  10. webscout/Provider/OPENAI/README_AUTOPROXY.md +238 -0
  11. webscout/Provider/OPENAI/__init__.py +15 -1
  12. webscout/Provider/OPENAI/autoproxy.py +332 -39
  13. webscout/Provider/OPENAI/base.py +15 -5
  14. webscout/Provider/OPENAI/e2b.py +0 -1
  15. webscout/Provider/OPENAI/mcpcore.py +109 -70
  16. webscout/Provider/OPENAI/scirachat.py +59 -51
  17. webscout/Provider/OPENAI/toolbaz.py +2 -9
  18. webscout/Provider/OPENAI/xenai.py +514 -0
  19. webscout/Provider/OPENAI/yep.py +8 -2
  20. webscout/Provider/TTI/__init__.py +1 -0
  21. webscout/Provider/TTI/bing.py +231 -0
  22. webscout/Provider/TTS/speechma.py +45 -39
  23. webscout/Provider/TogetherAI.py +366 -0
  24. webscout/Provider/XenAI.py +324 -0
  25. webscout/Provider/__init__.py +8 -3
  26. webscout/Provider/deepseek_assistant.py +378 -0
  27. webscout/auth/__init__.py +44 -0
  28. webscout/auth/api_key_manager.py +189 -0
  29. webscout/auth/auth_system.py +100 -0
  30. webscout/auth/config.py +76 -0
  31. webscout/auth/database.py +400 -0
  32. webscout/auth/exceptions.py +67 -0
  33. webscout/auth/middleware.py +248 -0
  34. webscout/auth/models.py +130 -0
  35. webscout/auth/providers.py +257 -0
  36. webscout/auth/rate_limiter.py +254 -0
  37. webscout/auth/request_models.py +127 -0
  38. webscout/auth/request_processing.py +226 -0
  39. webscout/auth/routes.py +526 -0
  40. webscout/auth/schemas.py +103 -0
  41. webscout/auth/server.py +312 -0
  42. webscout/auth/static/favicon.svg +11 -0
  43. webscout/auth/swagger_ui.py +203 -0
  44. webscout/auth/templates/components/authentication.html +237 -0
  45. webscout/auth/templates/components/base.html +103 -0
  46. webscout/auth/templates/components/endpoints.html +750 -0
  47. webscout/auth/templates/components/examples.html +491 -0
  48. webscout/auth/templates/components/footer.html +75 -0
  49. webscout/auth/templates/components/header.html +27 -0
  50. webscout/auth/templates/components/models.html +286 -0
  51. webscout/auth/templates/components/navigation.html +70 -0
  52. webscout/auth/templates/static/api.js +455 -0
  53. webscout/auth/templates/static/icons.js +168 -0
  54. webscout/auth/templates/static/main.js +784 -0
  55. webscout/auth/templates/static/particles.js +201 -0
  56. webscout/auth/templates/static/styles.css +3353 -0
  57. webscout/auth/templates/static/ui.js +374 -0
  58. webscout/auth/templates/swagger_ui.html +170 -0
  59. webscout/client.py +49 -3
  60. webscout/scout/core/scout.py +104 -26
  61. webscout/scout/element.py +139 -18
  62. webscout/swiftcli/core/cli.py +14 -3
  63. webscout/swiftcli/decorators/output.py +59 -9
  64. webscout/update_checker.py +31 -49
  65. webscout/version.py +1 -1
  66. webscout/webscout_search.py +4 -12
  67. webscout/webscout_search_async.py +3 -10
  68. webscout/yep_search.py +2 -11
  69. {webscout-8.3.1.dist-info → webscout-8.3.2.dist-info}/METADATA +41 -11
  70. {webscout-8.3.1.dist-info → webscout-8.3.2.dist-info}/RECORD +74 -36
  71. {webscout-8.3.1.dist-info → webscout-8.3.2.dist-info}/entry_points.txt +1 -1
  72. webscout/Provider/HF_space/__init__.py +0 -0
  73. webscout/Provider/HF_space/qwen_qwen2.py +0 -206
  74. webscout/Provider/OPENAI/api.py +0 -1320
  75. {webscout-8.3.1.dist-info → webscout-8.3.2.dist-info}/WHEEL +0 -0
  76. {webscout-8.3.1.dist-info → webscout-8.3.2.dist-info}/licenses/LICENSE.md +0 -0
  77. {webscout-8.3.1.dist-info → webscout-8.3.2.dist-info}/top_level.txt +0 -0
@@ -1,39 +1,332 @@
1
- # # ProxyFox integration for OpenAI-compatible providers
2
- # # This module provides a singleton proxy pool for all providers
3
-
4
- # import proxyfox
5
-
6
- # def get_auto_proxy(protocol='https', country=None, max_speed_ms=1000):
7
- # """
8
- # Returns a single proxy string (e.g. '11.22.33.44:8080') using proxyfox.
9
- # You can specify protocol, country, and max_speed_ms for filtering.
10
- # """
11
- # kwargs = {'protocol': protocol, 'max_speed_ms': max_speed_ms}
12
- # if country:
13
- # kwargs['country'] = country
14
- # return proxyfox.get_one(**kwargs)
15
-
16
- # # Optionally: pool support for advanced usage
17
- # _pool = None
18
-
19
- # def get_proxy_pool(size=10, refresh_interval=300, protocol='https', max_speed_ms=1000):
20
- # global _pool
21
- # if _pool is None:
22
- # _pool = proxyfox.create_pool(
23
- # size=size,
24
- # refresh_interval=refresh_interval,
25
- # protocol=protocol,
26
- # max_speed_ms=max_speed_ms
27
- # )
28
- # return _pool
29
-
30
- # def get_pool_proxy():
31
- # pool = get_proxy_pool()
32
- # return pool.get()
33
-
34
- # def get_all_pool_proxies():
35
- # pool = get_proxy_pool()
36
- # return pool.all()
37
-
38
- # if __name__ == "__main__":
39
- # print(get_auto_proxy())
1
+ """
2
+ Auto-proxy module for OpenAI-compatible providers.
3
+ This module provides automatic proxy injection for HTTP sessions using a remote proxy list.
4
+ """
5
+
6
+ import random
7
+ import time
8
+ from abc import ABCMeta
9
+ from typing import Dict, List, Optional, Any
10
+ import requests
11
+
12
+ # Optional imports for different HTTP clients
13
+ try:
14
+ import httpx
15
+ except ImportError:
16
+ httpx = None
17
+
18
+ try:
19
+ from curl_cffi.requests import Session as CurlSession
20
+ from curl_cffi.requests import AsyncSession as CurlAsyncSession
21
+ except ImportError:
22
+ CurlSession = None
23
+ CurlAsyncSession = None
24
+
25
+ # Global proxy cache
26
+ _proxy_cache = {
27
+ 'proxies': [],
28
+ 'last_updated': 0,
29
+ 'cache_duration': 300 # 5 minutes
30
+ }
31
+
32
+ PROXY_SOURCE_URL = "http://207.180.209.185:5000/ips.txt"
33
+
34
+
35
+ def fetch_proxies() -> List[str]:
36
+ """
37
+ Fetch proxy list from the remote source.
38
+
39
+ Returns:
40
+ List[str]: List of proxy URLs in format 'http://user:pass@host:port'
41
+ """
42
+ try:
43
+ response = requests.get(PROXY_SOURCE_URL, timeout=10)
44
+ response.raise_for_status()
45
+
46
+ proxies = []
47
+ for line in response.text.strip().split('\n'):
48
+ line = line.strip()
49
+ if line and line.startswith('http://'):
50
+ proxies.append(line)
51
+
52
+ return proxies
53
+
54
+ except Exception:
55
+ return []
56
+
57
+
58
+ def get_cached_proxies() -> List[str]:
59
+ """
60
+ Get proxies from cache or fetch new ones if cache is expired.
61
+
62
+ Returns:
63
+ List[str]: List of proxy URLs
64
+ """
65
+ current_time = time.time()
66
+
67
+ # Check if cache is expired or empty
68
+ if (current_time - _proxy_cache['last_updated'] > _proxy_cache['cache_duration'] or
69
+ not _proxy_cache['proxies']):
70
+
71
+ new_proxies = fetch_proxies()
72
+ if new_proxies:
73
+ _proxy_cache['proxies'] = new_proxies
74
+ _proxy_cache['last_updated'] = current_time
75
+ else:
76
+ pass
77
+
78
+ return _proxy_cache['proxies']
79
+
80
+
81
+ def get_auto_proxy() -> Optional[str]:
82
+ """
83
+ Get a random proxy from the cached proxy list.
84
+
85
+ Returns:
86
+ Optional[str]: A proxy URL or None if no proxies available
87
+ """
88
+ proxies = get_cached_proxies()
89
+ if not proxies:
90
+ return None
91
+
92
+ proxy = random.choice(proxies)
93
+ return proxy
94
+
95
+
96
+ def get_proxy_dict(proxy_url: Optional[str] = None) -> Dict[str, str]:
97
+ """
98
+ Convert a proxy URL to a dictionary format suitable for requests/httpx.
99
+
100
+ Args:
101
+ proxy_url: Proxy URL, if None will get one automatically
102
+
103
+ Returns:
104
+ Dict[str, str]: Proxy dictionary with 'http' and 'https' keys
105
+ """
106
+ if proxy_url is None:
107
+ proxy_url = get_auto_proxy()
108
+
109
+ if proxy_url is None:
110
+ return {}
111
+
112
+ return {
113
+ 'http': proxy_url,
114
+ 'https': proxy_url
115
+ }
116
+
117
+
118
+ def test_proxy(proxy_url: str, timeout: int = 10) -> bool:
119
+ """
120
+ Test if a proxy is working by making a request to a test URL.
121
+
122
+ Args:
123
+ proxy_url: The proxy URL to test
124
+ timeout: Request timeout in seconds
125
+
126
+ Returns:
127
+ bool: True if proxy is working, False otherwise
128
+ """
129
+ try:
130
+ test_url = "https://httpbin.org/ip"
131
+ proxies = {'http': proxy_url, 'https': proxy_url}
132
+
133
+ response = requests.get(test_url, proxies=proxies, timeout=timeout)
134
+ return response.status_code == 200
135
+
136
+ except Exception:
137
+ return False
138
+
139
+
140
+ class ProxyAutoMeta(ABCMeta):
141
+ """
142
+ Metaclass to ensure all OpenAICompatibleProvider subclasses automatically get proxy support.
143
+ This will inject proxies into any requests.Session, httpx.Client, or curl_cffi session attributes found on the instance.
144
+
145
+ To disable automatic proxy injection, set disable_auto_proxy=True in the constructor or
146
+ set the class attribute DISABLE_AUTO_PROXY = True.
147
+ """
148
+
149
+ def __call__(cls, *args, **kwargs):
150
+ instance = super().__call__(*args, **kwargs)
151
+
152
+ # Check if auto proxy is disabled
153
+ disable_auto_proxy = kwargs.get('disable_auto_proxy', False) or getattr(cls, 'DISABLE_AUTO_PROXY', False)
154
+
155
+ # Get proxies from various sources
156
+ proxies = getattr(instance, 'proxies', None) or kwargs.get('proxies', None)
157
+
158
+ if proxies is None and not disable_auto_proxy:
159
+ try:
160
+ proxy_url = get_auto_proxy()
161
+ if proxy_url:
162
+ proxies = get_proxy_dict(proxy_url)
163
+ else:
164
+ proxies = {}
165
+ except Exception:
166
+ proxies = {}
167
+ elif proxies is None:
168
+ proxies = {}
169
+
170
+ instance.proxies = proxies
171
+
172
+ # Patch existing sessions if we have valid proxies
173
+ if proxies:
174
+ _patch_instance_sessions(instance, proxies)
175
+
176
+ # Provide helper methods for creating proxied sessions
177
+ _add_proxy_helpers(instance, proxies)
178
+
179
+ return instance
180
+
181
+
182
+ def _patch_instance_sessions(instance: Any, proxies: Dict[str, str]) -> None:
183
+ """
184
+ Patch existing session objects on the instance with proxy configuration.
185
+
186
+ Args:
187
+ instance: The class instance to patch
188
+ proxies: Proxy dictionary to apply
189
+ """
190
+ for attr_name in dir(instance):
191
+ if attr_name.startswith('_'):
192
+ continue
193
+
194
+ try:
195
+ attr_obj = getattr(instance, attr_name)
196
+
197
+ # Patch requests.Session objects
198
+ if isinstance(attr_obj, requests.Session):
199
+ attr_obj.proxies.update(proxies)
200
+
201
+ # Patch httpx.Client objects
202
+ elif httpx and isinstance(attr_obj, httpx.Client):
203
+ try:
204
+ # httpx uses different proxy format
205
+ attr_obj._proxies = proxies
206
+ except Exception:
207
+ pass
208
+
209
+ # Patch curl_cffi Session objects
210
+ elif CurlSession and isinstance(attr_obj, CurlSession):
211
+ try:
212
+ attr_obj.proxies.update(proxies)
213
+ except Exception:
214
+ pass
215
+
216
+ # Patch curl_cffi AsyncSession objects
217
+ elif CurlAsyncSession and isinstance(attr_obj, CurlAsyncSession):
218
+ try:
219
+ attr_obj.proxies.update(proxies)
220
+ except Exception:
221
+ pass
222
+
223
+ except Exception:
224
+ continue
225
+
226
+
227
+ def _add_proxy_helpers(instance: Any, proxies: Dict[str, str]) -> None:
228
+ """
229
+ Add helper methods to the instance for creating proxied sessions.
230
+
231
+ Args:
232
+ instance: The class instance to add methods to
233
+ proxies: Proxy dictionary to use in helper methods
234
+ """
235
+
236
+ def get_proxied_session():
237
+ """Get a requests.Session with proxies configured"""
238
+ session = requests.Session()
239
+ session.proxies.update(proxies)
240
+ return session
241
+
242
+ def get_proxied_httpx_client(**kwargs):
243
+ """Get an httpx.Client with proxies configured"""
244
+ if httpx:
245
+ return httpx.Client(proxies=proxies, **kwargs)
246
+ else:
247
+ raise ImportError("httpx is not installed")
248
+
249
+ def get_proxied_curl_session(impersonate="chrome120", **kwargs):
250
+ """Get a curl_cffi Session with proxies configured"""
251
+ if CurlSession:
252
+ return CurlSession(proxies=proxies, impersonate=impersonate, **kwargs)
253
+ else:
254
+ raise ImportError("curl_cffi is not installed")
255
+
256
+ def get_proxied_curl_async_session(impersonate="chrome120", **kwargs):
257
+ """Get a curl_cffi AsyncSession with proxies configured"""
258
+ if CurlAsyncSession:
259
+ return CurlAsyncSession(proxies=proxies, impersonate=impersonate, **kwargs)
260
+ else:
261
+ raise ImportError("curl_cffi is not installed")
262
+
263
+ # Add methods to instance
264
+ instance.get_proxied_session = get_proxied_session
265
+ instance.get_proxied_httpx_client = get_proxied_httpx_client
266
+ instance.get_proxied_curl_session = get_proxied_curl_session
267
+ instance.get_proxied_curl_async_session = get_proxied_curl_async_session
268
+
269
+
270
+ def get_working_proxy(max_attempts: int = 5, timeout: int = 10) -> Optional[str]:
271
+ """
272
+ Get a working proxy by testing multiple proxies from the list.
273
+
274
+ Args:
275
+ max_attempts: Maximum number of proxies to test
276
+ timeout: Timeout for each proxy test
277
+
278
+ Returns:
279
+ Optional[str]: A working proxy URL or None if none found
280
+ """
281
+ proxies = get_cached_proxies()
282
+ if not proxies:
283
+ return None
284
+
285
+ # Shuffle to avoid always testing the same proxies first
286
+ test_proxies = random.sample(proxies, min(max_attempts, len(proxies)))
287
+
288
+ for proxy in test_proxies:
289
+ if test_proxy(proxy, timeout):
290
+ return proxy
291
+
292
+ return None
293
+
294
+
295
+ def refresh_proxy_cache() -> int:
296
+ """
297
+ Force refresh the proxy cache.
298
+
299
+ Returns:
300
+ int: Number of proxies loaded
301
+ """
302
+ global _proxy_cache
303
+ _proxy_cache['last_updated'] = 0 # Force refresh
304
+ proxies = get_cached_proxies()
305
+ return len(proxies)
306
+
307
+
308
+ def get_proxy_stats() -> Dict[str, Any]:
309
+ """
310
+ Get statistics about the proxy cache.
311
+
312
+ Returns:
313
+ Dict[str, Any]: Statistics including count, last update time, etc.
314
+ """
315
+ return {
316
+ 'proxy_count': len(_proxy_cache['proxies']),
317
+ 'last_updated': _proxy_cache['last_updated'],
318
+ 'cache_duration': _proxy_cache['cache_duration'],
319
+ 'cache_age_seconds': time.time() - _proxy_cache['last_updated'],
320
+ 'source_url': PROXY_SOURCE_URL
321
+ }
322
+
323
+
324
+ def set_proxy_cache_duration(duration: int) -> None:
325
+ """
326
+ Set the proxy cache duration.
327
+
328
+ Args:
329
+ duration: Cache duration in seconds
330
+ """
331
+ global _proxy_cache
332
+ _proxy_cache['cache_duration'] = duration
@@ -1,9 +1,19 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from typing import List, Dict, Optional, Union, Generator, Any, TypedDict, Callable
3
3
  import json
4
- import logging
5
4
  from dataclasses import dataclass
6
- logger = logging.getLogger(__name__)
5
+
6
+ # Import WebScout Litlogger instead of standard logging
7
+ from webscout.Litlogger import Logger, LogLevel
8
+
9
+ logger = Logger(name="OpenAIBase", level=LogLevel.INFO)
10
+
11
+ # Import the ProxyAutoMeta metaclass
12
+ try:
13
+ from .autoproxy import ProxyAutoMeta
14
+ except ImportError:
15
+ # Fallback if autoproxy is not available
16
+ ProxyAutoMeta = type
7
17
 
8
18
 
9
19
  # Import the utils for response structures
@@ -243,8 +253,7 @@ class BaseChat(ABC):
243
253
  # instance.get_proxied_curl_async_session = get_proxied_curl_async_session
244
254
 
245
255
  # return instance
246
- # class OPENAICompatibleMeta(ABC, metaclass=ProxyAutoMeta):
247
- class OpenAICompatibleProvider(ABC):
256
+ class OpenAICompatibleProvider(ABC, metaclass=ProxyAutoMeta):
248
257
  """
249
258
  Abstract Base Class for providers mimicking the OpenAI Python client structure.
250
259
  Requires a nested 'chat.completions' structure with tool support.
@@ -267,13 +276,14 @@ class OpenAICompatibleProvider(ABC):
267
276
  supports_tool_choice: bool = False # Whether the provider supports tool_choice
268
277
 
269
278
  @abstractmethod
270
- def __init__(self, api_key: Optional[str] = None, tools: Optional[List[Tool]] = None, proxies: Optional[dict] = None, **kwargs: Any):
279
+ def __init__(self, api_key: Optional[str] = None, tools: Optional[List[Tool]] = None, proxies: Optional[dict] = None, disable_auto_proxy: bool = False, **kwargs: Any):
271
280
  self.available_tools = {}
272
281
  if tools:
273
282
  self.register_tools(tools)
274
283
  # self.proxies is set by ProxyAutoMeta
275
284
  # Subclasses should use self.proxies for all network requests
276
285
  # Optionally, use self.get_proxied_session() for a requests.Session with proxies
286
+ # The disable_auto_proxy parameter is handled by ProxyAutoMeta
277
287
  # raise NotImplementedError # <-- Commented out for metaclass test
278
288
 
279
289
  @property
@@ -1609,6 +1609,5 @@ if __name__ == "__main__":
1609
1609
  print("\n--- End of Stream ---")
1610
1610
  if not full_stream_response:
1611
1611
  print(f"{RED}Stream test failed: No content received.{RESET}")
1612
-
1613
1612
  except Exception as e:
1614
1613
  print(f"{RED}Streaming Test Failed: {e}{RESET}")
@@ -1,6 +1,8 @@
1
1
  import time
2
2
  import uuid
3
3
  import json
4
+ import random
5
+ import string
4
6
  from typing import List, Dict, Optional, Union, Generator, Any
5
7
 
6
8
  # Use curl_cffi for requests
@@ -8,8 +10,8 @@ from curl_cffi.requests import Session
8
10
  from curl_cffi import CurlError
9
11
 
10
12
  # Import base classes and utility structures
11
- from .base import OpenAICompatibleProvider, BaseChat, BaseCompletions
12
- from .utils import (
13
+ from webscout.Provider.OPENAI.base import OpenAICompatibleProvider, BaseChat, BaseCompletions
14
+ from webscout.Provider.OPENAI.utils import (
13
15
  ChatCompletionChunk, ChatCompletion, Choice, ChoiceDelta,
14
16
  ChatCompletionMessage, CompletionUsage
15
17
  )
@@ -274,11 +276,8 @@ class MCPCore(OpenAICompatibleProvider):
274
276
  """
275
277
  OpenAI-compatible client for the MCPCore API (chat.mcpcore.xyz).
276
278
 
277
- Requires cookies to be stored in a JSON file (e.g., 'cookies.json').
278
- The JSON file should contain a list of cookie objects, including one named 'token'.
279
-
280
279
  Usage:
281
- client = MCPCore(cookies_path="path/to/your/cookies.json")
280
+ client = MCPCore()
282
281
  response = client.chat.completions.create(
283
282
  model="google/gemma-7b-it",
284
283
  messages=[{"role": "user", "content": "Hello!"}]
@@ -286,71 +285,96 @@ class MCPCore(OpenAICompatibleProvider):
286
285
  print(response.choices[0].message.content)
287
286
  """
288
287
  AVAILABLE_MODELS = [
289
- "google/gemma-7b-it",
290
- "deepseek-ai/deepseek-coder-33b-instruct",
291
- "deepseek-ai/DeepSeek-R1",
292
- "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B",
293
- "deepseek-ai/DeepSeek-v3-0324",
294
- "fixie-ai/ultravox-v0_4_1-llama-3_1-8b",
295
- "meta-llama/Llama-3.3-70B-Instruct",
296
- "meta-llama/Llama-4-Maverick-Instruct",
297
- "mistralai/Mistral-7B-Instruct-v0.2",
298
- "qwen-max-latest",
299
- "qwen-plus-latest",
300
- "qwen2.5-coder-32b-instruct",
301
- "qwen-turbo-latest",
302
- "qwen2.5-14b-instruct-1m",
303
- "GLM-4-32B",
304
- "Z1-32B",
305
- "Z1-Rumination",
306
- "arena-model",
307
- "qvq-72b-preview-0310",
308
- "qwq-32b",
309
- "qwen3-235b-a22b",
310
- "qwen3-30b-a3b",
311
- "qwen3-32b",
312
- "deepseek-flash",
313
- "@cf/meta/llama-4-scout-17b-16e-instruct",
314
- "任务专用",
288
+ "@cf/deepseek-ai/deepseek-math-7b-instruct",
289
+ "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b",
290
+ "@cf/defog/sqlcoder-7b-2",
291
+ "@cf/fblgit/una-cybertron-7b-v2-bf16",
292
+ "@cf/google/gemma-3-12b-it",
293
+ "@cf/meta/llama-2-7b-chat-int8",
294
+ "@hf/thebloke/llama-2-13b-chat-awq",
295
+ "@hf/thebloke/llamaguard-7b-awq",
296
+ "@hf/thebloke/mistral-7b-instruct-v0.1-awq",
297
+ "@hf/thebloke/neural-chat-7b-v3-1-awq",
298
+ "anthropic/claude-3.5-haiku",
299
+ "anthropic/claude-3.5-sonnet",
300
+ "anthropic/claude-3.7-sonnet",
301
+ "anthropic/claude-3.7-sonnet:thinking",
302
+ "anthropic/claude-opus-4",
303
+ "anthropic/claude-sonnet-4",
304
+ "openai/chatgpt-4o-latest",
305
+ "openai/gpt-3.5-turbo",
306
+ "openai/gpt-4.1",
307
+ "openai/gpt-4.1-mini",
308
+ "openai/gpt-4.1-nano",
309
+ "openai/gpt-4o-mini-search-preview",
310
+ "openai/gpt-4o-search-preview",
311
+ "openai/o1-pro",
312
+ "openai/o3-mini",
313
+ "sarvam-m",
314
+ "x-ai/grok-3-beta",
315
315
  ]
316
316
 
317
+ def _auto_fetch_token(self):
318
+ """Automatically fetch a token from the signup endpoint."""
319
+ session = Session()
320
+ def random_string(length=8):
321
+ return ''.join(random.choices(string.ascii_lowercase, k=length))
322
+ name = random_string(6)
323
+ email = f"{random_string(8)}@gmail.com"
324
+ password = email
325
+ profile_image_url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAkRJREFUeF7tmDFOw0AUBdcSiIaKM3CKHIQ7UHEISq5AiUTFHYC0XADoTRsJEZFEjhFIaYAim92fjGFS736/zOTZzjavl0d98oMh0CgE4+IriEJYPhQC86EQhdAIwPL4DFEIjAAsjg1RCIwALI4NUQiMACyODVEIjAAsjg1RCIwALI4NUQiMACyODVEIjAAsjg1RCIwALI4NUQiMACyODVEIjAAsjg1RCIwALI4NUQiMACyODVEIjAAsjg2BCfkAIqwAA94KZ/EAAAAASUVORK5CYII="
326
+ payload = {
327
+ "name": name,
328
+ "email": email,
329
+ "password": password,
330
+ "profile_image_url": profile_image_url
331
+ }
332
+ headers = {
333
+ **LitAgent().generate_fingerprint(),
334
+ 'origin': 'https://chat.mcpcore.xyz',
335
+ 'referer': 'https://chat.mcpcore.xyz/auth',
336
+ }
337
+ try:
338
+ resp = session.post(
339
+ "https://chat.mcpcore.xyz/api/v1/auths/signup",
340
+ headers=headers,
341
+ json=payload,
342
+ timeout=30,
343
+ impersonate="chrome110"
344
+ )
345
+ if resp.ok:
346
+ data = resp.json()
347
+ token = data.get("token")
348
+ if token:
349
+ return token
350
+ # fallback: try to get from set-cookie
351
+ set_cookie = resp.headers.get("set-cookie", "")
352
+ if "token=" in set_cookie:
353
+ return set_cookie.split("token=")[1].split(";")[0]
354
+ raise RuntimeError(f"Failed to auto-fetch token: {resp.status_code} {resp.text}")
355
+ except Exception as e:
356
+ raise RuntimeError(f"Token auto-fetch failed: {e}")
357
+
317
358
  def __init__(
318
359
  self,
319
- cookies_path: str, # Make cookies path mandatory for authentication
320
360
  timeout: int = 60,
321
361
  ):
322
362
  """
323
363
  Initializes the MCPCore OpenAI-compatible client.
324
364
 
325
365
  Args:
326
- cookies_path: Path to the JSON file containing cookies (must include 'token').
327
366
  timeout: Request timeout in seconds.
328
- proxies: Optional proxy configuration.
329
- system_prompt: Default system prompt to use if none is provided in messages.
330
367
  """
331
368
  self.api_endpoint = "https://chat.mcpcore.xyz/api/chat/completions"
332
369
  self.timeout = timeout
333
- self.cookies_path = cookies_path
334
-
335
- try:
336
- self.token = self._load_token_from_cookies()
337
- if not self.token:
338
- raise ValueError("Could not find 'token' cookie in the provided file.")
339
- except Exception as e:
340
- raise ValueError(f"Failed to load authentication token from cookies file '{cookies_path}': {e}") from e
341
-
370
+ self.token = self._auto_fetch_token()
342
371
  self.session = Session() # Use curl_cffi Session
343
372
 
344
373
  # Basic headers + Authorization
345
374
  self.headers = {
346
- 'authority': 'chat.mcpcore.xyz',
347
- 'accept': '*/*', # Accept anything, let the server decide
348
- 'accept-language': 'en-US,en;q=0.9',
349
- 'authorization': f'Bearer {self.token}',
350
- 'content-type': 'application/json',
375
+ **LitAgent().generate_fingerprint(),
351
376
  'origin': 'https://chat.mcpcore.xyz',
352
- 'referer': 'https://chat.mcpcore.xyz/',
353
- 'user-agent': LitAgent().random(), # Use LitAgent for User-Agent
377
+ 'referer': 'https://chat.mcpcore.xyz/auth',
354
378
  }
355
379
  # Add more headers mimicking browser behavior if needed, e.g., sec-ch-ua, etc.
356
380
  # Example:
@@ -366,27 +390,42 @@ class MCPCore(OpenAICompatibleProvider):
366
390
  self.session.headers.update(self.headers)
367
391
  self.chat = Chat(self) # Initialize chat interface
368
392
 
369
- def _load_token_from_cookies(self) -> Optional[str]:
370
- """Load the 'token' value from a JSON cookies file."""
371
- try:
372
- with open(self.cookies_path, "r") as f:
373
- cookies = json.load(f)
374
- # Find the cookie named 'token'
375
- token_cookie = next((cookie for cookie in cookies if cookie.get("name") == "token"), None)
376
- return token_cookie.get("value") if token_cookie else None
377
- except FileNotFoundError:
378
- print(f"{RED}Error: Cookies file not found at {self.cookies_path}!{RESET}")
379
- return None
380
- except json.JSONDecodeError:
381
- print(f"{RED}Error: Invalid JSON format in cookies file: {self.cookies_path}!{RESET}")
382
- return None
383
- except Exception as e:
384
- print(f"{RED}An unexpected error occurred loading cookies: {e}{RESET}")
385
- return None
386
-
387
393
  @property
388
394
  def models(self):
389
395
  class _ModelList:
390
396
  def list(inner_self):
391
397
  return type(self).AVAILABLE_MODELS
392
398
  return _ModelList()
399
+
400
+ if __name__ == "__main__":
401
+ print("-" * 100)
402
+ print(f"{'Model':<50} {'Status':<10} {'Response'}")
403
+ print("-" * 100)
404
+
405
+ test_prompt = "Say 'Hello' in one word"
406
+
407
+ client = MCPCore()
408
+ for model in client.models.list():
409
+ print(f"\rTesting {model}...", end="")
410
+ try:
411
+ presp = client.chat.completions.create(
412
+ model=model,
413
+ messages=[{"role": "user", "content": test_prompt}]
414
+ )
415
+ # Try to get the response text (truncate to 100 chars)
416
+ if hasattr(presp, "choices") and presp.choices and hasattr(presp.choices[0], "message"):
417
+ content = presp.choices[0].message.content or ""
418
+ clean_text = content.strip().encode('utf-8', errors='ignore').decode('utf-8')
419
+ display_text = clean_text[:100] + "..." if len(clean_text) > 100 else clean_text
420
+ status = "✓" if clean_text else "✗"
421
+ if not clean_text:
422
+ display_text = "Empty or invalid response"
423
+ else:
424
+ status = "✗"
425
+ display_text = "Empty or invalid response"
426
+ print(f"\r{model:<50} {status:<10} {display_text}")
427
+ except Exception as e:
428
+ error_msg = str(e)
429
+ if len(error_msg) > 100:
430
+ error_msg = error_msg[:97] + "..."
431
+ print(f"\r{model:<50} {'✗':<10} Error: {error_msg}")