webscout 8.3.4__py3-none-any.whl → 8.3.6__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 (98) hide show
  1. webscout/AIutel.py +52 -1016
  2. webscout/Bard.py +12 -6
  3. webscout/DWEBS.py +66 -57
  4. webscout/Provider/AISEARCH/PERPLEXED_search.py +214 -0
  5. webscout/Provider/AISEARCH/__init__.py +11 -10
  6. webscout/Provider/AISEARCH/felo_search.py +7 -3
  7. webscout/Provider/AISEARCH/scira_search.py +2 -0
  8. webscout/Provider/AISEARCH/stellar_search.py +53 -8
  9. webscout/Provider/Deepinfra.py +13 -1
  10. webscout/Provider/Flowith.py +6 -1
  11. webscout/Provider/GithubChat.py +1 -0
  12. webscout/Provider/GptOss.py +207 -0
  13. webscout/Provider/Kimi.py +445 -0
  14. webscout/Provider/Netwrck.py +3 -6
  15. webscout/Provider/OPENAI/README.md +2 -1
  16. webscout/Provider/OPENAI/TogetherAI.py +12 -8
  17. webscout/Provider/OPENAI/TwoAI.py +94 -1
  18. webscout/Provider/OPENAI/__init__.py +4 -4
  19. webscout/Provider/OPENAI/copilot.py +20 -4
  20. webscout/Provider/OPENAI/deepinfra.py +12 -0
  21. webscout/Provider/OPENAI/e2b.py +60 -8
  22. webscout/Provider/OPENAI/flowith.py +4 -3
  23. webscout/Provider/OPENAI/generate_api_key.py +48 -0
  24. webscout/Provider/OPENAI/gptoss.py +288 -0
  25. webscout/Provider/OPENAI/kimi.py +469 -0
  26. webscout/Provider/OPENAI/netwrck.py +8 -12
  27. webscout/Provider/OPENAI/refact.py +274 -0
  28. webscout/Provider/OPENAI/scirachat.py +4 -0
  29. webscout/Provider/OPENAI/textpollinations.py +11 -10
  30. webscout/Provider/OPENAI/toolbaz.py +1 -0
  31. webscout/Provider/OPENAI/venice.py +1 -0
  32. webscout/Provider/Perplexitylabs.py +163 -147
  33. webscout/Provider/Qodo.py +30 -6
  34. webscout/Provider/TTI/__init__.py +1 -0
  35. webscout/Provider/TTI/bing.py +14 -2
  36. webscout/Provider/TTI/together.py +11 -9
  37. webscout/Provider/TTI/venice.py +368 -0
  38. webscout/Provider/TTS/README.md +0 -1
  39. webscout/Provider/TTS/__init__.py +0 -1
  40. webscout/Provider/TTS/base.py +479 -159
  41. webscout/Provider/TTS/deepgram.py +409 -156
  42. webscout/Provider/TTS/elevenlabs.py +425 -111
  43. webscout/Provider/TTS/freetts.py +317 -140
  44. webscout/Provider/TTS/gesserit.py +192 -128
  45. webscout/Provider/TTS/murfai.py +248 -113
  46. webscout/Provider/TTS/openai_fm.py +347 -129
  47. webscout/Provider/TTS/speechma.py +620 -586
  48. webscout/Provider/TextPollinationsAI.py +11 -10
  49. webscout/Provider/TogetherAI.py +12 -4
  50. webscout/Provider/TwoAI.py +96 -2
  51. webscout/Provider/TypliAI.py +33 -27
  52. webscout/Provider/UNFINISHED/VercelAIGateway.py +339 -0
  53. webscout/Provider/UNFINISHED/fetch_together_models.py +6 -11
  54. webscout/Provider/Venice.py +1 -0
  55. webscout/Provider/WiseCat.py +18 -20
  56. webscout/Provider/__init__.py +2 -96
  57. webscout/Provider/cerebras.py +83 -33
  58. webscout/Provider/copilot.py +42 -23
  59. webscout/Provider/scira_chat.py +4 -0
  60. webscout/Provider/toolbaz.py +6 -10
  61. webscout/Provider/typefully.py +1 -11
  62. webscout/__init__.py +3 -15
  63. webscout/auth/__init__.py +19 -4
  64. webscout/auth/api_key_manager.py +189 -189
  65. webscout/auth/auth_system.py +25 -40
  66. webscout/auth/config.py +105 -6
  67. webscout/auth/database.py +377 -22
  68. webscout/auth/models.py +185 -130
  69. webscout/auth/request_processing.py +175 -11
  70. webscout/auth/routes.py +99 -2
  71. webscout/auth/server.py +9 -2
  72. webscout/auth/simple_logger.py +236 -0
  73. webscout/conversation.py +22 -20
  74. webscout/sanitize.py +1078 -0
  75. webscout/scout/README.md +20 -23
  76. webscout/scout/core/crawler.py +125 -38
  77. webscout/scout/core/scout.py +26 -5
  78. webscout/version.py +1 -1
  79. webscout/webscout_search.py +13 -6
  80. webscout/webscout_search_async.py +10 -8
  81. webscout/yep_search.py +13 -5
  82. {webscout-8.3.4.dist-info → webscout-8.3.6.dist-info}/METADATA +10 -149
  83. {webscout-8.3.4.dist-info → webscout-8.3.6.dist-info}/RECORD +88 -87
  84. webscout/Provider/Glider.py +0 -225
  85. webscout/Provider/OPENAI/README_AUTOPROXY.md +0 -238
  86. webscout/Provider/OPENAI/c4ai.py +0 -394
  87. webscout/Provider/OPENAI/glider.py +0 -330
  88. webscout/Provider/OPENAI/typegpt.py +0 -368
  89. webscout/Provider/OPENAI/uncovrAI.py +0 -477
  90. webscout/Provider/TTS/sthir.py +0 -94
  91. webscout/Provider/WritingMate.py +0 -273
  92. webscout/Provider/typegpt.py +0 -284
  93. webscout/Provider/uncovr.py +0 -333
  94. /webscout/Provider/{samurai.py → UNFINISHED/samurai.py} +0 -0
  95. {webscout-8.3.4.dist-info → webscout-8.3.6.dist-info}/WHEEL +0 -0
  96. {webscout-8.3.4.dist-info → webscout-8.3.6.dist-info}/entry_points.txt +0 -0
  97. {webscout-8.3.4.dist-info → webscout-8.3.6.dist-info}/licenses/LICENSE.md +0 -0
  98. {webscout-8.3.4.dist-info → webscout-8.3.6.dist-info}/top_level.txt +0 -0
