webscout 8.2.4__py3-none-any.whl → 8.2.5__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 (80) hide show
  1. webscout/Extra/gguf.py +2 -0
  2. webscout/Provider/AISEARCH/scira_search.py +2 -5
  3. webscout/Provider/Aitopia.py +75 -51
  4. webscout/Provider/AllenAI.py +64 -67
  5. webscout/Provider/ChatGPTClone.py +33 -34
  6. webscout/Provider/ChatSandbox.py +342 -0
  7. webscout/Provider/Cloudflare.py +79 -32
  8. webscout/Provider/Deepinfra.py +69 -56
  9. webscout/Provider/ElectronHub.py +48 -39
  10. webscout/Provider/ExaChat.py +36 -20
  11. webscout/Provider/GPTWeb.py +24 -18
  12. webscout/Provider/GithubChat.py +52 -49
  13. webscout/Provider/GizAI.py +283 -0
  14. webscout/Provider/Glider.py +39 -28
  15. webscout/Provider/Groq.py +48 -20
  16. webscout/Provider/HeckAI.py +18 -36
  17. webscout/Provider/Jadve.py +30 -37
  18. webscout/Provider/LambdaChat.py +36 -59
  19. webscout/Provider/MCPCore.py +18 -21
  20. webscout/Provider/Marcus.py +23 -14
  21. webscout/Provider/Netwrck.py +35 -26
  22. webscout/Provider/OPENAI/__init__.py +1 -1
  23. webscout/Provider/OPENAI/exachat.py +4 -0
  24. webscout/Provider/OPENAI/scirachat.py +2 -4
  25. webscout/Provider/OPENAI/textpollinations.py +20 -22
  26. webscout/Provider/OPENAI/toolbaz.py +1 -0
  27. webscout/Provider/PI.py +22 -13
  28. webscout/Provider/StandardInput.py +42 -30
  29. webscout/Provider/TeachAnything.py +16 -7
  30. webscout/Provider/TextPollinationsAI.py +78 -76
  31. webscout/Provider/TwoAI.py +120 -88
  32. webscout/Provider/TypliAI.py +305 -0
  33. webscout/Provider/Venice.py +24 -22
  34. webscout/Provider/VercelAI.py +31 -12
  35. webscout/Provider/__init__.py +7 -7
  36. webscout/Provider/asksteve.py +53 -44
  37. webscout/Provider/cerebras.py +77 -31
  38. webscout/Provider/chatglm.py +47 -37
  39. webscout/Provider/elmo.py +38 -32
  40. webscout/Provider/granite.py +24 -21
  41. webscout/Provider/hermes.py +27 -20
  42. webscout/Provider/learnfastai.py +25 -20
  43. webscout/Provider/llmchatco.py +48 -78
  44. webscout/Provider/multichat.py +13 -3
  45. webscout/Provider/scira_chat.py +49 -30
  46. webscout/Provider/scnet.py +23 -20
  47. webscout/Provider/searchchat.py +16 -24
  48. webscout/Provider/sonus.py +37 -39
  49. webscout/Provider/toolbaz.py +24 -46
  50. webscout/Provider/turboseek.py +37 -41
  51. webscout/Provider/typefully.py +30 -22
  52. webscout/Provider/typegpt.py +47 -51
  53. webscout/Provider/uncovr.py +46 -40
  54. webscout/cli.py +256 -0
  55. webscout/conversation.py +0 -2
  56. webscout/exceptions.py +3 -0
  57. webscout/version.py +1 -1
  58. {webscout-8.2.4.dist-info → webscout-8.2.5.dist-info}/METADATA +166 -45
  59. {webscout-8.2.4.dist-info → webscout-8.2.5.dist-info}/RECORD +63 -76
  60. {webscout-8.2.4.dist-info → webscout-8.2.5.dist-info}/WHEEL +1 -1
  61. webscout-8.2.5.dist-info/entry_points.txt +3 -0
  62. {webscout-8.2.4.dist-info → webscout-8.2.5.dist-info}/top_level.txt +0 -1
  63. inferno/__init__.py +0 -6
  64. inferno/__main__.py +0 -9
  65. inferno/cli.py +0 -6
  66. inferno/lol.py +0 -589
  67. webscout/Local/__init__.py +0 -12
  68. webscout/Local/__main__.py +0 -9
  69. webscout/Local/api.py +0 -576
  70. webscout/Local/cli.py +0 -516
  71. webscout/Local/config.py +0 -75
  72. webscout/Local/llm.py +0 -287
  73. webscout/Local/model_manager.py +0 -253
  74. webscout/Local/server.py +0 -721
  75. webscout/Local/utils.py +0 -93
  76. webscout/Provider/Chatify.py +0 -175
  77. webscout/Provider/askmyai.py +0 -158
  78. webscout/Provider/gaurish.py +0 -244
  79. webscout-8.2.4.dist-info/entry_points.txt +0 -5
  80. {webscout-8.2.4.dist-info → webscout-8.2.5.dist-info}/licenses/LICENSE.md +0 -0
