webscout 8.3.6__py3-none-any.whl → 8.3.7__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 (130) hide show
  1. webscout/AIutel.py +2 -0
  2. webscout/Provider/AISEARCH/__init__.py +18 -11
  3. webscout/Provider/AISEARCH/scira_search.py +3 -1
  4. webscout/Provider/Aitopia.py +2 -3
  5. webscout/Provider/Andi.py +3 -3
  6. webscout/Provider/ChatGPTClone.py +1 -1
  7. webscout/Provider/ChatSandbox.py +1 -0
  8. webscout/Provider/Cloudflare.py +1 -1
  9. webscout/Provider/Cohere.py +1 -0
  10. webscout/Provider/Deepinfra.py +7 -10
  11. webscout/Provider/ExaAI.py +1 -1
  12. webscout/Provider/ExaChat.py +1 -80
  13. webscout/Provider/Flowith.py +1 -1
  14. webscout/Provider/Gemini.py +7 -5
  15. webscout/Provider/GeminiProxy.py +1 -0
  16. webscout/Provider/GithubChat.py +3 -1
  17. webscout/Provider/Groq.py +1 -1
  18. webscout/Provider/HeckAI.py +8 -4
  19. webscout/Provider/Jadve.py +23 -38
  20. webscout/Provider/K2Think.py +308 -0
  21. webscout/Provider/Koboldai.py +8 -186
  22. webscout/Provider/LambdaChat.py +2 -4
  23. webscout/Provider/Nemotron.py +3 -4
  24. webscout/Provider/Netwrck.py +3 -2
  25. webscout/Provider/OLLAMA.py +1 -0
  26. webscout/Provider/OPENAI/Cloudflare.py +6 -7
  27. webscout/Provider/OPENAI/FalconH1.py +2 -7
  28. webscout/Provider/OPENAI/FreeGemini.py +6 -8
  29. webscout/Provider/OPENAI/{monochat.py → K2Think.py} +180 -77
  30. webscout/Provider/OPENAI/NEMOTRON.py +3 -6
  31. webscout/Provider/OPENAI/PI.py +5 -4
  32. webscout/Provider/OPENAI/Qwen3.py +2 -3
  33. webscout/Provider/OPENAI/TogetherAI.py +2 -2
  34. webscout/Provider/OPENAI/TwoAI.py +3 -4
  35. webscout/Provider/OPENAI/__init__.py +17 -58
  36. webscout/Provider/OPENAI/ai4chat.py +313 -303
  37. webscout/Provider/OPENAI/base.py +9 -29
  38. webscout/Provider/OPENAI/chatgpt.py +7 -2
  39. webscout/Provider/OPENAI/chatgptclone.py +4 -7
  40. webscout/Provider/OPENAI/chatsandbox.py +84 -59
  41. webscout/Provider/OPENAI/deepinfra.py +6 -6
  42. webscout/Provider/OPENAI/heckai.py +4 -1
  43. webscout/Provider/OPENAI/netwrck.py +1 -0
  44. webscout/Provider/OPENAI/scirachat.py +6 -0
  45. webscout/Provider/OPENAI/textpollinations.py +3 -11
  46. webscout/Provider/OPENAI/toolbaz.py +14 -11
  47. webscout/Provider/OpenGPT.py +1 -1
  48. webscout/Provider/Openai.py +150 -402
  49. webscout/Provider/PI.py +1 -0
  50. webscout/Provider/Perplexitylabs.py +1 -2
  51. webscout/Provider/QwenLM.py +107 -89
  52. webscout/Provider/STT/__init__.py +17 -2
  53. webscout/Provider/{Llama3.py → Sambanova.py} +9 -10
  54. webscout/Provider/StandardInput.py +1 -1
  55. webscout/Provider/TTI/__init__.py +18 -12
  56. webscout/Provider/TTS/__init__.py +18 -10
  57. webscout/Provider/TeachAnything.py +1 -0
  58. webscout/Provider/TextPollinationsAI.py +5 -12
  59. webscout/Provider/TogetherAI.py +86 -87
  60. webscout/Provider/TwoAI.py +53 -309
  61. webscout/Provider/TypliAI.py +2 -1
  62. webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +1 -1
  63. webscout/Provider/Venice.py +2 -1
  64. webscout/Provider/VercelAI.py +1 -0
  65. webscout/Provider/WiseCat.py +2 -1
  66. webscout/Provider/WrDoChat.py +2 -1
  67. webscout/Provider/__init__.py +18 -86
  68. webscout/Provider/ai4chat.py +1 -1
  69. webscout/Provider/akashgpt.py +7 -10
  70. webscout/Provider/cerebras.py +115 -9
  71. webscout/Provider/chatglm.py +170 -83
  72. webscout/Provider/cleeai.py +1 -2
  73. webscout/Provider/deepseek_assistant.py +1 -1
  74. webscout/Provider/elmo.py +1 -1
  75. webscout/Provider/geminiapi.py +1 -1
  76. webscout/Provider/granite.py +1 -1
  77. webscout/Provider/hermes.py +1 -3
  78. webscout/Provider/julius.py +1 -0
  79. webscout/Provider/learnfastai.py +1 -1
  80. webscout/Provider/llama3mitril.py +1 -1
  81. webscout/Provider/llmchat.py +1 -1
  82. webscout/Provider/llmchatco.py +1 -1
  83. webscout/Provider/meta.py +3 -3
  84. webscout/Provider/oivscode.py +2 -2
  85. webscout/Provider/scira_chat.py +51 -124
  86. webscout/Provider/searchchat.py +1 -0
  87. webscout/Provider/sonus.py +1 -1
  88. webscout/Provider/toolbaz.py +15 -12
  89. webscout/Provider/turboseek.py +31 -22
  90. webscout/Provider/typefully.py +2 -1
  91. webscout/Provider/x0gpt.py +1 -0
  92. webscout/Provider/yep.py +2 -1
  93. webscout/tempid.py +6 -0
  94. webscout/version.py +1 -1
  95. {webscout-8.3.6.dist-info → webscout-8.3.7.dist-info}/METADATA +2 -1
  96. {webscout-8.3.6.dist-info → webscout-8.3.7.dist-info}/RECORD +103 -129
  97. webscout/Provider/AllenAI.py +0 -440
  98. webscout/Provider/Blackboxai.py +0 -793
  99. webscout/Provider/FreeGemini.py +0 -250
  100. webscout/Provider/GptOss.py +0 -207
  101. webscout/Provider/Hunyuan.py +0 -283
  102. webscout/Provider/Kimi.py +0 -445
  103. webscout/Provider/MCPCore.py +0 -322
  104. webscout/Provider/MiniMax.py +0 -207
  105. webscout/Provider/OPENAI/BLACKBOXAI.py +0 -1045
  106. webscout/Provider/OPENAI/MiniMax.py +0 -298
  107. webscout/Provider/OPENAI/autoproxy.py +0 -1067
  108. webscout/Provider/OPENAI/copilot.py +0 -321
  109. webscout/Provider/OPENAI/gptoss.py +0 -288
  110. webscout/Provider/OPENAI/kimi.py +0 -469
  111. webscout/Provider/OPENAI/mcpcore.py +0 -431
  112. webscout/Provider/OPENAI/multichat.py +0 -378
  113. webscout/Provider/Reka.py +0 -214
  114. webscout/Provider/UNFINISHED/fetch_together_models.py +0 -90
  115. webscout/Provider/asksteve.py +0 -220
  116. webscout/Provider/copilot.py +0 -441
  117. webscout/Provider/freeaichat.py +0 -294
  118. webscout/Provider/koala.py +0 -182
  119. webscout/Provider/lmarena.py +0 -198
  120. webscout/Provider/monochat.py +0 -275
  121. webscout/Provider/multichat.py +0 -375
  122. webscout/Provider/scnet.py +0 -244
  123. webscout/Provider/talkai.py +0 -194
  124. /webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +0 -0
  125. /webscout/Provider/{Qodo.py → UNFINISHED/Qodo.py} +0 -0
  126. /webscout/Provider/{XenAI.py → UNFINISHED/XenAI.py} +0 -0
  127. {webscout-8.3.6.dist-info → webscout-8.3.7.dist-info}/WHEEL +0 -0
  128. {webscout-8.3.6.dist-info → webscout-8.3.7.dist-info}/entry_points.txt +0 -0
  129. {webscout-8.3.6.dist-info → webscout-8.3.7.dist-info}/licenses/LICENSE.md +0 -0
  130. {webscout-8.3.6.dist-info → webscout-8.3.7.dist-info}/top_level.txt +0 -0