@@ -49,11 +49,11 @@ def fetch_together_models():
49
49
  print(f"Model: {model_id}")
50
50
  print(f" Type: {model_type}")
51
51
  print(f" Context Length: {context_length}")
52
- if model.get("config"):
53
- config = model["config"]
54
- if config.get("stop"):
55
- print(f" Stop Tokens: {config['stop']}")
56
- print("-" * 40)
52
+ # if model.get("config"):
53
+ # config = model["config"]
54
+ # if config.get("stop"):
55
+ # print(f" Stop Tokens: {config['stop']}")
56
+ # print("-" * 40)
57
57
 
58
58
  print(f"\nSUMMARY:")
59
59
  print(f"Chat Models: {len(chat_models)}")
@@ -87,9 +87,4 @@ if __name__ == "__main__":
87
87
  result = fetch_together_models()
88
88
 
89
89
  if result:
90
- print(f"\n📊 Successfully fetched {len(result['all_models'])} models from Together.xyz")
91
-
92
- # Save to file
93
- with open("together_models.json", "w") as f:
94
- json.dump(result, f, indent=2)
95
- print("✅ Results saved to together_models.json")
90
+ print(f"\n📊 Successfully fetched {len(result['all_models'])} models from Together.xyz")
@@ -20,6 +20,7 @@ class Venice(Provider):
20
20
  AVAILABLE_MODELS = [
21
21
  "mistral-31-24b",
22
22
  "dolphin-3.0-mistral-24b",
23
+ "dolphin-3.0-mistral-24b-1dot1",
23
24
  "qwen2dot5-coder-32b",
24
25
  "deepseek-coder-v2-lite",
25
26
 
@@ -76,16 +76,6 @@ class WiseCat(Provider):
76
76
  )
77
77
  self.conversation.history_offset = history_offset
78
78
 
79
- @staticmethod
80
- def _wisecat_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
81
- """Extracts content from the WiseCat stream format '0:"..."'."""
82
- if isinstance(chunk, str):
83
- match = re.search(r'0:"(.*?)"', chunk)
84
- if match:
85
- # Decode potential unicode escapes like \u00e9
86
- content = match.group(1).encode().decode('unicode_escape')
87
- return content.replace('\\\\', '\\').replace('\\"', '"') # Handle escaped backslashes and quotes
88
- return None
89
79
 