webscout/Extra/gguf.py CHANGED
@@ -1,6 +1,8 @@
1
1
  """
2
2
  Convert Hugging Face models to GGUF format with advanced features.
3
3
 
4
+ For detailed documentation, see: webscout/Extra/gguf.md
5
+
4
6
  >>> python -m webscout.Extra.gguf convert -m "OEvortex/HelpingAI-Lite-1.5T" -q "q4_k_m,q5_k_m"
5
7
  >>> # With upload options:
6
8
  >>> python -m webscout.Extra.gguf convert -m "your-model" -u "username" -t "token" -q "q4_k_m"
@@ -69,17 +69,14 @@ class Scira(AISearch):
69
69
  """
70
70
 
71
71
  AVAILABLE_MODELS = {
72
- "scira-default": "Grok3",
73
- "scira-grok-3-mini": "Grok3-mini", # thinking model
72
+ "scira-default": "Grok3-mini", # thinking model
73
+ "scira-grok-3": "Grok3",
74
74
  "scira-vision" : "Grok2-Vision", # vision model
75
75
  "scira-4.1-mini": "GPT4.1-mini",
76
76
  "scira-qwq": "QWQ-32B",
77
77
  "scira-o4-mini": "o4-mini",
78
78
  "scira-google": "gemini 2.5 flash"
79
-
80
-
81
79
  }
82
-
83
80
  def __init__(
84
81
  self,
85
82
  timeout: int = 60,
@@ -1,4 +1,5 @@
1
- import requests
1
+ from curl_cffi import CurlError
2
+ from curl_cffi.requests import Session
2
3
  import json
3
4
  import uuid
4
5
  import time
@@ -6,8 +7,8 @@ import hashlib
6
7
  from typing import Any, Dict, Optional, Generator, Union
7
8
 
8
9
  from webscout.AIutel import Optimizers
9
- from webscout.AIutel import Conversation
10
- from webscout.AIutel import AwesomePrompts, sanitize_stream
10
+ from webscout.AIutel import Conversation, sanitize_stream # Import sanitize_stream
11
+ from webscout.AIutel import AwesomePrompts
11
12
  from webscout.AIbase import Provider, AsyncProvider
12
13
  from webscout import exceptions
13
14
  from webscout.litagent import LitAgent
@@ -67,9 +68,9 @@ class Aitopia(Provider):
67
68
  "user-agent": self.fingerprint["user_agent"]
68
69
  }
69
70
 
70
- self.session = requests.Session()
71
+ self.session = Session() # Use curl_cffi Session
71
72
  self.session.headers.update(self.headers)
72
- self.session.proxies.update(proxies)
73
+ self.session.proxies = proxies # Assign proxies directly
73
74
 
74
75
  self.is_conversation = is_conversation
75
76
  self.max_tokens_to_sample = max_tokens
@@ -129,6 +130,20 @@ class Aitopia(Provider):
129
130
  random_str = str(uuid.uuid4()) + str(time.time())
130
131
  return hashlib.md5(random_str.encode()).hexdigest()
131
132
 