@@ -2,21 +2,15 @@ from curl_cffi.requests import Session
2
2
  from curl_cffi import CurlError
3
3
  import json
4
4
  import base64
5
- import time
6
- import os
7
- import pickle
8
- import tempfile
9
5
  from typing import Any, Dict, Optional, Generator, Union
10
6
  import re # Import re for parsing SSE
11
- import urllib.parse
12
7
 
13
8
  from webscout.AIutel import Optimizers
14
9
  from webscout.AIutel import Conversation
15
- from webscout.AIutel import AwesomePrompts, sanitize_stream # Import sanitize_stream
10
+ from webscout.AIutel import AwesomePrompts, sanitize_stream
16
11
  from webscout.AIbase import Provider
17
12
  from webscout import exceptions
18
13
  from webscout.litagent import LitAgent
19
- from webscout.Extra.tempmail import get_random_email
20
14
 
21
15
 
22
16
  class TwoAI(Provider):
@@ -26,234 +20,18 @@ class TwoAI(Provider):
26
20
  SUTRA's dual-transformer extends the power of both MoE and Dense AI language model architectures,
27
21
  delivering cost-efficient multilingual capabilities for over 50+ languages.
28
22
 
29
- API keys can be generated using the generate_api_key() method, which uses a temporary email
30
- to register for the Two AI service and extract the API key from the confirmation email.
31
- API keys are cached to avoid regenerating them on every initialization.
23
+ API keys must be provided directly by the user.
32
24
  """
33
25
 
26
+ required_auth = True
34
27
  AVAILABLE_MODELS = [
35
28
  "sutra-v2", # Multilingual AI model for instruction execution and conversational intelligence
36
29
  "sutra-r0", # Advanced reasoning model for complex problem-solving and deep contextual understanding
37
30
  ]
38
-
39
- # Class-level cache for API keys
40
- _api_key_cache = None
41
- _cache_file = os.path.join(tempfile.gettempdir(), "webscout_twoai_cache.pkl")
42
-
43
- @classmethod
44
- def _load_cached_api_key(cls) -> Optional[str]:
45
- """Load cached API key from file."""
46
- try:
47
- if os.path.exists(cls._cache_file):
48
- with open(cls._cache_file, 'rb') as f:
49
- cache_data = pickle.load(f)
50
- # Check if cache is not too old (24 hours)
51
- if time.time() - cache_data.get('timestamp', 0) < 86400:
52
- return cache_data.get('api_key')
53
- except Exception:
54
- # If cache is corrupted or unreadable, ignore and regenerate
55
- pass
56
- return None
57
-
58
- @classmethod
59
- def _save_cached_api_key(cls, api_key: str):
60
- """Save API key to cache file."""
61
- try:
62
- cache_data = {
63
- 'api_key': api_key,
64
- 'timestamp': time.time()
65
- }
66
- with open(cls._cache_file, 'wb') as f:
67
- pickle.dump(cache_data, f)
68
- except Exception:
69
- # If caching fails, continue without caching
70
- pass
71
-
72
- @classmethod
73
- def _validate_api_key(cls, api_key: str) -> bool:
74
- """Validate if an API key is still working."""
75
- try:
76
- session = Session()
77
- headers = {
78
- 'User-Agent': LitAgent().random(),
79
- 'Accept': 'application/json',
80
- 'Content-Type': 'application/json',
81
- 'Authorization': f'Bearer {api_key}',
82
- }
83
-
84
- # Test with a simple request
85
- test_payload = {
86
- "messages": [{"role": "user", "content": "test"}],
87
- "model": "sutra-v2",
88
- "max_tokens": 1,
89
- "stream": False
90
- }
91
-
92
- response = session.post(
93
- "https://api.two.ai/v2/chat/completions",
94
- headers=headers,
95
- json=test_payload,
96
- timeout=10,
97
- impersonate="chrome120"
98
- )
99
-
100
- # If we get a 200 or 400 (bad request but auth worked), key is valid
101
- # If we get 401/403, key is invalid
102
- return response.status_code not in [401, 403]
103
- except Exception:
104
- # If validation fails, assume key is invalid
105
- return False
106
-
107
- @classmethod
108
- def get_cached_api_key(cls) -> str:
109
- """Get a cached API key or generate a new one if needed."""
110
- # First check class-level cache
111
- if cls._api_key_cache:
112
- if cls._validate_api_key(cls._api_key_cache):
113
- return cls._api_key_cache
114
- else:
115
- cls._api_key_cache = None
116
-
117
- # Then check file cache
118
- cached_key = cls._load_cached_api_key()
119
- if cached_key and cls._validate_api_key(cached_key):
120
- cls._api_key_cache = cached_key
121
- return cached_key
122
-
123
- # Generate new key if no valid cached key
124
- new_key = cls.generate_api_key()
125
- cls._api_key_cache = new_key
126
- cls._save_cached_api_key(new_key)
127
- return new_key
128
-
129
- @staticmethod
130
- def generate_api_key() -> str:
131
- """
132
- Generate a new Two AI API key using a temporary email.
133
-
134
- This method:
135
- 1. Creates a temporary email using webscout's tempmail module
136
- 2. Registers for Two AI using the Loops.so newsletter form
137
- 3. Waits for and extracts the API key from the confirmation email
138
-
139
- Returns:
140
- str: The generated API key
141
-
142
- Raises:
143
- Exception: If the API key cannot be generated
144
- """
145
- # Get a temporary email
146
- email, provider = get_random_email("tempmailio")
147
-
148
- # Register for Two AI using the Loops.so newsletter form
149
- loops_url = "https://app.loops.so/api/newsletter-form/cm7i4o92h057auy1o74cxbhxo"
150
-
151
- # Create a session with appropriate headers
152
- session = Session()
153
- session.headers.update({
154
- 'User-Agent': LitAgent().random(),
155
- 'Content-Type': 'application/x-www-form-urlencoded',
156
- 'Origin': 'https://www.two.ai',
157
- 'Referer': 'https://app.loops.so/',
158
- })
159
-
160
- # Prepare form data
161
- form_data = {
162
- 'email': email,
163
- 'userGroup': 'Via Framer',
164
- 'mailingLists': 'cm8ay9cic00x70kjv0bd34k66'
165
- }
166
-
167
- # Send the registration request
168
- encoded_data = urllib.parse.urlencode(form_data)
169
- response = session.post(loops_url, data=encoded_data, impersonate="chrome120")
170
-
171
- if response.status_code != 200:
172
- raise Exception(f"Failed to register for Two AI: {response.status_code} - {response.text}")
173
-
174
- # Wait for the confirmation email and extract the API key
175
- max_attempts = 5
176
- attempt = 0
177
- api_key = None
178
- wait_time = 2
179
-
180
- while attempt < max_attempts and not api_key:
181
- messages = provider.get_messages()
182
-
183
- for message in messages:
184
- # Check if this is likely the confirmation email based on subject and sender
185
- subject = message.get('subject', '')
186
- sender = ''
187
-
188
- # Try to get the sender from different possible fields
189
- if 'from' in message:
190
- if isinstance(message['from'], dict):
191
- sender = message['from'].get('address', '')
192
- else:
193
- sender = str(message['from'])
194
- elif 'sender' in message:
195
- if isinstance(message['sender'], dict):
196
- sender = message['sender'].get('address', '')
197
- else:
198
- sender = str(message['sender'])
199
-
200
- # Look for keywords in the subject that indicate this is the confirmation email
201
- subject_match = any(keyword in subject.lower() for keyword in
202
- ['welcome', 'confirm', 'verify', 'api', 'key', 'sutra', 'two.ai', 'loops'])
203
-
204
- # Look for keywords in the sender that indicate this is from Two AI or Loops
205
- sender_match = any(keyword in sender.lower() for keyword in
206
- ['two.ai', 'sutra', 'loops.so', 'loops', 'no-reply', 'noreply'])
207
-
208
- is_confirmation = subject_match or sender_match
209
-
210
- if is_confirmation:
211
- pass
212
- # Try to get the message content from various possible fields
213
- content = None
214
-
215
- # Check for body field (seen in the debug output)
216
- if 'body' in message:
217
- content = message['body']
218
- # Check for content.text field
219
- elif 'content' in message and 'text' in message['content']:
220
- content = message['content']['text']
221
- # Check for html field
222
- elif 'html' in message:
223
- content = message['html']
224
- # Check for text field
225
- elif 'text' in message:
226
- content = message['text']
227
-
228
- if not content:
229
- continue
230
-
231
- # Look for the API key pattern in the email content
232
- # First, try to find the API key directly
233
- api_key_match = re.search(r'sutra_[A-Za-z0-9]{60,70}', content)
234
-
235
- # If not found, try looking for the key with the label
236
- if not api_key_match:
237
- key_section_match = re.search(r'🔑 SUTRA API Key\s*([^\s]+)', content)
238
- if key_section_match:
239
- api_key_match = re.search(r'(sutra_[A-Za-z0-9]+)', key_section_match.group(1))
240
-
241
- # If still not found, try a more general pattern
242
- if not api_key_match:
243
- api_key_match = re.search(r'sutra_\S+', content)
244
-
245
- if api_key_match:
246
- api_key = api_key_match.group(0)
247
- break
248
- if not api_key:
249
- attempt += 1
250
- time.sleep(wait_time)
251
- if not api_key:
252
- raise Exception("Failed to get API key from confirmation email")
253
- return api_key
254
31
 
255
32
  def __init__(
256
33
  self,
34
+ api_key: str,
257
35
  is_conversation: bool = True,
258
36
  max_tokens: int = 1024,
259
37
  timeout: int = 30,
@@ -271,6 +49,7 @@ class TwoAI(Provider):
271
49
  Initializes the TwoAI API client.
272
50
 
273
51
  Args:
52
+ api_key: TwoAI API key (required).
274
53
  is_conversation: Whether to maintain conversation history.
275
54
  max_tokens: Maximum number of tokens to generate.
276
55
  timeout: Request timeout in seconds.
@@ -287,17 +66,27 @@ class TwoAI(Provider):
287
66
  if model not in self.AVAILABLE_MODELS:
288
67
  raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
289
68
 
290
- # Use cached API key or generate new one if needed
291
- api_key = self.get_cached_api_key()
69
+ if not api_key:
70
+ raise exceptions.AuthenticationError("TwoAI API key is required.")
292
71
 
293
- self.url = "https://api.two.ai/v2/chat/completions" # API endpoint
72
+ self.url = "https://chatsutra-server.account-2b0.workers.dev/v2/chat/completions" # Correct API endpoint
294
73
  self.headers = {
295
- 'User-Agent': LitAgent().random(),
296
- 'Accept': 'text/event-stream', # For streaming responses
74
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0',
75
+ 'Accept': 'application/json',
76
+ 'Accept-Encoding': 'gzip, deflate, br, zstd',
77
+ 'Accept-Language': 'en-US,en;q=0.9,en-IN;q=0.8',
297
78
  'Content-Type': 'application/json',
298
- 'Authorization': f'Bearer {api_key}', # Using Bearer token authentication
299
79
  'Origin': 'https://chat.two.ai',
300
- 'Referer': 'https://api.two.app/'
80
+ 'Referer': 'https://chatsutra-server.account-2b0.workers.dev/',
81
+ 'Sec-Ch-Ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Microsoft Edge";v="140"',
82
+ 'Sec-Ch-Ua-Mobile': '?0',
83
+ 'Sec-Ch-Ua-Platform': '"Windows"',
84
+ 'Sec-Fetch-Dest': 'empty',
85
+ 'Sec-Fetch-Mode': 'cors',
86
+ 'Sec-Fetch-Site': 'cross-site',
87
+ 'Sec-Gpc': '1',
88
+ 'Dnt': '1',
89
+ 'X-Session-Token': api_key # Using session token instead of Bearer auth
301
90
  }
302
91
 
303
92
  # Initialize curl_cffi Session
@@ -404,7 +193,6 @@ class TwoAI(Provider):
404
193
  "model": self.model,
405
194
  "temperature": self.temperature,
406
195
  "max_tokens": self.max_tokens_to_sample,
407
- "stream": stream,
408
196
  "extra_body": {
409
197
  "online_search": online_search,
410
198
  }
@@ -417,8 +205,7 @@ class TwoAI(Provider):
417
205
  self.url,
418
206
  json=payload,
419
207
  stream=True,
420
- timeout=self.timeout,
421
- impersonate="chrome110"
208
+ timeout=self.timeout
422
209
  )
423
210
 
424
211
  if response.status_code != 200:
@@ -432,18 +219,17 @@ class TwoAI(Provider):
432
219
  f"Request failed with status code {response.status_code} - {error_detail}"
433
220
  )
434
221
 
435
- # Use sanitize_stream for SSE processing
222
+ # Use sanitize_stream to process the SSE stream
436
223
  processed_stream = sanitize_stream(
437
- data=response.iter_content(chunk_size=None), # Pass byte iterator
224
+ data=response.iter_content(chunk_size=None),
438
225
  intro_value="data:",
439
- to_json=True, # Stream sends JSON
226
+ to_json=True,
440
227
  skip_markers=["[DONE]"],
441
- content_extractor=self._twoai_extractor, # Use the specific extractor
442
- yield_raw_on_error=False # Skip non-JSON lines or lines where extractor fails
228
+ content_extractor=self._twoai_extractor,
229
+ yield_raw_on_error=False
443
230
  )
444
231
 
445
232
  for content_chunk in processed_stream:
446
- # content_chunk is the string extracted by _twoai_extractor
447
233
  if content_chunk and isinstance(content_chunk, str):
448
234
  streaming_text += content_chunk
449
235
  resp = dict(text=content_chunk)
@@ -483,8 +269,8 @@ class TwoAI(Provider):
483
269
  # self.last_response and history are updated within for_stream's try/finally
484
270
  return self.last_response # Return the final aggregated dict
485
271
 
486
- effective_stream = stream if stream is not None else True
487
- return for_stream() if effective_stream else for_non_stream()
272
+ # The API uses SSE streaming for all requests, so we always use streaming
273
+ return for_stream()
488
274
 
489
275
  def chat(
490
276
  self,
@@ -495,36 +281,24 @@ class TwoAI(Provider):
495
281
  online_search: bool = True,
496
282
  image_path: str = None,
497
283
  ) -> str:
498
- effective_stream = stream if stream is not None else True
499
-
500
- def for_stream_chat():
501
- # ask() yields dicts when raw=False (default for chat)
502
- gen = self.ask(
503
- prompt,
504
- stream=True,
505
- raw=False, # Ensure ask yields dicts
506
- optimizer=optimizer,
507
- conversationally=conversationally,
508
- online_search=online_search,
509
- image_path=image_path,
510
- )
511
- for response_dict in gen:
512
- yield self.get_message(response_dict) # get_message expects dict
513
-
514
- def for_non_stream_chat():
515
- # ask() returns a dict when stream=False
516
- response_dict = self.ask(
517
- prompt,
518
- stream=False, # Ensure ask returns dict
519
- raw=False,
520
- optimizer=optimizer,
521
- conversationally=conversationally,
522
- online_search=online_search,
523
- image_path=image_path,
524
- )
525
- return self.get_message(response_dict) # get_message expects dict
526
-
527
- return for_stream_chat() if effective_stream else for_non_stream_chat()
284
+ # The API uses SSE streaming for all requests, so we always aggregate
285
+ aggregated_text = ""
286
+ gen = self.ask(
287
+ prompt,
288
+ stream=True,
289
+ raw=False, # Ensure ask yields dicts
290
+ optimizer=optimizer,
291
+ conversationally=conversationally,
292
+ online_search=online_search,
293
+ image_path=image_path,
294
+ )
295
+ for response_dict in gen:
296
+ if isinstance(response_dict, dict) and "text" in response_dict:
297
+ aggregated_text += response_dict["text"]
298
+ elif isinstance(response_dict, str):
299
+ aggregated_text += response_dict
300
+
301
+ return aggregated_text
528
302
 
529
303
  def get_message(self, response: dict) -> str:
530
304
  assert isinstance(response, dict), "Response should be of dict data-type only"
@@ -532,38 +306,8 @@ class TwoAI(Provider):
532
306
 
533
307
 
534
308
  if __name__ == "__main__":
535
- print("-" * 80)
536
- print(f"{'Model':<50} {'Status':<10} {'Response'}")
537
- print("-" * 80)
538
-
539
- for model in TwoAI.AVAILABLE_MODELS:
540
- try:
541
- test_ai = TwoAI(model=model, timeout=60)
542
- # Test stream first
543
- response_stream = test_ai.chat("Say 'Hello' in one word", stream=True)
544
- response_text = ""
545
- print(f"\r{model:<50} {'Streaming...':<10}", end="", flush=True)
546
- for chunk in response_stream:
547
- response_text += chunk
548
- # Optional: print chunks as they arrive for visual feedback
549
- # print(chunk, end="", flush=True)
550
-
551
- if response_text and len(response_text.strip()) > 0:
552
- status = "✓"
553
- # Clean and truncate response
554
- clean_text = response_text.strip() # Already decoded in get_message
555
- display_text = clean_text[:50] + "..." if len(clean_text) > 50 else clean_text
556
- else:
557
- status = "✗ (Stream)"
558
- display_text = "Empty or invalid stream response"
559
- print(f"\r{model:<50} {status:<10} {display_text}")
560
-
561
- # Optional: Add non-stream test if needed, but stream test covers basic functionality
562
- # print(f"\r{model:<50} {'Non-Stream...':<10}", end="", flush=True)
563
- # response_non_stream = test_ai.chat("Say 'Hi' again", stream=False)
564
- # if not response_non_stream or len(response_non_stream.strip()) == 0:
565
- # print(f"\r{model:<50} {'✗ (Non-Stream)':<10} Empty non-stream response")
566
-
567
-
568
- except Exception as e:
569
- print(f"\r{model:<50} {'✗':<10} {str(e)}")
309
+ from rich import print
310
+ ai = TwoAI(api_key="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJzanl2OHJtZGxDZDFnQ2hQdGxzZHdxUlVteXkyIiwic291cmNlIjoiRmlyZWJhc2UiLCJpYXQiOjE3NTc4NTEyMzYsImV4cCI6MTc1Nzg1MjEzNn0.ilTYrHRdN3_cme6VW3knWWfbypY_n_gsUe9DeDhEwrM", model="sutra-v2", temperature=0.7)
311
+ response = ai.chat("Write a poem about AI in the style of Shakespeare.")
312
+ for chunk in response:
313
+ print(chunk, end="", flush=True)
@@ -33,6 +33,7 @@ class TypliAI(Provider):
33
33
  >>> print(response)
34
34
  'I don't have access to real-time weather information...'
35
35
  """