90
80
  def ask(
91
81
  self,
@@ -138,19 +128,27 @@ class WiseCat(Provider):
138
128
  data=response.iter_content(chunk_size=None),
139
129
  intro_value=None,
140
130
  to_json=False,
141
- content_extractor=self._wisecat_extractor,
131
+ extract_regexes=[
132
+ r'0:"(.*?)"' # Extract content from 0:"..." format
133
+ ],
134
+ skip_regexes=[
135
+ r'\(\d+\.?\d*s\)', # Skip timing information like (0.3s), (1s), (0.5s)
136
+ r'\(\d+\.?\d*ms\)', # Skip millisecond timing like (300ms)
137
+ ],
142
138
  raw=raw
143
139
  )
144
140
  for content_chunk in processed_stream:
145
- # Always yield as string, even in raw mode
146
- if isinstance(content_chunk, bytes):
147
- content_chunk = content_chunk.decode('utf-8', errors='ignore')
148
- if raw:
149
- yield content_chunk
150
- else:
151
- if content_chunk and isinstance(content_chunk, str):
152
- streaming_text += content_chunk
153
- yield dict(text=content_chunk)
141
+ if content_chunk and isinstance(content_chunk, str):
142
+ # Content is already extracted by sanitize_stream
143
+ # Handle unicode escaping and quote unescaping
144
+ extracted_content = content_chunk.encode().decode('unicode_escape')
145
+ extracted_content = extracted_content.replace('\\\\', '\\').replace('\\"', '"')
146
+
147
+ if raw:
148
+ yield extracted_content
149
+ else:
150
+ streaming_text += extracted_content
151
+ yield dict(text=extracted_content)
154
152
  self.last_response.update(dict(text=streaming_text))