133
+ @staticmethod
134
+ def _aitopia_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
135
+ """Extracts content from Aitopia stream JSON objects."""
136
+ if isinstance(chunk, dict):
137
+ # Handle Claude 3 Haiku response format
138
+ if "delta" in chunk and "text" in chunk["delta"]:
139
+ return chunk["delta"]["text"]
140
+ # Handle GPT-4o Mini response format
141
+ elif "choices" in chunk and "0" in chunk["choices"]:
142
+ return chunk["choices"]["0"]["delta"].get("content")
143
+ # Add other potential formats here if needed
144
+ return None
145
+
146
+
132
147
  def ask(
133
148
  self,
134
149
  prompt: str,
@@ -184,63 +199,72 @@ class Aitopia(Provider):
184
199
  }
185
200
 
186
201
  def for_stream():
202
+ streaming_text = "" # Initialize outside try block
187
203
  try:
188
- with requests.post(self.url, headers=self.headers, json=payload, stream=True, timeout=self.timeout) as response:
189
- if response.status_code != 200:
190
- raise exceptions.FailedToGenerateResponseError(
191
- f"Request failed with status code {response.status_code}"
192
- )
193
-
194
- streaming_text = ""
195
- for line in response.iter_lines():
196
- if line:
197
- line = line.decode('utf-8')
198
- if line.startswith('data: '):
199
- data = line[6:]
200
- if data == '[DONE]':
201
- break
202
- try:
203
- json_data = json.loads(data)
204
-
205
- # Handle Claude 3 Haiku response format
206
- if "delta" in json_data and "text" in json_data["delta"]:
207
- content = json_data["delta"]["text"]
208
- if content:
209
- streaming_text += content
210
- resp = dict(text=content)
211
- yield resp if raw else resp
212
- # Handle GPT-4o Mini response format
213
- elif "choices" in json_data and "0" in json_data["choices"]:
214
- content = json_data["choices"]["0"]["delta"].get("content", "")
215
- if content:
216
- streaming_text += content
217
- resp = dict(text=content)
218
- yield resp if raw else resp
219
- except json.JSONDecodeError:
220
- continue
221
-
204
+ response = self.session.post(
205
+ self.url, headers=self.headers, json=payload, stream=True, timeout=self.timeout,
206
+ impersonate="chrome120" # Add impersonate
207
+ )
208
+ response.raise_for_status()
209
+
210
+ # Use sanitize_stream
211
+ processed_stream = sanitize_stream(
212
+ data=response.iter_content(chunk_size=None), # Pass byte iterator
213
+ intro_value="data:",
214
+ to_json=True, # Stream sends JSON
215
+ skip_markers=["[DONE]"],
216
+ content_extractor=self._aitopia_extractor, # Use the specific extractor
217
+ yield_raw_on_error=False # Skip non-JSON lines or lines where extractor fails
218
+ )
219
+
220
+ for content_chunk in processed_stream:
221
+ # content_chunk is the string extracted by _aitopia_extractor
222
+ if content_chunk and isinstance(content_chunk, str):
223
+ streaming_text += content_chunk
224
+ resp = dict(text=content_chunk)
225
+ yield resp if not raw else content_chunk
226
+
227
+ except CurlError as e:
228
+ raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {str(e)}") from e
229
+ except Exception as e:
230
+ raise exceptions.FailedToGenerateResponseError(f"Request failed: {str(e)}")
231
+ finally:
232
+ # Update history after stream finishes or fails
233
+ if streaming_text:
222
234
  self.last_response = {"text": streaming_text}
223
235
  self.conversation.update_chat_history(prompt, streaming_text)
224
-
225
- except requests.RequestException as e:
226
- raise exceptions.FailedToGenerateResponseError(f"Request failed: {str(e)}")
227
236
 
228
237
  def for_non_stream():
229
238
  try:
230
- response = requests.post(self.url, headers=self.headers, json=payload, timeout=self.timeout)
231
- if response.status_code != 200:
232
- raise exceptions.FailedToGenerateResponseError(
233
- f"Request failed with status code {response.status_code}"
234
- )
239
+ response = self.session.post(
240
+ self.url, headers=self.headers, json=payload, timeout=self.timeout,
241
+ impersonate="chrome120" # Add impersonate
242
+ )
243
+ response.raise_for_status()
244
+
245
+ response_text_raw = response.text # Get raw text
246
+
247
+ # Use sanitize_stream to parse the non-streaming JSON response
248
+ # Assuming non-stream uses the GPT format based on original code
249
+ processed_stream = sanitize_stream(
250
+ data=response_text_raw,
251
+ to_json=True, # Parse the whole text as JSON
252
+ intro_value=None,
253
+ content_extractor=lambda chunk: chunk.get("choices", [{}])[0].get("message", {}).get("content") if isinstance(chunk, dict) else None,
254
+ yield_raw_on_error=False
255
+ )
256
+ # Extract the single result
257
+ content = next(processed_stream, None)
258
+ content = content if isinstance(content, str) else "" # Ensure it's a string
235
259
 
236
- response_data = response.json()
237
- if 'choices' in response_data and len(response_data['choices']) > 0:
238
- content = response_data['choices'][0].get('message', {}).get('content', '')
260
+ if content: # Check if content was successfully extracted
239
261
  self.last_response = {"text": content}
240
262
  self.conversation.update_chat_history(prompt, content)
241
263
  return {"text": content}
242
264
  else:
243
- raise exceptions.FailedToGenerateResponseError("No response content found")
265
+ raise exceptions.FailedToGenerateResponseError("No response content found or failed to parse")
266
+ except CurlError as e:
267
+ raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {e}") from e
244
268
  except Exception as e:
245
269
  raise exceptions.FailedToGenerateResponseError(f"Request failed: {e}")
246
270
 
@@ -156,19 +156,15 @@ class AllenAI(Provider):
156
156
  err_text = getattr(e, 'response', None) and getattr(e.response, 'text', '')
157
157
  return {"client": temp_id, "error": f"{type(e).__name__}: {e} - {err_text}"}
158
158
 
159
-
160
- def parse_stream(self, raw_data):
161
- """Parse the raw streaming data according to the JS implementation"""
162
- result = ""
163
- for line in raw_data.splitlines():
164
- try:
165
- parsed = json.loads(line)
166
- # Check if message starts with msg_ pattern
167
- if parsed.get("message", "").startswith("msg_"):
168
- result += parsed.get("content", "")
169
- except:
170
- continue
171
- return result
159
+ @staticmethod
160
+ def _allenai_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
161
+ """Extracts content from AllenAI stream JSON objects."""
162
+ if isinstance(chunk, dict):
163
+ if chunk.get("message", "").startswith("msg_") and "content" in chunk:
164
+ return chunk.get("content")
165
+ elif "message" in chunk and chunk.get("content"): # Legacy handling
166
+ return chunk.get("content")
167
+ return None
172
168
 
173
169
  def ask(
174
170
  self,
@@ -269,57 +265,49 @@ class AllenAI(Provider):
269
265
  )
270
266
  response.raise_for_status() # Check for HTTP errors
271
267
 
272
- # Iterate over bytes and decode manually
273
- for chunk_bytes in response.iter_content(chunk_size=None): # Process byte chunks
274
- if not chunk_bytes:
275
- continue
276
-
277
- try:
278
- decoded = chunk_bytes.decode('utf-8')
279
- for line in decoded.splitlines(): # Process lines within the chunk
280
- line = line.strip()
281
- if not line:
282
- continue
283
-
284
- data = json.loads(line)
285
-
286
- # Check for message pattern from JS implementation
287
- if isinstance(data, dict):
288
- content_found = False
289
- if data.get("message", "").startswith("msg_") and "content" in data:
290
- content = data.get("content", "")
291
- content_found = True
292
- # Legacy handling for older API
293
- elif "message" in data and data.get("content"):
294
- content = data.get("content")
295
- content_found = True
296
-
297
- if content_found and content:
298
- streaming_text += content
299
- resp = dict(text=content)
300
- yield resp if not raw else content
301
-
302
- # Update parent ID if present
303
- if data.get("id"):
304
- current_parent = data.get("id")
305
- elif data.get("children"):
306
- for child in data["children"]:
307
- if child.get("role") == "assistant":
308
- current_parent = child.get("id")
309
- break
310
-
311
- # Handle completion
312
- if data.get("final") or data.get("finish_reason") == "stop":
313
- if current_parent:
314
- self.parent = current_parent
315
-
316
- # Update conversation history
317
- self.conversation.update_chat_history(prompt, streaming_text)
318
- self.last_response = {"text": streaming_text}
319
- return # End the generator
268
+ # Use sanitize_stream
269
+ processed_stream = sanitize_stream(
270
+ data=response.iter_content(chunk_size=None), # Pass byte iterator
271
+ intro_value=None, # No prefix
272
+ to_json=True, # Stream sends JSON lines
273
+ content_extractor=self._allenai_extractor, # Use the specific extractor
274
+ yield_raw_on_error=False # Skip non-JSON lines or lines where extractor fails
275
+ )
276
+
277
+ for content_chunk in processed_stream:
278
+ # content_chunk is the string extracted by _allenai_extractor
279
+ if content_chunk and isinstance(content_chunk, str):
280
+ streaming_text += content_chunk
281
+ resp = dict(text=content_chunk)
282
+ yield resp if not raw else content_chunk
320
283
 
