webscout 8.3.5__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.
- webscout/AIutel.py +2 -0
- webscout/Bard.py +12 -6
- webscout/DWEBS.py +66 -57
- webscout/Provider/{UNFINISHED → AISEARCH}/PERPLEXED_search.py +34 -74
- webscout/Provider/AISEARCH/__init__.py +18 -11
- webscout/Provider/AISEARCH/scira_search.py +3 -1
- webscout/Provider/Aitopia.py +2 -3
- webscout/Provider/Andi.py +3 -3
- webscout/Provider/ChatGPTClone.py +1 -1
- webscout/Provider/ChatSandbox.py +1 -0
- webscout/Provider/Cloudflare.py +1 -1
- webscout/Provider/Cohere.py +1 -0
- webscout/Provider/Deepinfra.py +13 -10
- webscout/Provider/ExaAI.py +1 -1
- webscout/Provider/ExaChat.py +1 -80
- webscout/Provider/Flowith.py +6 -1
- webscout/Provider/Gemini.py +7 -5
- webscout/Provider/GeminiProxy.py +1 -0
- webscout/Provider/GithubChat.py +4 -1
- webscout/Provider/Groq.py +1 -1
- webscout/Provider/HeckAI.py +8 -4
- webscout/Provider/Jadve.py +23 -38
- webscout/Provider/K2Think.py +308 -0
- webscout/Provider/Koboldai.py +8 -186
- webscout/Provider/LambdaChat.py +2 -4
- webscout/Provider/Nemotron.py +3 -4
- webscout/Provider/Netwrck.py +6 -8
- webscout/Provider/OLLAMA.py +1 -0
- webscout/Provider/OPENAI/Cloudflare.py +6 -7
- webscout/Provider/OPENAI/FalconH1.py +2 -7
- webscout/Provider/OPENAI/FreeGemini.py +6 -8
- webscout/Provider/OPENAI/{monochat.py → K2Think.py} +180 -77
- webscout/Provider/OPENAI/NEMOTRON.py +3 -6
- webscout/Provider/OPENAI/PI.py +5 -4
- webscout/Provider/OPENAI/Qwen3.py +2 -3
- webscout/Provider/OPENAI/README.md +2 -1
- webscout/Provider/OPENAI/TogetherAI.py +52 -57
- webscout/Provider/OPENAI/TwoAI.py +3 -4
- webscout/Provider/OPENAI/__init__.py +17 -56
- webscout/Provider/OPENAI/ai4chat.py +313 -303
- webscout/Provider/OPENAI/base.py +9 -29
- webscout/Provider/OPENAI/chatgpt.py +7 -2
- webscout/Provider/OPENAI/chatgptclone.py +4 -7
- webscout/Provider/OPENAI/chatsandbox.py +84 -59
- webscout/Provider/OPENAI/deepinfra.py +12 -6
- webscout/Provider/OPENAI/e2b.py +60 -8
- webscout/Provider/OPENAI/flowith.py +4 -3
- webscout/Provider/OPENAI/generate_api_key.py +48 -0
- webscout/Provider/OPENAI/heckai.py +4 -1
- webscout/Provider/OPENAI/netwrck.py +9 -12
- webscout/Provider/OPENAI/refact.py +274 -0
- webscout/Provider/OPENAI/scirachat.py +6 -0
- webscout/Provider/OPENAI/textpollinations.py +3 -14
- webscout/Provider/OPENAI/toolbaz.py +14 -10
- webscout/Provider/OpenGPT.py +1 -1
- webscout/Provider/Openai.py +150 -402
- webscout/Provider/PI.py +1 -0
- webscout/Provider/Perplexitylabs.py +1 -2
- webscout/Provider/QwenLM.py +107 -89
- webscout/Provider/STT/__init__.py +17 -2
- webscout/Provider/{Llama3.py → Sambanova.py} +9 -10
- webscout/Provider/StandardInput.py +1 -1
- webscout/Provider/TTI/__init__.py +18 -12
- webscout/Provider/TTI/bing.py +14 -2
- webscout/Provider/TTI/together.py +10 -9
- webscout/Provider/TTS/README.md +0 -1
- webscout/Provider/TTS/__init__.py +18 -11
- webscout/Provider/TTS/base.py +479 -159
- webscout/Provider/TTS/deepgram.py +409 -156
- webscout/Provider/TTS/elevenlabs.py +425 -111
- webscout/Provider/TTS/freetts.py +317 -140
- webscout/Provider/TTS/gesserit.py +192 -128
- webscout/Provider/TTS/murfai.py +248 -113
- webscout/Provider/TTS/openai_fm.py +347 -129
- webscout/Provider/TTS/speechma.py +620 -586
- webscout/Provider/TeachAnything.py +1 -0
- webscout/Provider/TextPollinationsAI.py +5 -15
- webscout/Provider/TogetherAI.py +136 -142
- webscout/Provider/TwoAI.py +53 -309
- webscout/Provider/TypliAI.py +2 -1
- webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +1 -1
- webscout/Provider/UNFINISHED/VercelAIGateway.py +339 -0
- webscout/Provider/Venice.py +2 -1
- webscout/Provider/VercelAI.py +1 -0
- webscout/Provider/WiseCat.py +2 -1
- webscout/Provider/WrDoChat.py +2 -1
- webscout/Provider/__init__.py +18 -174
- webscout/Provider/ai4chat.py +1 -1
- webscout/Provider/akashgpt.py +7 -10
- webscout/Provider/cerebras.py +194 -38
- webscout/Provider/chatglm.py +170 -83
- webscout/Provider/cleeai.py +1 -2
- webscout/Provider/deepseek_assistant.py +1 -1
- webscout/Provider/elmo.py +1 -1
- webscout/Provider/geminiapi.py +1 -1
- webscout/Provider/granite.py +1 -1
- webscout/Provider/hermes.py +1 -3
- webscout/Provider/julius.py +1 -0
- webscout/Provider/learnfastai.py +1 -1
- webscout/Provider/llama3mitril.py +1 -1
- webscout/Provider/llmchat.py +1 -1
- webscout/Provider/llmchatco.py +1 -1
- webscout/Provider/meta.py +3 -3
- webscout/Provider/oivscode.py +2 -2
- webscout/Provider/scira_chat.py +51 -124
- webscout/Provider/searchchat.py +1 -0
- webscout/Provider/sonus.py +1 -1
- webscout/Provider/toolbaz.py +15 -11
- webscout/Provider/turboseek.py +31 -22
- webscout/Provider/typefully.py +2 -1
- webscout/Provider/x0gpt.py +1 -0
- webscout/Provider/yep.py +2 -1
- webscout/conversation.py +22 -20
- webscout/sanitize.py +14 -10
- webscout/scout/README.md +20 -23
- webscout/scout/core/crawler.py +125 -38
- webscout/scout/core/scout.py +26 -5
- webscout/tempid.py +6 -0
- webscout/version.py +1 -1
- webscout/webscout_search.py +13 -6
- webscout/webscout_search_async.py +10 -8
- webscout/yep_search.py +13 -5
- {webscout-8.3.5.dist-info → webscout-8.3.7.dist-info}/METADATA +3 -1
- {webscout-8.3.5.dist-info → webscout-8.3.7.dist-info}/RECORD +132 -155
- webscout/Provider/AllenAI.py +0 -440
- webscout/Provider/Blackboxai.py +0 -793
- webscout/Provider/FreeGemini.py +0 -250
- webscout/Provider/Glider.py +0 -225
- webscout/Provider/Hunyuan.py +0 -283
- webscout/Provider/MCPCore.py +0 -322
- webscout/Provider/MiniMax.py +0 -207
- webscout/Provider/OPENAI/BLACKBOXAI.py +0 -1045
- webscout/Provider/OPENAI/MiniMax.py +0 -298
- webscout/Provider/OPENAI/autoproxy.py +0 -1067
- webscout/Provider/OPENAI/c4ai.py +0 -394
- webscout/Provider/OPENAI/copilot.py +0 -305
- webscout/Provider/OPENAI/glider.py +0 -330
- webscout/Provider/OPENAI/mcpcore.py +0 -431
- webscout/Provider/OPENAI/multichat.py +0 -378
- webscout/Provider/Reka.py +0 -214
- webscout/Provider/TTS/sthir.py +0 -94
- webscout/Provider/UNFINISHED/fetch_together_models.py +0 -90
- webscout/Provider/asksteve.py +0 -220
- webscout/Provider/copilot.py +0 -422
- webscout/Provider/freeaichat.py +0 -294
- webscout/Provider/koala.py +0 -182
- webscout/Provider/lmarena.py +0 -198
- webscout/Provider/monochat.py +0 -275
- webscout/Provider/multichat.py +0 -375
- webscout/Provider/scnet.py +0 -244
- webscout/Provider/talkai.py +0 -194
- /webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +0 -0
- /webscout/Provider/{Qodo.py → UNFINISHED/Qodo.py} +0 -0
- /webscout/Provider/{XenAI.py → UNFINISHED/XenAI.py} +0 -0
- /webscout/Provider/{samurai.py → UNFINISHED/samurai.py} +0 -0
- {webscout-8.3.5.dist-info → webscout-8.3.7.dist-info}/WHEEL +0 -0
- {webscout-8.3.5.dist-info → webscout-8.3.7.dist-info}/entry_points.txt +0 -0
- {webscout-8.3.5.dist-info → webscout-8.3.7.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.3.5.dist-info → webscout-8.3.7.dist-info}/top_level.txt +0 -0
webscout/Provider/TwoAI.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
291
|
-
|
|
69
|
+
if not api_key:
|
|
70
|
+
raise exceptions.AuthenticationError("TwoAI API key is required.")
|
|
292
71
|
|
|
293
|
-
self.url = "https://
|
|
72
|
+
self.url = "https://chatsutra-server.account-2b0.workers.dev/v2/chat/completions" # Correct API endpoint
|
|
294
73
|
self.headers = {
|
|
295
|
-
'User-Agent':
|
|
296
|
-
'Accept': '
|
|
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://
|
|
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
|
|
222
|
+
# Use sanitize_stream to process the SSE stream
|
|
436
223
|
processed_stream = sanitize_stream(
|
|
437
|
-
data=response.iter_content(chunk_size=None),
|
|
224
|
+
data=response.iter_content(chunk_size=None),
|
|
438
225
|
intro_value="data:",
|
|
439
|
-
to_json=True,
|
|
226
|
+
to_json=True,
|
|
440
227
|
skip_markers=["[DONE]"],
|
|
441
|
-
content_extractor=self._twoai_extractor,
|
|
442
|
-
yield_raw_on_error=False
|
|
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
|
-
|
|
487
|
-
return for_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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
)
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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)
|
webscout/Provider/TypliAI.py
CHANGED
|
@@ -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}")
|