155
153
  self.conversation.update_chat_history(
156
154
  prompt, self.get_message(self.last_response)
@@ -34,13 +34,11 @@ from .llmchatco import LLMChatCo # Add new LLMChat.co provider
34
34
  from .talkai import *
35
35
  from .llama3mitril import *
36
36
  from .Marcus import *
37
- from .typegpt import *
38
37
  from .multichat import *
39
38
  from .Jadve import *
40
39
  from .chatglm import *
41
40
  from .hermes import *
42
41
  from .TextPollinationsAI import *
43
- from .Glider import *
44
42
  from .QwenLM import *
45
43
  from .granite import *
46
44
  from .WiseCat import *
@@ -54,7 +52,6 @@ from .Venice import *
54
52
  from .GithubChat import *
55
53
  from .copilot import *
56
54
  from .sonus import *
57
- from .uncovr import *
58
55
  from .LambdaChat import *
59
56
  from .ChatGPTClone import *
60
57
  from .VercelAI import *
@@ -68,7 +65,6 @@ from .scira_chat import *
68
65
  from .StandardInput import *
69
66
  from .toolbaz import Toolbaz
70
67
  from .scnet import SCNet
71
- from .WritingMate import WritingMate
72
68
  from .MCPCore import MCPCore
73
69
  from .TypliAI import TypliAI
74
70
  from .ChatSandbox import ChatSandbox
@@ -77,7 +73,6 @@ from .WrDoChat import WrDoChat
77
73
  from .Nemotron import NEMOTRON
78
74
  from .FreeGemini import FreeGemini
79
75
  from .Flowith import Flowith
80
- from .samurai import samurai
81
76
  from .lmarena import lmarena
82
77
  from .oivscode import oivscode
83
78
  from .XenAI import XenAI
@@ -87,94 +82,5 @@ from .TogetherAI import TogetherAI
87
82
  from .MiniMax import MiniMax
88
83
  from .Qodo import *
89
84
  from .monochat import MonoChat
90
- __all__ = [
91
- 'SCNet',
92
- 'MonoChat',
93
- 'MiniMax',
94
- 'QodoAI',
95
- 'GeminiProxy',
96
- 'TogetherAI',
97
- 'oivscode',
98
- 'DeepSeekAssistant',
99
- 'lmarena',
100
- 'XenAI',
101
- 'NEMOTRON',
102
- 'Flowith',
103
- 'samurai',
104
- 'FreeGemini',
105
- 'WrDoChat',
106
- 'GizAI',
107
- 'ChatSandbox',
108
- 'SciraAI',
109
- 'StandardInputAI',
110
- 'OpenGPT',
111
- 'Venice',
112
- 'ExaAI',
113
- 'Copilot',
114
- 'TwoAI',
115
- 'HeckAI',
116
- 'AllenAI',
117
- 'PerplexityLabs',
118
- 'AkashGPT',
119
- 'WritingMate',
120
- 'WiseCat',
121
- 'IBMGranite',
122
- 'QwenLM',
123
- 'LambdaChat',
124
- 'TextPollinationsAI',
125
- 'GliderAI',
126
- 'Cohere',
127
- 'REKA',
128
- 'GROQ',
129
- 'AsyncGROQ',
130
- 'OPENAI',
131
- 'AsyncOPENAI',
132
- 'KOBOLDAI',
133
- 'AsyncKOBOLDAI',
134
- 'BLACKBOXAI',
135
- 'GEMINI',
136
- 'DeepInfra',
137
- 'AI4Chat',
138
- 'OLLAMA',
139
- 'AndiSearch',
140
- 'Sambanova',
141
- 'KOALA',
142
- 'Meta',
143
- 'PiAI',
144
- 'Julius',
145
- 'YEPCHAT',
146
- 'Cloudflare',
147
- 'TurboSeek',
148
- 'TeachAnything',
149
- 'X0GPT',
150
- 'Cerebras',
151
- 'GEMINIAPI',
152
- 'SonusAI',
153
- 'Cleeai',
154
- 'Elmo',
155
- 'ChatGPTClone',
156
- 'TypefullyAI',
157
- 'Netwrck',
158
- 'LLMChat',
159
- 'LLMChatCo',
160
- 'Talkai',
161
- 'Llama3Mitril',
162
- 'Marcus',
163
- 'TypeGPT',
164
- 'Netwrck',
165
- 'MultiChatAI',
166
- 'JadveOpenAI',
167
- 'ChatGLM',
168
- 'NousHermes',
169
- 'FreeAIChat',
170
- 'GithubChat',
171
- 'UncovrAI',
172
- 'VercelAI',
173
- 'ExaChat',
174
- 'AskSteve',
175
- 'Aitopia',
176
- 'SearchChatAI',
177
- 'Toolbaz',
178
- 'MCPCore',
179
- 'TypliAI',
180
- ]
85
+ from .Kimi import Kimi
86
+ from .GptOss import GptOss
@@ -1,34 +1,48 @@
1
1
 
2
2
  import re
3
+
4
+ # Import trio before curl_cffi to prevent eventlet socket monkey-patching conflicts
5
+ # See: https://github.com/python-trio/trio/issues/3015
6
+ try:
7
+ import trio # noqa: F401
8
+ except ImportError:
9
+ pass # trio is optional, ignore if not available
10
+ import json
11
+ from typing import Any, Dict, Generator, List, Optional, Union
12
+
3
13
  import curl_cffi
4
14
  from curl_cffi.requests import Session
5
- import json
6
- import os
7
- from typing import Any, Dict, Optional, Generator, List, Union
8
- from webscout.AIutel import Optimizers, Conversation, AwesomePrompts, sanitize_stream # Import sanitize_stream
9
- from webscout.AIbase import Provider
15
+
10
16
  from webscout import exceptions
17
+ from webscout.AIbase import Provider
18
+ from webscout.AIutel import ( # Import sanitize_stream
19
+ AwesomePrompts,
20
+ Conversation,
21
+ Optimizers,
22
+ sanitize_stream,
23
+ )
11
24
  from webscout.litagent import LitAgent as UserAgent
12
25
 
26
+
13
27
  class Cerebras(Provider):
14
28
  """
15
29
  A class to interact with the Cerebras API using a cookie for authentication.
16
30
  """
17
-
31
+
18
32
  AVAILABLE_MODELS = [
19
- "llama3.1-8b",
20
- "llama-3.3-70b",
21
- "deepseek-r1-distill-llama-70b",
22
- "llama-4-scout-17b-16e-instruct",
33
+ "qwen-3-coder-480b",
34
+ "qwen-3-235b-a22b-instruct-2507",
35
+ "qwen-3-235b-a22b-thinking-2507",
23
36
  "qwen-3-32b",
24
-
25
-
37
+ "llama-3.3-70b",
38
+ "llama-4-maverick-17b-128e-instruct"
26
39
  ]
27
40
 
28
41
  def __init__(
29
42
  self,
43
+ cookie_path: str = None,
30
44
  is_conversation: bool = True,
31
- max_tokens: int = 2049,
45
+ max_tokens: int = 40000,
32
46
  timeout: int = 30,
33
47
  intro: str = None,
34
48
  filepath: str = None,
@@ -36,9 +50,11 @@ class Cerebras(Provider):
36
50
  proxies: dict = {},
37
51
  history_offset: int = 10250,
38
52
  act: str = None,
39
- cookie_path: str = "cookie.json",
40
- model: str = "llama3.1-8b",
53
+ api_key: str = None,
54
+ model: str = "qwen-3-coder-480b",
41
55
  system_prompt: str = "You are a helpful assistant.",
56
+ temperature: float = 0.7,
57
+ top_p: float = 0.8,
42
58
  ):
43
59
  # Validate model choice
44
60
  if model not in self.AVAILABLE_MODELS:
@@ -52,15 +68,26 @@ class Cerebras(Provider):
52
68
  self.system_prompt = system_prompt
53
69
  self.is_conversation = is_conversation
54
70
  self.max_tokens_to_sample = max_tokens
71
+ self.temperature = temperature
72
+ self.top_p = top_p
55
73
  self.last_response = {}
56
74
 
57
75
  self.session = Session() # Initialize curl_cffi session
58
76
 
59
- # Get API key first
60
- try:
61
- self.api_key = self.get_demo_api_key(cookie_path)
62
- except Exception as e:
63
- raise exceptions.APIConnectionError(f"Failed to initialize Cerebras client: {e}")
77
+ # Handle API key - either provided directly or retrieved from cookies
78
+ if api_key:
79
+ self.api_key = api_key.strip()
80
+ # Basic validation for API key format
81
+ if not self.api_key or len(self.api_key) < 10:
82
+ raise ValueError("Invalid API key format. API key must be at least 10 characters long.")
83
+ elif cookie_path:
84
+ # Get API key from cookies
85
+ try:
86
+ self.api_key = self.get_demo_api_key(cookie_path)
87
+ except Exception as e:
88
+ raise exceptions.APIConnectionError(f"Failed to initialize Cerebras client: {e}")
89
+ else:
90
+ raise ValueError("Either api_key must be provided or cookie_path must be specified")
64
91
 
65
92
  # Initialize optimizers
66
93
  self.__available_optimizers = (
@@ -72,16 +99,16 @@ class Cerebras(Provider):
72
99
  # Initialize conversation settings
73
100
  Conversation.intro = (
74
101
  AwesomePrompts().get_act(
75
- act, raise_not_found=True, default=None, case_insensitive=True
102
+ act, raise_not_found=True, default="You are a helpful assistant.", case_insensitive=True
76
103
  )
77
104
  if act
78
- else None
105
+ else "You are a helpful assistant."
79
106
  )
80
107
  self.conversation = Conversation(
81
108
  is_conversation, self.max_tokens_to_sample, filepath, update_file
82
109
  )
83
110
  self.conversation.history_offset = history_offset
84
-
111
+
85
112
  # Apply proxies to the session
86
113
  self.session.proxies = proxies
87
114
 
@@ -105,8 +132,10 @@ class Cerebras(Provider):
105
132
  return chunk.get("choices", [{}])[0].get("delta", {}).get("content")
106
133
  return None
107
134
 
108
- def get_demo_api_key(self, cookie_path: str) -> str: # Keep this using requests or switch to curl_cffi
135
+ def get_demo_api_key(self, cookie_path: str = None) -> str: # Keep this using requests or switch to curl_cffi
109
136
  """Retrieves the demo API key using the provided cookie."""
137
+ if not cookie_path:
138
+ raise ValueError("cookie_path must be provided when using cookie-based authentication")
110
139
  try:
111
140
  with open(cookie_path, "r") as file:
112
141
  cookies = {item["name"]: item["value"] for item in json.load(file)}
@@ -159,7 +188,10 @@ class Cerebras(Provider):
159
188
  payload = {
160
189
  "model": self.model,
161
190
  "messages": messages,
162
- "stream": stream
191
+ "stream": stream,
192
+ "max_tokens": self.max_tokens_to_sample,
193
+ "temperature": self.temperature,
194
+ "top_p": self.top_p
163
195
  }
164
196
 
165
197
  try:
@@ -197,8 +229,26 @@ class Cerebras(Provider):
197
229
 
198
230
  except curl_cffi.CurlError as e:
199
231
  raise exceptions.APIConnectionError(f"Request failed (CurlError): {e}") from e
200
- except Exception as e: # Catch other potential errors
201
- raise exceptions.APIConnectionError(f"Request failed: {e}")
232
+ except Exception as e:
233
+ # Check if it's an HTTP error with status code
234
+ if hasattr(e, 'response') and hasattr(e.response, 'status_code'):
235
+ status_code = e.response.status_code
236
+ if status_code == 401:
237
+ raise exceptions.APIConnectionError(
238
+ "Authentication failed (401): Invalid API key. Please check your API key and try again."
239
+ ) from e
240
+ elif status_code == 403:
241
+ raise exceptions.APIConnectionError(
242
+ "Access forbidden (403): Your API key may not have permission to access this resource."
243
+ ) from e
244
+ elif status_code == 429:
245
+ raise exceptions.APIConnectionError(
246
+ "Rate limit exceeded (429): Too many requests. Please wait and try again."
247
+ ) from e
248
+ else:
249
+ raise exceptions.APIConnectionError(f"HTTP {status_code} error: {e}") from e
250
+ else:
251
+ raise exceptions.APIConnectionError(f"Request failed: {e}") from e
202
252
 
203
253
  def ask(
204
254
  self,
@@ -225,7 +275,7 @@ class Cerebras(Provider):
225
275
 
226
276
  try:
227
277
  response = self._make_request(messages, stream)
228
-
278
+
229
279
  if stream:
230
280
  # Wrap the generator to yield dicts or raw strings
231
281
  def stream_wrapper():
@@ -256,7 +306,7 @@ class Cerebras(Provider):
256
306
  """Chat with the model."""
257
307
  # Ask returns a generator for stream=True, dict/str for stream=False
258
308
  response_gen_or_dict = self.ask(prompt, stream, raw=False, optimizer=optimizer, conversationally=conversationally)
259
-
309
+
260
310
  if stream:
261
311
  # Wrap the generator from ask() to get message text
262
312
  def stream_wrapper():
@@ -276,14 +326,14 @@ class Cerebras(Provider):
276
326
 
277
327
  if __name__ == "__main__":
278
328
  from rich import print
279
-
329
+
280
330
  # Example usage
281
331
  cerebras = Cerebras(
282
- cookie_path=r'cookies.json',
283
- model='llama3.1-8b',
332
+ api_key='csk-**********************', # Replace with your actual API key
333
+ model='qwen-3-235b-a22b-instruct-2507',
284
334
  system_prompt="You are a helpful AI assistant."
285
335
  )
286
-
336
+
287
337
  # Test with streaming
288
338
  response = cerebras.chat("Hello!", stream=True)
289
339
  for chunk in response:
@@ -1,17 +1,21 @@
1
- import os
2
- import json
3
- import base64
4
1
  import asyncio
2
+ import base64
3
+ import json
4
+ import os
5
+ from typing import Any, Dict, Generator, Union
5
6
  from urllib.parse import quote
6
- from typing import Optional, Dict, Any, List, Union, Generator
7
7
 
8
- from curl_cffi.requests import Session, CurlWsFlag
8
+ # Import trio before curl_cffi to prevent eventlet socket monkey-patching conflicts
9
+ # See: https://github.com/python-trio/trio/issues/3015
10
+ try:
11
+ import trio # noqa: F401
12
+ except ImportError:
13
+ pass # trio is optional, ignore if not available
14
+ from curl_cffi.requests import CurlWsFlag, Session
9
15
 
10
- from webscout.AIutel import Optimizers
11
- from webscout.AIutel import Conversation
12
- from webscout.AIutel import AwesomePrompts, sanitize_stream
13
- from webscout.AIbase import Provider, AsyncProvider
14
16
  from webscout import exceptions
17
+ from webscout.AIbase import Provider
18
+ from webscout.AIutel import AwesomePrompts, Conversation, Optimizers
15
19
  from webscout.litagent import LitAgent
16
20
 
17
21
  try:
@@ -41,12 +45,17 @@ class Copilot(Provider):
41
45
  """
42
46
  A class to interact with the Microsoft Copilot API.
43
47
  """
44
-
48
+
45
49
  label = "Microsoft Copilot"
46
50
  url = "https://copilot.microsoft.com"
47
51
  websocket_url = "wss://copilot.microsoft.com/c/api/chat?api-version=2"
48
52
  conversation_url = f"{url}/c/api/conversations"
49
- AVAILABLE_MODELS = ["Copilot", "Think Deeper"]
53
+ AVAILABLE_MODELS = ["Copilot", "Think Deeper", "Smart"]
54
+ MODEL_ALIASES = {
55
+ "gpt-4o": "Copilot",
56
+ "o4-mini": "Think Deeper",
57
+ "gpt-5": "Smart",
58
+ }
50
59
  _access_token: str = None
51
60
  _cookies: dict = None
52
61
 
@@ -64,9 +73,12 @@ class Copilot(Provider):
64
73
  model: str = "Copilot"
65
74
  ):
66
75
  """Initializes the Copilot API client."""
67
- if model not in self.AVAILABLE_MODELS:
76
+ # Map alias to real model name if needed
77
+ real_model = self.MODEL_ALIASES.get(model, model)
78
+ if real_model not in self.AVAILABLE_MODELS:
68
79
  raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
69
-
80
+ self.model = real_model
81
+
70
82
  # Use LitAgent for user-agent
71
83
  self.headers = {
72
84
  'User-Agent': LitAgent().random(),
@@ -79,7 +91,7 @@ class Copilot(Provider):
79
91
  'Sec-Fetch-Mode': 'cors',
80
92
  'Sec-Fetch-Site': 'same-origin',
81
93
  }
82
-
94
+
83
95
  self.is_conversation = is_conversation
84
96
  self.max_tokens_to_sample = max_tokens
85
97
  self.timeout = timeout
@@ -253,6 +265,12 @@ class Copilot(Provider):
253
265
  # WebSocket connection
254
266
  wss = session.ws_connect(websocket_url)
255
267
  wss.send(json.dumps({"event": "setOptions", "supportedCards": ["weather", "local", "image", "sports", "video", "ads", "finance"], "ads": {"supportedTypes": ["multimedia", "product", "tourActivity", "propertyPromotion", "text"]}}))
268
+ if self.model == "Smart":
269
+ mode_value = "smart"
270
+ elif "Think" in self.model:
271
+ mode_value = "reasoning"
272
+ else:
273
+ mode_value = "chat"
256
274
  wss.send(json.dumps({
257
275
  "event": "send",
258
276
  "conversationId": conversation_id,
@@ -260,7 +278,8 @@ class Copilot(Provider):
260
278
  "type": "text",
261
279
  "text": conversation_prompt,
262
280
  }],
263
- "mode": "reasoning" if "Think" in self.model else "chat"
281
+ "mode": mode_value,
282
+ "model": self.model
264
283
  }).encode(), CurlWsFlag.TEXT)