321
- except (json.JSONDecodeError, UnicodeDecodeError):
322
- continue # Ignore lines/chunks that are not valid JSON or cannot be decoded
284
+ # Try to extract parent ID from the *last* raw line (less reliable than before)
285
+ # This part is tricky as sanitize_stream consumes the raw lines.
286
+ # We might need to re-fetch or adjust if parent ID is critical per stream.
287
+ # For now, we'll rely on the non-stream request to update parent ID more reliably.
288
+ # Example placeholder logic (might not work reliably):
289
+ try:
290
+ last_line_data = json.loads(response.text.splitlines()[-1]) # Get last line if possible
291
+ if last_line_data.get("id"):
292
+ current_parent = last_line_data.get("id")
293
+ elif last_line_data.get("children"):
294
+ for child in last_line_data["children"]: # Use last_line_data here
295
+ if child.get("role") == "assistant":
296
+ current_parent = child.get("id")
297
+ break
298
+
299
+ # Handle completion
300
+ if last_line_data.get("final") or last_line_data.get("finish_reason") == "stop":
301
+ if current_parent:
302
+ self.parent = current_parent
303
+
304
+ # Update conversation history
305
+ self.conversation.update_chat_history(prompt, streaming_text)
306
+ self.last_response = {"text": streaming_text} # Update last response here
307
+ return # End the generator
308
+ except Exception as e:
309
+ # Log the error but continue with the rest of the function
310
+ print(f"Error processing response data: {str(e)}")
323
311
 
324
312
  # If loop finishes without returning (e.g., no final message), update history
325
313
  if current_parent:
@@ -348,10 +336,19 @@ class AllenAI(Provider):
348
336
  )
349
337
  response.raise_for_status() # Check for HTTP errors
350
338
 
351
- # Parse the response as per JS implementation
352
- raw_response = response.text
353
- parsed_response = self.parse_stream(raw_response) # Use existing parser
354
-
339
+ raw_response = response.text # Get raw text
340
+
341
+ # Process the full text using sanitize_stream line by line
342
+ processed_stream = sanitize_stream(
343
+ data=raw_response.splitlines(), # Split into lines
344
+ intro_value=None,
345
+ to_json=True,
346
+ content_extractor=self._allenai_extractor,
347
+ yield_raw_on_error=False
348
+ )
349
+ # Aggregate the results
350
+ parsed_response = "".join(list(processed_stream))
351
+
355
352
  # Update parent ID from the full response if possible (might need adjustment based on actual non-stream response structure)
356
353
  # This part is speculative as the non-stream structure isn't fully clear from the stream logic
357
354
  try:
@@ -10,7 +10,7 @@ from datetime import date
10
10
 
11
11
  from webscout.AIutel import Optimizers
12
12
  from webscout.AIutel import Conversation
13
- from webscout.AIutel import AwesomePrompts
13
+ from webscout.AIutel import AwesomePrompts, sanitize_stream # Import sanitize_stream
14
14
  from webscout.AIbase import Provider
15
15
  from webscout import WEBS, exceptions
16
16
  # from webscout.litagent import LitAgent