36
+ required_auth = False
36
37
  AVAILABLE_MODELS = ["gpt-4o-mini"]
37
38
 
38
39
  def __init__(
@@ -308,4 +309,4 @@ if __name__ == "__main__":
308
309
  for chunk in response:
309
310
  print(chunk, end="", flush=True)
310
311
  except Exception as e:
311
- print(f"An error occurred: {e}")
312
+ print(f"An error occurred: {e}")
@@ -29,7 +29,7 @@ class GizAI(Provider):
29
29
  >>> response = ai.chat("What's the weather today?")
30
30
  >>> print(response)
31
31
  """
32
-
32
+ required_auth = False
33
33
  AVAILABLE_MODELS = [
34
34
  "azure-gpt-4-1",
35
35
  "chat-gpt4",
@@ -17,6 +17,7 @@ class Venice(Provider):
17
17
  A class to interact with the Venice AI API.
18
18
  """
19
19
 
20
+ required_auth = False
20
21
  AVAILABLE_MODELS = [
21
22
  "mistral-31-24b",
22
23
  "dolphin-3.0-mistral-24b",
@@ -247,4 +248,4 @@ if __name__ == "__main__":
247
248
  display_text = "Empty or invalid response"
248
249
  print(f"\r{model:<50} {status:<10} {display_text}")
249
250
  except Exception as e:
250
- print(f"\r{model:<50} {'✗':<10} {str(e)}")
251
+ print(f"\r{model:<50} {'✗':<10} {str(e)}")
@@ -18,6 +18,7 @@ class VercelAI(Provider):
18
18
  A class to interact with the Vercel AI API.
19
19
  """
20
20
 
21
+ required_auth = False
21
22
  AVAILABLE_MODELS = [
22
23
  "chat-model",
23
24
  "chat-model-reasoning"
@@ -17,6 +17,7 @@ class WiseCat(Provider):
17
17
  A class to interact with the WiseCat API.
18
18
  """
19
19
 
20
+ required_auth = False
20
21
  AVAILABLE_MODELS = [
21
22
  "chat-model-small",
22
23
  # "chat-model-large", # >>> NOT WORKING <<<
@@ -228,4 +229,4 @@ if __name__ == "__main__":
228
229
  display_text = "Empty or invalid response"
229
230
  print(f"\r{model:<50} {status:<10} {display_text}")
230
231
  except Exception as e:
231
- print(f"\r{model:<50} {'✗':<10} {str(e)}")
232
+ print(f"\r{model:<50} {'✗':<10} {str(e)}")
@@ -28,6 +28,7 @@ class WrDoChat(Provider):
28
28
  >>> print(response)
29
29
  """
30
30
 
31
+ required_auth = True
31
32
  AVAILABLE_MODELS = [
32
33
  "deepseek-chat-v3-0324",
33
34
  "deepseek-r1",
@@ -363,4 +364,4 @@ if __name__ == "__main__":
363
364
  ai = WrDoChat(cookies_path="cookies.json")
364
365
  response = ai.chat("write me a poem about AI", stream=True)
365
366
  for chunk in response:
366
- print(chunk, end="", flush=True)
367
+ print(chunk, end="", flush=True)
@@ -1,86 +1,18 @@
1
- # webscout/providers/__init__.py
2
- from .PI import *
3
- from .Cohere import Cohere
4
- from .Reka import REKA
5
- from .Groq import GROQ
6
- from .Groq import AsyncGROQ
7
- from .Openai import OPENAI
8
- from .Openai import AsyncOPENAI
9
- from .Koboldai import KOBOLDAI
10
- from .Koboldai import AsyncKOBOLDAI
11
- from .Blackboxai import BLACKBOXAI
12
- from .ai4chat import *
13
- from .Gemini import GEMINI
14
- from .Deepinfra import DeepInfra
15
- from .typefully import *
16
- from .cleeai import *
17
- from .OLLAMA import OLLAMA
18
- from .Andi import AndiSearch
19
- from .Llama3 import *
20
- from .koala import *
21
- from .meta import *
22
- from .julius import *
23
- from .yep import *
24
- from .Cloudflare import *
25
- from .turboseek import *
26
- from .TeachAnything import *
27
- from .x0gpt import *
28
- from .cerebras import *
29
- from .geminiapi import *
30
- from .elmo import *
31
- from .Netwrck import Netwrck
32
- from .llmchat import *
33
- from .llmchatco import LLMChatCo # Add new LLMChat.co provider
34
- from .talkai import *
35
- from .llama3mitril import *
36
- from .Marcus import *
37
- from .multichat import *
38
- from .Jadve import *
39
- from .chatglm import *
40
- from .hermes import *
41
- from .TextPollinationsAI import *
42
- from .QwenLM import *
43
- from .granite import *
44
- from .WiseCat import *
45
- from .freeaichat import FreeAIChat
46
- from .akashgpt import *
47
- from .Perplexitylabs import *
48
- from .AllenAI import *
49
- from .HeckAI import *
50
- from .TwoAI import *
51
- from .Venice import *
52
- from .GithubChat import *
53
- from .copilot import *
54
- from .sonus import *
55
- from .LambdaChat import *
56
- from .ChatGPTClone import *
57
- from .VercelAI import *
58
- from .ExaChat import *
59
- from .asksteve import *
60
- from .Aitopia import *
61
- from .searchchat import *
62
- from .ExaAI import ExaAI
63
- from .OpenGPT import OpenGPT
64
- from .scira_chat import *
65
- from .StandardInput import *
66
- from .toolbaz import Toolbaz
67
- from .scnet import SCNet
68
- from .MCPCore import MCPCore
69
- from .TypliAI import TypliAI
70
- from .ChatSandbox import ChatSandbox
71
- from .GizAI import GizAI
72
- from .WrDoChat import WrDoChat
73
- from .Nemotron import NEMOTRON
74
- from .FreeGemini import FreeGemini
75
- from .Flowith import Flowith
76
- from .lmarena import lmarena
77
- from .oivscode import oivscode
78
- from .XenAI import XenAI
79
- from .deepseek_assistant import DeepSeekAssistant
80
- from .GeminiProxy import GeminiProxy
81
- from .TogetherAI import TogetherAI
82
- from .MiniMax import MiniMax
83
- from .Qodo import *
84
- from .monochat import MonoChat
85
- from .Kimi import Kimi
86
- from .GptOss import GptOss
1
+ # This file marks the directory as a Python package.
2
+
3
+ import os
4
+ import importlib
5
+ from pathlib import Path
6
+
7
+ # Get current directory
8
+ current_dir = Path(__file__).parent
9
+
10
+ # Auto-import all .py files (except __init__.py)
11
+ for file_path in current_dir.glob("*.py"):
12
+ if file_path.name != "__init__.py":
13
+ module_name = file_path.stem
14
+ try:
15
+ module = importlib.import_module(f".{module_name}", package=__name__)
16
+ globals().update(vars(module))
17
+ except ImportError:
18
+ pass # Skip files that can't be imported
@@ -11,7 +11,7 @@ class AI4Chat(Provider):
11
11
  """
12
12
  A class to interact with the AI4Chat Riddle API.
13
13
  """
14
-
14
+ required_auth = False
15
15
  def __init__(
16
16
  self,
17
17
  is_conversation: bool = True,
@@ -27,17 +27,14 @@ class AkashGPT(Provider):
27
27
  >>> print(response)
28
28
  'The weather today depends on your location. I don't have access to real-time weather data.'
29
29
  """
30
-
30
+ required_auth = True
31
31
  AVAILABLE_MODELS = [
32
- "Qwen3-235B-A22B-FP8",
33
- "meta-llama-Llama-4-Maverick-17B-128E-Instruct-FP8",
34
- "nvidia-Llama-3-3-Nemotron-Super-49B-v1",
35
- "Qwen-QwQ-32B",
36
- "Meta-Llama-3-3-70B-Instruct",
37
- "DeepSeek-R1",
38
- "AkashGen"
39
-
40
-
32
+ "Qwen3-Next-80B-A3B-Instruct",
33
+ "DeepSeek-V3.1",
34
+ "openai-gpt-oss-120b",
35
+ "Qwen3-235B-A22B-Instruct-2507-FP8"
36
+ "meta-llama-Llama-4-Maverick-17B-128E-Instruct-FP8"
37
+ "Meta-Llama-3-3-70B-Instruct"
41
38
  ]
42
39
 
43
40
  def __init__(