265
284
 
266
285
  # Event-driven response loop
@@ -307,8 +326,8 @@ class Copilot(Provider):
307
326
  **kwargs
308
327
  ) -> Union[str, Generator]:
309
328
  def for_stream():
310
- for response in self.ask(prompt, True, optimizer=optimizer,
311
- conversationally=conversationally,
329
+ for response in self.ask(prompt, True, optimizer=optimizer,
330
+ conversationally=conversationally,
312
331
  images=images, api_key=api_key, **kwargs):
313
332
  if isinstance(response, dict):
314
333
  if "text" in response:
@@ -320,13 +339,13 @@ class Copilot(Provider):
320
339
  yield "\nSuggested follow-up questions:\n"
321
340
  for suggestion in response["suggestions"]:
322
341
  yield f"- {suggestion}\n"
323
-
342
+
324
343
  def for_non_stream():
325
- response = self.ask(prompt, False, optimizer=optimizer,
344
+ response = self.ask(prompt, False, optimizer=optimizer,
326
345
  conversationally=conversationally,
327
346
  images=images, api_key=api_key, **kwargs)
328
347
  return self.get_message(response)
329
-
348
+
330
349
  return for_stream() if stream else for_non_stream()
331
350
 
332
351
  def get_message(self, response: dict) -> str:
@@ -379,7 +398,7 @@ def readHAR(url: str):
379
398
  for file in os.listdir(path):
380
399
  if file.endswith(".har"):
381
400
  har_files.append(os.path.join(path, file))
382
-
401
+
383
402
  for path in har_files:
384
403
  with open(path, 'rb') as file:
385
404
  try:
@@ -416,7 +435,7 @@ async def get_nodriver(proxy=None, user_data_dir=None):
416
435
 
417
436
  if __name__ == "__main__":
418
437
  from rich import print
419
- ai = Copilot(timeout=900, model="Think Deeper")
438
+ ai = Copilot(timeout=900, model="gpt-5")
420
439
  response = ai.chat(input("> "), stream=True)
421
440
  for chunk in response:
422
- print(chunk, end="", flush=True)
441
+ print(chunk, end="", flush=True)
@@ -43,6 +43,8 @@ class SciraAI(Provider):
43
43
  "claude-4-opus-20250514": "scira-opus",
44
44
  "claude-4-opus-20250514-pro": "scira-opus-pro",
45
45
  "meta-llama/llama-4-maverick-17b-128e-instruct": "scira-llama-4",
46
+ "kimi-k2-instruct": "scira-kimi-k2",
47
+ "scira-kimi-k2": "kimi-k2-instruct",
46
48
  }
47
49
 
48
50
  # Reverse mapping: Scira format to actual model names
@@ -57,6 +59,8 @@ class SciraAI(Provider):
57
59
  SCIRA_TO_MODEL["scira-qwen-30b"] = "qwen3-30b-a3b"
58
60
  SCIRA_TO_MODEL["scira-deepseek-v3"] = "deepseek-v3-0324"
59
61
  SCIRA_TO_MODEL["scira-grok-4"] = "grok-4"
62
+ SCIRA_TO_MODEL["scira-kimi-k2"] = "kimi-k2-instruct"
63
+ SCIRA_TO_MODEL["kimi-k2-instruct"] = "scira-kimi-k2"
60
64
  MODEL_MAPPING["claude-4-opus-20250514-pro"] = "scira-opus-pro"
61
65
  # Available models list (actual model names + scira aliases)
62
66
  AVAILABLE_MODELS = list(MODEL_MAPPING.keys()) + list(SCIRA_TO_MODEL.keys())
@@ -34,11 +34,13 @@ class Toolbaz(Provider):
34
34
  "Llama-4-Maverick",
35
35
  "Llama-4-Scout",
36
36
  "Llama-3.3-70B",
37
+ "gpt-oss-120b",
37
38
  "Qwen2.5-72B",
38
39
  "grok-2-1212",
39
40
  "grok-3-beta",
40
- "toolbaz_v3.5_pro",
41
41
  "toolbaz_v3",
42
+ "toolbaz_v3.5_pro",
43
+ "toolbaz_v4",
42
44
  "mixtral_8x22b",
43
45
  "L3-70B-Euryale-v2.1",
44
46
  "midnight-rose",
@@ -112,12 +114,6 @@ class Toolbaz(Provider):
112
114
  )