@@ -105,6 +105,17 @@ class ChatGPTClone(Provider):
105
105
  self.session.headers.update(self.headers)
106
106
  return self.impersonate
107
107
 
108
+ @staticmethod
109
+ def _chatgptclone_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
110
+ """Extracts content from the ChatGPTClone stream format '0:"..."'."""
111
+ if isinstance(chunk, str):
112
+ match = re.search(r'0:"((?:[^\\"]|\\.)*)"', chunk) # Use the existing regex
113
+ if match:
114
+ content = match.group(1)
115
+ # Decode JSON string escapes and then unicode escapes
116
+ decoded_content = json.loads(f'"{content}"').encode().decode('unicode_escape')
117
+ return decoded_content
118
+ return None
108
119
  def ask(
109
120
  self,
110
121
  prompt: str,
@@ -154,44 +165,31 @@ class ChatGPTClone(Provider):
154
165
 
155
166
  def for_stream():
156
167
  response = _make_request()
157
- streaming_text = ""
158
- buffer = ""
168
+ streaming_text = "" # Initialize outside try block
159
169
  try:
160
- for line in response.iter_content():
161
- if line:
162
- # decode bytes to str before concatenation
163
- if isinstance(line, bytes):
164
- line = line.decode("utf-8", errors="replace")
165
- buffer += line
166
- match = re.search(r'0:"((?:[^\\"]|\\.)*)"', buffer)
167
- if match:
168
- content = match.group(1)
169
- try:
170
- decoded_content = json.loads(f'"{content}"')
171
- except json.JSONDecodeError:
172
- decoded_content = content.encode().decode('unicode_escape')
173
- streaming_text += decoded_content
174
- yield decoded_content if raw else dict(text=decoded_content)
175
- buffer = ""
176
- elif len(buffer) > 1024:
177
- buffer = ""
178
- if buffer:
179
- match = re.search(r'0:"((?:[^\\"]|\\.)*)"', buffer)
180
- if match:
181
- content = match.group(1)
182
- try:
183
- decoded_content = json.loads(f'"{content}"')
184
- except json.JSONDecodeError:
185
- decoded_content = content.encode().decode('unicode_escape')
186
- streaming_text += decoded_content
187
- yield decoded_content if raw else dict(text=decoded_content)
188
- self.last_response.update(dict(text=streaming_text))
189
- self.conversation.update_chat_history(prompt, streaming_text)
170
+ # Use sanitize_stream
171
+ processed_stream = sanitize_stream(
172
+ data=response.iter_content(chunk_size=None), # Pass byte iterator
173
+ intro_value=None, # No simple prefix
174
+ to_json=False, # Content is text after extraction
175
+ content_extractor=self._chatgptclone_extractor, # Use the specific extractor
176
+ yield_raw_on_error=True # Yield even if extractor fails (might get metadata lines)
177
+ )
178
+
179
+ for content_chunk in processed_stream:
180
+ # content_chunk is the string extracted by _chatgptclone_extractor
181
+ if content_chunk and isinstance(content_chunk, str):
182
+ streaming_text += content_chunk
183
+ yield content_chunk if raw else dict(text=content_chunk)
184
+
190
185
  except RequestsError as e:
191
186
  raise exceptions.FailedToGenerateResponseError(f"Stream interrupted by request error: {e}") from e
192
187
  except Exception as e:
193
188
  raise exceptions.FailedToGenerateResponseError(f"Error processing stream: {e}") from e
194
189
  finally:
190
+ # Update history after stream finishes or fails
191
+ self.last_response.update(dict(text=streaming_text))
192
+ self.conversation.update_chat_history(prompt, streaming_text)
195
193
  response.close()
196
194
 
197
195
  def for_non_stream():
@@ -227,7 +225,8 @@ class ChatGPTClone(Provider):
227
225
  assert isinstance(response, dict)
228
226
  if not isinstance(response, dict) or "text" not in response:
229
227
  return str(response)
230
- formatted_text = response["text"]
228
+ # Extractor handles formatting
229
+ formatted_text = response.get("text", "")
231
230
  return formatted_text
232
231
 
233
232
  if __name__ == "__main__":