113
115
  self.conversation.history_offset = history_offset
114
116
 
115
- @staticmethod
116
- def _toolbaz_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
117
- """Removes [model:...] tags from a string chunk."""
118
- if isinstance(chunk, str):
119
- return re.sub(r"\[model:.*?\]", "", chunk)
120
- return None
121
117
 
122
118
  def random_string(self, length):
123
119
  return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
@@ -217,14 +213,14 @@ class Toolbaz(Provider):
217
213
 
218
214
  streaming_text = ""
219
215
 
220
- # Use sanitize_stream with the custom extractor
216
+ # Use sanitize_stream with skip_regexes to remove [model:...] tags
221
217
  # It will decode bytes and yield processed string chunks
222
218
  processed_stream = sanitize_stream(
223
219
  data=resp.iter_content(chunk_size=None), # Pass byte iterator
224
220
  intro_value=None, # No simple prefix
225
221
  to_json=False, # Content is text
226
- content_extractor=self._toolbaz_extractor, # Use the tag remover
227
- yield_raw_on_error=True, # Yield even if extractor somehow fails (though unlikely for regex)
222
+ skip_regexes=[r"\[model:.*?\]"], # Skip [model:...] tags
223
+ yield_raw_on_error=True, # Yield even if regex processing fails
228
224
  raw=raw
229
225
  )
230
226
 
@@ -71,16 +71,6 @@ class TypefullyAI(Provider):
71
71
  )
72
72
  self.conversation.history_offset = history_offset
73
73
 
74
- @staticmethod
75
- def _typefully_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
76
- if isinstance(chunk, str):
77
- if isinstance(chunk, bytes):
78
- chunk = chunk.decode('utf-8', errors='replace')
79
- match = re.search(r'0:"(.*?)"', chunk)
80
- if match:
81
- content = match.group(1).encode().decode('unicode_escape')
82
- return content.replace('\\', '\\').replace('\\"', '"')
83
- return None
84
74
 
85
75
  def ask(
86
76
  self,
@@ -125,7 +115,7 @@ class TypefullyAI(Provider):
125
115
  data=response.iter_content(chunk_size=None),
126
116
  intro_value=None,
127
117
  to_json=False,
128
- content_extractor=self._typefully_extractor,
118
+ extract_regexes=[r'0:"(.*?)"'],
129
119
  raw=raw
130
120
  )
131
121
  for content_chunk in processed_stream: