webscout 8.2.7__py3-none-any.whl → 8.2.9__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.
Files changed (281) hide show
  1. webscout/AIauto.py +33 -15
  2. webscout/AIbase.py +96 -37
  3. webscout/AIutel.py +703 -250
  4. webscout/Bard.py +441 -323
  5. webscout/Extra/Act.md +309 -0
  6. webscout/Extra/GitToolkit/__init__.py +10 -0
  7. webscout/Extra/GitToolkit/gitapi/README.md +110 -0
  8. webscout/Extra/GitToolkit/gitapi/__init__.py +12 -0
  9. webscout/Extra/GitToolkit/gitapi/repository.py +195 -0
  10. webscout/Extra/GitToolkit/gitapi/user.py +96 -0
  11. webscout/Extra/GitToolkit/gitapi/utils.py +62 -0
  12. webscout/Extra/YTToolkit/README.md +375 -0
  13. webscout/Extra/YTToolkit/YTdownloader.py +957 -0
  14. webscout/Extra/YTToolkit/__init__.py +3 -0
  15. webscout/Extra/YTToolkit/transcriber.py +476 -0
  16. webscout/Extra/YTToolkit/ytapi/README.md +44 -0
  17. webscout/Extra/YTToolkit/ytapi/__init__.py +6 -0
  18. webscout/Extra/YTToolkit/ytapi/channel.py +307 -0
  19. webscout/Extra/YTToolkit/ytapi/errors.py +13 -0
  20. webscout/Extra/YTToolkit/ytapi/extras.py +118 -0
  21. webscout/Extra/YTToolkit/ytapi/https.py +88 -0
  22. webscout/Extra/YTToolkit/ytapi/patterns.py +61 -0
  23. webscout/Extra/YTToolkit/ytapi/playlist.py +59 -0
  24. webscout/Extra/YTToolkit/ytapi/pool.py +8 -0
  25. webscout/Extra/YTToolkit/ytapi/query.py +40 -0
  26. webscout/Extra/YTToolkit/ytapi/stream.py +63 -0
  27. webscout/Extra/YTToolkit/ytapi/utils.py +62 -0
  28. webscout/Extra/YTToolkit/ytapi/video.py +232 -0
  29. webscout/Extra/__init__.py +7 -0
  30. webscout/Extra/autocoder/__init__.py +9 -0
  31. webscout/Extra/autocoder/autocoder.py +1105 -0
  32. webscout/Extra/autocoder/autocoder_utiles.py +332 -0
  33. webscout/Extra/gguf.md +430 -0
  34. webscout/Extra/gguf.py +684 -0
  35. webscout/Extra/tempmail/README.md +488 -0
  36. webscout/Extra/tempmail/__init__.py +28 -0
  37. webscout/Extra/tempmail/async_utils.py +141 -0
  38. webscout/Extra/tempmail/base.py +161 -0
  39. webscout/Extra/tempmail/cli.py +187 -0
  40. webscout/Extra/tempmail/emailnator.py +84 -0
  41. webscout/Extra/tempmail/mail_tm.py +361 -0
  42. webscout/Extra/tempmail/temp_mail_io.py +292 -0
  43. webscout/Extra/weather.md +281 -0
  44. webscout/Extra/weather.py +194 -0
  45. webscout/Extra/weather_ascii.py +76 -0
  46. webscout/Litlogger/README.md +10 -0
  47. webscout/Litlogger/__init__.py +15 -0
  48. webscout/Litlogger/formats.py +4 -0
  49. webscout/Litlogger/handlers.py +103 -0
  50. webscout/Litlogger/levels.py +13 -0
  51. webscout/Litlogger/logger.py +92 -0
  52. webscout/Provider/AI21.py +177 -0
  53. webscout/Provider/AISEARCH/DeepFind.py +254 -0
  54. webscout/Provider/AISEARCH/Perplexity.py +333 -0
  55. webscout/Provider/AISEARCH/README.md +279 -0
  56. webscout/Provider/AISEARCH/__init__.py +9 -0
  57. webscout/Provider/AISEARCH/felo_search.py +202 -0
  58. webscout/Provider/AISEARCH/genspark_search.py +324 -0
  59. webscout/Provider/AISEARCH/hika_search.py +186 -0
  60. webscout/Provider/AISEARCH/iask_search.py +410 -0
  61. webscout/Provider/AISEARCH/monica_search.py +220 -0
  62. webscout/Provider/AISEARCH/scira_search.py +298 -0
  63. webscout/Provider/AISEARCH/webpilotai_search.py +255 -0
  64. webscout/Provider/Aitopia.py +316 -0
  65. webscout/Provider/AllenAI.py +440 -0
  66. webscout/Provider/Andi.py +228 -0
  67. webscout/Provider/Blackboxai.py +791 -0
  68. webscout/Provider/ChatGPTClone.py +237 -0
  69. webscout/Provider/ChatGPTGratis.py +194 -0
  70. webscout/Provider/ChatSandbox.py +342 -0
  71. webscout/Provider/Cloudflare.py +324 -0
  72. webscout/Provider/Cohere.py +208 -0
  73. webscout/Provider/Deepinfra.py +340 -0
  74. webscout/Provider/ExaAI.py +261 -0
  75. webscout/Provider/ExaChat.py +358 -0
  76. webscout/Provider/Flowith.py +217 -0
  77. webscout/Provider/FreeGemini.py +250 -0
  78. webscout/Provider/Gemini.py +169 -0
  79. webscout/Provider/GithubChat.py +369 -0
  80. webscout/Provider/GizAI.py +295 -0
  81. webscout/Provider/Glider.py +225 -0
  82. webscout/Provider/Groq.py +801 -0
  83. webscout/Provider/HF_space/__init__.py +0 -0
  84. webscout/Provider/HF_space/qwen_qwen2.py +206 -0
  85. webscout/Provider/HeckAI.py +375 -0
  86. webscout/Provider/HuggingFaceChat.py +469 -0
  87. webscout/Provider/Hunyuan.py +283 -0
  88. webscout/Provider/Jadve.py +291 -0
  89. webscout/Provider/Koboldai.py +384 -0
  90. webscout/Provider/LambdaChat.py +411 -0
  91. webscout/Provider/Llama3.py +259 -0
  92. webscout/Provider/MCPCore.py +315 -0
  93. webscout/Provider/Marcus.py +198 -0
  94. webscout/Provider/Nemotron.py +218 -0
  95. webscout/Provider/Netwrck.py +270 -0
  96. webscout/Provider/OLLAMA.py +396 -0
  97. webscout/Provider/OPENAI/BLACKBOXAI.py +766 -0
  98. webscout/Provider/OPENAI/Cloudflare.py +378 -0
  99. webscout/Provider/OPENAI/FreeGemini.py +283 -0
  100. webscout/Provider/OPENAI/NEMOTRON.py +232 -0
  101. webscout/Provider/OPENAI/Qwen3.py +283 -0
  102. webscout/Provider/OPENAI/README.md +952 -0
  103. webscout/Provider/OPENAI/TwoAI.py +357 -0
  104. webscout/Provider/OPENAI/__init__.py +40 -0
  105. webscout/Provider/OPENAI/ai4chat.py +293 -0
  106. webscout/Provider/OPENAI/api.py +969 -0
  107. webscout/Provider/OPENAI/base.py +249 -0
  108. webscout/Provider/OPENAI/c4ai.py +373 -0
  109. webscout/Provider/OPENAI/chatgpt.py +556 -0
  110. webscout/Provider/OPENAI/chatgptclone.py +494 -0
  111. webscout/Provider/OPENAI/chatsandbox.py +173 -0
  112. webscout/Provider/OPENAI/copilot.py +242 -0
  113. webscout/Provider/OPENAI/deepinfra.py +322 -0
  114. webscout/Provider/OPENAI/e2b.py +1414 -0
  115. webscout/Provider/OPENAI/exaai.py +417 -0
  116. webscout/Provider/OPENAI/exachat.py +444 -0
  117. webscout/Provider/OPENAI/flowith.py +162 -0
  118. webscout/Provider/OPENAI/freeaichat.py +359 -0
  119. webscout/Provider/OPENAI/glider.py +326 -0
  120. webscout/Provider/OPENAI/groq.py +364 -0
  121. webscout/Provider/OPENAI/heckai.py +308 -0
  122. webscout/Provider/OPENAI/llmchatco.py +335 -0
  123. webscout/Provider/OPENAI/mcpcore.py +389 -0
  124. webscout/Provider/OPENAI/multichat.py +376 -0
  125. webscout/Provider/OPENAI/netwrck.py +357 -0
  126. webscout/Provider/OPENAI/oivscode.py +287 -0
  127. webscout/Provider/OPENAI/opkfc.py +496 -0
  128. webscout/Provider/OPENAI/pydantic_imports.py +172 -0
  129. webscout/Provider/OPENAI/scirachat.py +477 -0
  130. webscout/Provider/OPENAI/sonus.py +304 -0
  131. webscout/Provider/OPENAI/standardinput.py +433 -0
  132. webscout/Provider/OPENAI/textpollinations.py +339 -0
  133. webscout/Provider/OPENAI/toolbaz.py +413 -0
  134. webscout/Provider/OPENAI/typefully.py +355 -0
  135. webscout/Provider/OPENAI/typegpt.py +364 -0
  136. webscout/Provider/OPENAI/uncovrAI.py +463 -0
  137. webscout/Provider/OPENAI/utils.py +318 -0
  138. webscout/Provider/OPENAI/venice.py +431 -0
  139. webscout/Provider/OPENAI/wisecat.py +387 -0
  140. webscout/Provider/OPENAI/writecream.py +163 -0
  141. webscout/Provider/OPENAI/x0gpt.py +365 -0
  142. webscout/Provider/OPENAI/yep.py +382 -0
  143. webscout/Provider/OpenGPT.py +209 -0
  144. webscout/Provider/Openai.py +496 -0
  145. webscout/Provider/PI.py +429 -0
  146. webscout/Provider/Perplexitylabs.py +415 -0
  147. webscout/Provider/QwenLM.py +254 -0
  148. webscout/Provider/Reka.py +214 -0
  149. webscout/Provider/StandardInput.py +290 -0
  150. webscout/Provider/TTI/README.md +82 -0
  151. webscout/Provider/TTI/__init__.py +7 -0
  152. webscout/Provider/TTI/aiarta.py +365 -0
  153. webscout/Provider/TTI/artbit.py +0 -0
  154. webscout/Provider/TTI/base.py +64 -0
  155. webscout/Provider/TTI/fastflux.py +200 -0
  156. webscout/Provider/TTI/magicstudio.py +201 -0
  157. webscout/Provider/TTI/piclumen.py +203 -0
  158. webscout/Provider/TTI/pixelmuse.py +225 -0
  159. webscout/Provider/TTI/pollinations.py +221 -0
  160. webscout/Provider/TTI/utils.py +11 -0
  161. webscout/Provider/TTS/README.md +192 -0
  162. webscout/Provider/TTS/__init__.py +10 -0
  163. webscout/Provider/TTS/base.py +159 -0
  164. webscout/Provider/TTS/deepgram.py +156 -0
  165. webscout/Provider/TTS/elevenlabs.py +111 -0
  166. webscout/Provider/TTS/gesserit.py +128 -0
  167. webscout/Provider/TTS/murfai.py +113 -0
  168. webscout/Provider/TTS/openai_fm.py +129 -0
  169. webscout/Provider/TTS/parler.py +111 -0
  170. webscout/Provider/TTS/speechma.py +580 -0
  171. webscout/Provider/TTS/sthir.py +94 -0
  172. webscout/Provider/TTS/streamElements.py +333 -0
  173. webscout/Provider/TTS/utils.py +280 -0
  174. webscout/Provider/TeachAnything.py +229 -0
  175. webscout/Provider/TextPollinationsAI.py +308 -0
  176. webscout/Provider/TwoAI.py +475 -0
  177. webscout/Provider/TypliAI.py +305 -0
  178. webscout/Provider/UNFINISHED/ChatHub.py +209 -0
  179. webscout/Provider/UNFINISHED/Youchat.py +330 -0
  180. webscout/Provider/UNFINISHED/liner_api_request.py +263 -0
  181. webscout/Provider/UNFINISHED/puterjs.py +635 -0
  182. webscout/Provider/UNFINISHED/test_lmarena.py +119 -0
  183. webscout/Provider/Venice.py +258 -0
  184. webscout/Provider/VercelAI.py +253 -0
  185. webscout/Provider/WiseCat.py +233 -0
  186. webscout/Provider/WrDoChat.py +370 -0
  187. webscout/Provider/Writecream.py +246 -0
  188. webscout/Provider/WritingMate.py +269 -0
  189. webscout/Provider/__init__.py +174 -0
  190. webscout/Provider/ai4chat.py +174 -0
  191. webscout/Provider/akashgpt.py +335 -0
  192. webscout/Provider/asksteve.py +220 -0
  193. webscout/Provider/cerebras.py +290 -0
  194. webscout/Provider/chatglm.py +215 -0
  195. webscout/Provider/cleeai.py +213 -0
  196. webscout/Provider/copilot.py +425 -0
  197. webscout/Provider/elmo.py +283 -0
  198. webscout/Provider/freeaichat.py +285 -0
  199. webscout/Provider/geminiapi.py +208 -0
  200. webscout/Provider/granite.py +235 -0
  201. webscout/Provider/hermes.py +266 -0
  202. webscout/Provider/julius.py +223 -0
  203. webscout/Provider/koala.py +170 -0
  204. webscout/Provider/learnfastai.py +325 -0
  205. webscout/Provider/llama3mitril.py +215 -0
  206. webscout/Provider/llmchat.py +258 -0
  207. webscout/Provider/llmchatco.py +306 -0
  208. webscout/Provider/lmarena.py +198 -0
  209. webscout/Provider/meta.py +801 -0
  210. webscout/Provider/multichat.py +364 -0
  211. webscout/Provider/oivscode.py +309 -0
  212. webscout/Provider/samurai.py +224 -0
  213. webscout/Provider/scira_chat.py +299 -0
  214. webscout/Provider/scnet.py +243 -0
  215. webscout/Provider/searchchat.py +292 -0
  216. webscout/Provider/sonus.py +258 -0
  217. webscout/Provider/talkai.py +194 -0
  218. webscout/Provider/toolbaz.py +353 -0
  219. webscout/Provider/turboseek.py +266 -0
  220. webscout/Provider/typefully.py +202 -0
  221. webscout/Provider/typegpt.py +289 -0
  222. webscout/Provider/uncovr.py +368 -0
  223. webscout/Provider/x0gpt.py +299 -0
  224. webscout/Provider/yep.py +389 -0
  225. webscout/__init__.py +4 -2
  226. webscout/cli.py +3 -28
  227. webscout/client.py +70 -0
  228. webscout/conversation.py +35 -35
  229. webscout/litagent/Readme.md +276 -0
  230. webscout/litagent/__init__.py +29 -0
  231. webscout/litagent/agent.py +455 -0
  232. webscout/litagent/constants.py +60 -0
  233. webscout/litprinter/__init__.py +59 -0
  234. webscout/optimizers.py +419 -419
  235. webscout/scout/README.md +404 -0
  236. webscout/scout/__init__.py +8 -0
  237. webscout/scout/core/__init__.py +7 -0
  238. webscout/scout/core/crawler.py +210 -0
  239. webscout/scout/core/scout.py +607 -0
  240. webscout/scout/core/search_result.py +96 -0
  241. webscout/scout/core/text_analyzer.py +63 -0
  242. webscout/scout/core/text_utils.py +277 -0
  243. webscout/scout/core/web_analyzer.py +52 -0
  244. webscout/scout/element.py +478 -0
  245. webscout/scout/parsers/__init__.py +69 -0
  246. webscout/scout/parsers/html5lib_parser.py +172 -0
  247. webscout/scout/parsers/html_parser.py +236 -0
  248. webscout/scout/parsers/lxml_parser.py +178 -0
  249. webscout/scout/utils.py +37 -0
  250. webscout/swiftcli/Readme.md +323 -0
  251. webscout/swiftcli/__init__.py +95 -0
  252. webscout/swiftcli/core/__init__.py +7 -0
  253. webscout/swiftcli/core/cli.py +297 -0
  254. webscout/swiftcli/core/context.py +104 -0
  255. webscout/swiftcli/core/group.py +241 -0
  256. webscout/swiftcli/decorators/__init__.py +28 -0
  257. webscout/swiftcli/decorators/command.py +221 -0
  258. webscout/swiftcli/decorators/options.py +220 -0
  259. webscout/swiftcli/decorators/output.py +252 -0
  260. webscout/swiftcli/exceptions.py +21 -0
  261. webscout/swiftcli/plugins/__init__.py +9 -0
  262. webscout/swiftcli/plugins/base.py +135 -0
  263. webscout/swiftcli/plugins/manager.py +269 -0
  264. webscout/swiftcli/utils/__init__.py +59 -0
  265. webscout/swiftcli/utils/formatting.py +252 -0
  266. webscout/swiftcli/utils/parsing.py +267 -0
  267. webscout/version.py +1 -1
  268. webscout/webscout_search.py +2 -182
  269. webscout/webscout_search_async.py +1 -179
  270. webscout/zeroart/README.md +89 -0
  271. webscout/zeroart/__init__.py +135 -0
  272. webscout/zeroart/base.py +66 -0
  273. webscout/zeroart/effects.py +101 -0
  274. webscout/zeroart/fonts.py +1239 -0
  275. {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/METADATA +262 -83
  276. webscout-8.2.9.dist-info/RECORD +289 -0
  277. {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/WHEEL +1 -1
  278. {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/entry_points.txt +1 -0
  279. webscout-8.2.7.dist-info/RECORD +0 -26
  280. {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/licenses/LICENSE.md +0 -0
  281. {webscout-8.2.7.dist-info → webscout-8.2.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,224 @@
1
+ from typing import *
2
+ from webscout.AIutel import Conversation
3
+ from webscout.AIutel import Optimizers
4
+ from webscout.AIutel import AwesomePrompts, sanitize_stream
5
+ from webscout.AIbase import Provider
6
+ from webscout import exceptions
7
+ from curl_cffi.requests import Session
8
+ from curl_cffi import CurlError
9
+ import json
10
+
11
+ class samurai(Provider):
12
+ """
13
+ A class to interact with a custom API endpoint.
14
+ """
15
+ AVAILABLE_MODELS = [
16
+ "openai/gpt-4.1",
17
+ "openai/gpt-4o-search-preview",
18
+ "openai/gpt-4o-mini-search-preview",
19
+ "openai/gpt-4.1-nano",
20
+ "openai/chatgpt-4o-latest",
21
+ "openai/gpt-4.1-mini",
22
+ "gpt-4o",
23
+ "o3-mini",
24
+ "Claude-sonnet-3.7",
25
+ "uncensored-r1",
26
+ "anthropic/claude-3.5-sonnet",
27
+ "gemini-1.5-pro",
28
+ "gemini-1.5-pro-latest",
29
+ "gemini-flash-2.0",
30
+ "gemini-1.5-flash",
31
+ "llama-3.1-405b",
32
+ "Meta-Llama-3.1-405B-Instruct-Turbo",
33
+ "Meta-Llama-3.3-70B-Instruct-Turbo",
34
+ "chutesai/Llama-4-Maverick-17B-128E-Instruct-FP8",
35
+ "chutesai/Llama-4-Scout-17B-16E-Instruct",
36
+ "Qwen-QwQ-32B-Preview",
37
+ "Qwen/Qwen3-235B-A22B-fp8-tput",
38
+ "deepseek-r1",
39
+ "deepseek-v3",
40
+ "deepseek-ai/DeepSeek-V3-0324",
41
+ "dbrx-instruct",
42
+ "x-ai/grok-3-",
43
+ "perplexity-ai/r1-1776"
44
+ ]
45
+ def __init__(
46
+ self,
47
+ is_conversation: bool = True,
48
+ max_tokens: int = 2049,
49
+ timeout: int = 30,
50
+ intro: str = None,
51
+ filepath: str = None,
52
+ update_file: bool = True,
53
+ proxies: dict = {},
54
+ history_offset: int = 10250,
55
+ act: str = None,
56
+ model: str = "openai/gpt-4.1",
57
+ system_prompt: str = "You are a helpful assistant."
58
+ ):
59
+ """Initializes the Custom API client."""
60
+ self.url = "https://newapi-9qln.onrender.com/v1/chat/completions"
61
+ self.headers = {
62
+ "Authorization": "Bearer Samurai-AP1-Fr33",
63
+ "Content-Type": "application/json"
64
+ }
65
+ self.session = Session()
66
+ self.session.headers.update(self.headers)
67
+ self.system_prompt = system_prompt
68
+ self.is_conversation = is_conversation
69
+ self.max_tokens_to_sample = max_tokens
70
+ self.timeout = timeout
71
+ self.last_response = {}
72
+ self.model = model
73
+
74
+ self.__available_optimizers = (
75
+ method
76
+ for method in dir(Optimizers)
77
+ if callable(getattr(Optimizers, method)) and not method.startswith("__")
78
+ )
79
+ Conversation.intro = (
80
+ AwesomePrompts().get_act(
81
+ act, raise_not_found=True, default=None, case_insensitive=True
82
+ )
83
+ if act
84
+ else intro or Conversation.intro
85
+ )
86
+
87
+ self.conversation = Conversation(
88
+ is_conversation, self.max_tokens_to_sample, filepath, update_file
89
+ )
90
+ self.conversation.history_offset = history_offset
91
+
92
+ @staticmethod
93
+ def _extractor(chunk: dict) -> Optional[str]:
94
+ """Extracts content from stream JSON objects."""
95
+ return chunk.get("choices", [{}])[0].get("delta", {}).get("content")
96
+
97
+ def ask(
98
+ self,
99
+ prompt: str,
100
+ stream: bool = False,
101
+ raw: bool = False,
102
+ optimizer: str = None,
103
+ conversationally: bool = False,
104
+ ) -> Union[Dict[str, Any], Generator]:
105
+ conversation_prompt = self.conversation.gen_complete_prompt(prompt)
106
+ if optimizer:
107
+ if optimizer in self.__available_optimizers:
108
+ conversation_prompt = getattr(Optimizers, optimizer)(
109
+ conversation_prompt if conversationally else prompt
110
+ )
111
+ else:
112
+ raise Exception(f"Optimizer is not one of {self.__available_optimizers}")
113
+
114
+ payload = {
115
+ "model": self.model,
116
+ "messages": [
117
+ {"role": "system", "content": self.system_prompt},
118
+ {"role": "user", "content": conversation_prompt},
119
+ ],
120
+ "stream": stream
121
+ }
122
+
123
+ def for_stream():
124
+ streaming_text = ""
125
+ try:
126
+ response = self.session.post(
127
+ self.url,
128
+ data=json.dumps(payload),
129
+ stream=True,
130
+ timeout=self.timeout,
131
+ impersonate="chrome110"
132
+ )
133
+ response.raise_for_status()
134
+
135
+ processed_stream = sanitize_stream(
136
+ data=response.iter_lines(),
137
+ intro_value="data:",
138
+ to_json=True,
139
+ skip_markers=["[DONE]"],
140
+ content_extractor=self._extractor,
141
+ yield_raw_on_error=False
142
+ )
143
+
144
+ for content_chunk in processed_stream:
145
+ if content_chunk and isinstance(content_chunk, str):
146
+ streaming_text += content_chunk
147
+ resp = dict(text=content_chunk)
148
+ yield resp if not raw else content_chunk
149
+
150
+ except Exception as e:
151
+ raise exceptions.FailedToGenerateResponseError(f"Request failed: {str(e)}") from e
152
+ finally:
153
+ if streaming_text:
154
+ self.last_response = {"text": streaming_text}
155
+ self.conversation.update_chat_history(prompt, streaming_text)
156
+
157
+ def for_non_stream():
158
+ try:
159
+ response = self.session.post(
160
+ self.url,
161
+ data=json.dumps(payload),
162
+ timeout=self.timeout,
163
+ impersonate="chrome110"
164
+ )
165
+ response.raise_for_status()
166
+ json_response = response.json()
167
+ content = json_response.get("choices", [{}])[0].get("message", {}).get("content", "")
168
+
169
+ self.last_response = {"text": content}
170
+ self.conversation.update_chat_history(prompt, content)
171
+ return self.last_response if not raw else content
172
+
173
+ except Exception as e:
174
+ raise exceptions.FailedToGenerateResponseError(f"Request failed: {str(e)}") from e
175
+
176
+ return for_stream() if stream else for_non_stream()
177
+
178
+ def chat(
179
+ self,
180
+ prompt: str,
181
+ stream: bool = False,
182
+ optimizer: str = None,
183
+ conversationally: bool = False,
184
+ ) -> Union[str, Generator[str, None, None]]:
185
+ def for_stream_chat():
186
+ gen = self.ask(prompt, stream=True, raw=False, optimizer=optimizer, conversationally=conversationally)
187
+ for response_dict in gen:
188
+ yield self.get_message(response_dict)
189
+
190
+ def for_non_stream_chat():
191
+ response_data = self.ask(prompt, stream=False, raw=False, optimizer=optimizer, conversationally=conversationally)
192
+ return self.get_message(response_data)
193
+
194
+ return for_stream_chat() if stream else for_non_stream_chat()
195
+
196
+ def get_message(self, response: dict) -> str:
197
+ assert isinstance(response, dict), "Response should be of dict data-type only"
198
+ return response["text"]
199
+
200
+ if __name__ == "__main__":
201
+ # Ensure curl_cffi is installed
202
+ print("-" * 80)
203
+ print(f"{'Model':<50} {'Status':<10} {'Response'}")
204
+ print("-" * 80)
205
+
206
+ for model in samurai.AVAILABLE_MODELS:
207
+ try:
208
+ test_ai = samurai(model=model, timeout=60)
209
+ response = test_ai.chat("Say 'Hello' in one word", stream=True)
210
+ response_text = ""
211
+ for chunk in response:
212
+ response_text += chunk
213
+
214
+ if response_text and len(response_text.strip()) > 0:
215
+ status = "✓"
216
+ # Clean and truncate response
217
+ clean_text = response_text.strip().encode('utf-8', errors='ignore').decode('utf-8')
218
+ display_text = clean_text[:50] + "..." if len(clean_text) > 50 else clean_text
219
+ else:
220
+ status = "✗"
221
+ display_text = "Empty or invalid response"
222
+ print(f"\r{model:<50} {status:<10} {display_text}")
223
+ except Exception as e:
224
+ print(f"\r{model:<50} {'✗':<10} {str(e)}")
@@ -0,0 +1,299 @@
1
+ from os import system
2
+ from curl_cffi import CurlError
3
+ from curl_cffi.requests import Session
4
+ import json
5
+ import uuid
6
+ import re
7
+ from typing import Any, Dict, Optional, Union, List
8
+ from webscout.AIutel import Optimizers
9
+ from webscout.AIutel import Conversation, sanitize_stream # Import sanitize_stream
10
+ from webscout.AIutel import AwesomePrompts
11
+ from webscout.AIbase import Provider
12
+ from webscout import exceptions
13
+ from webscout.litagent import LitAgent
14
+
15
+ class SciraAI(Provider):
16
+ """
17
+ A class to interact with the Scira AI chat API.
18
+ """
19
+
20
+ AVAILABLE_MODELS = {
21
+ "scira-default": "Grok3-mini", # thinking model
22
+ "scira-grok-3": "Grok3",
23
+ "scira-anthropic": "Sonnet 3.7 thinking",
24
+ "scira-vision" : "Grok2-Vision", # vision model
25
+ "scira-4o": "GPT4o",
26
+ "scira-qwq": "QWQ-32B",
27
+ "scira-o4-mini": "o4-mini",
28
+ "scira-google": "gemini 2.5 flash",
29
+ "scira-google-pro": "gemini 2.5 pro",
30
+ "scira-llama-4": "llama 4 Maverick",
31
+ }
32
+
33
+ def __init__(
34
+ self,
35
+ is_conversation: bool = True,
36
+ max_tokens: int = 2049,
37
+ timeout: int = 30,
38
+ intro: str = None,
39
+ filepath: str = None,
40
+ update_file: bool = True,
41
+ proxies: dict = {},
42
+ history_offset: int = 10250,
43
+ act: str = None,
44
+ model: str = "scira-default",
45
+ chat_id: str = None,
46
+ user_id: str = None,
47
+ browser: str = "chrome",
48
+ system_prompt: str = "You are a helpful assistant.",
49
+ ):
50
+ """Initializes the Scira AI API client.
51
+
52
+ Args:
53
+ is_conversation (bool): Whether to maintain conversation history.
54
+ max_tokens (int): Maximum number of tokens to generate.
55
+ timeout (int): Request timeout in seconds.
56
+ intro (str): Introduction text for the conversation.
57
+ filepath (str): Path to save conversation history.
58
+ update_file (bool): Whether to update the conversation history file.
59
+ proxies (dict): Proxy configuration for requests.
60
+ history_offset (int): Maximum history length in characters.
61
+ act (str): Persona for the AI to adopt.
62
+ model (str): Model to use, must be one of AVAILABLE_MODELS.
63
+ chat_id (str): Unique identifier for the chat session.
64
+ user_id (str): Unique identifier for the user.
65
+ browser (str): Browser to emulate in requests.
66
+ system_prompt (str): System prompt for the AI.
67
+
68
+ """
69
+ if model not in self.AVAILABLE_MODELS:
70
+ raise ValueError(f"Invalid model: {model}. Choose from: {self.AVAILABLE_MODELS}")
71
+
72
+ self.url = "https://scira.ai/api/search"
73
+
74
+ # Initialize LitAgent for user agent generation
75
+ self.agent = LitAgent()
76
+ # Use fingerprinting to create a consistent browser identity
77
+ self.fingerprint = self.agent.generate_fingerprint(browser)
78
+ self.system_prompt = system_prompt
79
+
80
+ # Use the fingerprint for headers
81
+ self.headers = {
82
+ "Accept": self.fingerprint["accept"],
83
+ "Accept-Encoding": "gzip, deflate, br, zstd",
84
+ "Accept-Language": self.fingerprint["accept_language"],
85
+ "Content-Type": "application/json",
86
+ "Origin": "https://scira.ai",
87
+ "Referer": "https://scira.ai/",
88
+ "Sec-CH-UA": self.fingerprint["sec_ch_ua"] or '"Not)A;Brand";v="99", "Microsoft Edge";v="127", "Chromium";v="127"',
89
+ "Sec-CH-UA-Mobile": "?0",
90
+ "Sec-CH-UA-Platform": f'"{self.fingerprint["platform"]}"',
91
+ "User-Agent": self.fingerprint["user_agent"],
92
+ "Sec-Fetch-Dest": "empty",
93
+ "Sec-Fetch-Mode": "cors",
94
+ "Sec-Fetch-Site": "same-origin"
95
+ }
96
+
97
+ self.session = Session() # Use curl_cffi Session
98
+ self.session.headers.update(self.headers)
99
+ self.session.proxies = proxies # Assign proxies directly
100
+
101
+ self.is_conversation = is_conversation
102
+ self.max_tokens_to_sample = max_tokens
103
+ self.timeout = timeout
104
+ self.last_response = {}
105
+ self.model = model
106
+ self.chat_id = chat_id or str(uuid.uuid4())
107
+ self.user_id = user_id or f"user_{str(uuid.uuid4())[:8].upper()}"
108
+
109
+ # Always use chat mode (no web search)
110
+ self.search_mode = "chat"
111
+
112
+ self.__available_optimizers = (
113
+ method
114
+ for method in dir(Optimizers)
115
+ if callable(getattr(Optimizers, method)) and not method.startswith("__")
116
+ )
117
+ Conversation.intro = (
118
+ AwesomePrompts().get_act(
119
+ act, raise_not_found=True, default=None, case_insensitive=True
120
+ )
121
+ if act
122
+ else intro or Conversation.intro
123
+ )
124
+
125
+ self.conversation = Conversation(
126
+ is_conversation, self.max_tokens_to_sample, filepath, update_file
127
+ )
128
+ self.conversation.history_offset = history_offset
129
+
130
+ def refresh_identity(self, browser: str = None):
131
+ """
132
+ Refreshes the browser identity fingerprint.
133
+
134
+ Args:
135
+ browser: Specific browser to use for the new fingerprint
136
+ """
137
+ browser = browser or self.fingerprint.get("browser_type", "chrome")
138
+ self.fingerprint = self.agent.generate_fingerprint(browser)
139
+
140
+ # Update headers with new fingerprint
141
+ self.headers.update({
142
+ "Accept": self.fingerprint["accept"],
143
+ "Accept-Language": self.fingerprint["accept_language"],
144
+ "Sec-CH-UA": self.fingerprint["sec_ch_ua"] or self.headers["Sec-CH-UA"],
145
+ "Sec-CH-UA-Platform": f'"{self.fingerprint["platform"]}"',
146
+ "User-Agent": self.fingerprint["user_agent"],
147
+ })
148
+
149
+ # Update session headers
150
+ for header, value in self.headers.items():
151
+ self.session.headers[header] = value
152
+
153
+ return self.fingerprint
154
+
155
+ @staticmethod
156
+ def _scira_extractor(chunk: Union[str, Dict[str, Any]]) -> Optional[str]:
157
+ """Extracts content from the Scira stream format '0:"..."'."""
158
+ if isinstance(chunk, str):
159
+ match = re.search(r'0:"(.*?)"(?=,|$)', chunk) # Look for 0:"...", possibly followed by comma or end of string
160
+ if match:
161
+ # Decode potential unicode escapes like \u00e9 and handle escaped quotes/backslashes
162
+ content = match.group(1).encode().decode('unicode_escape')
163
+ return content.replace('\\\\', '\\').replace('\\"', '"')
164
+ return None
165
+
166
+ def ask(
167
+ self,
168
+ prompt: str,
169
+ optimizer: str = None,
170
+ conversationally: bool = False,
171
+ ) -> Dict[str, Any]: # Note: Stream parameter removed as API doesn't seem to support it
172
+ conversation_prompt = self.conversation.gen_complete_prompt(prompt)
173
+ if optimizer:
174
+ if optimizer in self.__available_optimizers:
175
+ conversation_prompt = getattr(Optimizers, optimizer)(
176
+ conversation_prompt if conversationally else prompt
177
+ )
178
+ else:
179
+ raise Exception(f"Optimizer is not one of {self.__available_optimizers}")
180
+
181
+ messages = [
182
+ {"role": "system", "content": self.system_prompt},
183
+ {"role": "user", "content": conversation_prompt, "parts": [{"type": "text", "text": conversation_prompt}]}
184
+ ]
185
+
186
+ # Prepare the request payload
187
+ payload = {
188
+ "id": self.chat_id,
189
+ "messages": messages,
190
+ "model": self.model,
191
+ "group": self.search_mode,
192
+ "user_id": self.user_id,
193
+ "timezone": "Asia/Calcutta"
194
+ }
195
+
196
+ try:
197
+ # Use curl_cffi post with impersonate
198
+ response = self.session.post(
199
+ self.url,
200
+ json=payload,
201
+ timeout=self.timeout,
202
+ impersonate="chrome120" # Add impersonate
203
+ )
204
+ if response.status_code != 200:
205
+ # Try to get response content for better error messages
206
+ try: # Use try-except for reading response content
207
+ error_content = response.text
208
+ except:
209
+ error_content = "<could not read response content>"
210
+
211
+ if response.status_code in [403, 429]:
212
+ print(f"Received status code {response.status_code}, refreshing identity...")
213
+ self.refresh_identity()
214
+ response = self.session.post(
215
+ self.url, json=payload, timeout=self.timeout,
216
+ impersonate="chrome120" # Add impersonate to retry
217
+ )
218
+ if not response.ok:
219
+ raise exceptions.FailedToGenerateResponseError(
220
+ f"Failed to generate response after identity refresh - ({response.status_code}, {response.reason}) - {error_content}"
221
+ )
222
+ print("Identity refreshed successfully.")
223
+ else:
224
+ raise exceptions.FailedToGenerateResponseError(
225
+ f"Request failed with status code {response.status_code}. Response: {error_content}"
226
+ )
227
+
228
+ response_text_raw = response.text # Get raw response text
229
+
230
+ # Process the text using sanitize_stream line by line
231
+ processed_stream = sanitize_stream(
232
+ data=response_text_raw.splitlines(), # Split into lines
233
+ intro_value=None, # No simple prefix
234
+ to_json=False, # Content is not JSON
235
+ content_extractor=self._scira_extractor # Use the specific extractor
236
+ )
237
+
238
+ # Aggregate the results from the generator
239
+ full_response = ""
240
+ for content in processed_stream:
241
+ if content and isinstance(content, str):
242
+ full_response += content
243
+
244
+ self.last_response = {"text": full_response}
245
+ self.conversation.update_chat_history(prompt, full_response)
246
+ return {"text": full_response}
247
+ except CurlError as e: # Catch CurlError
248
+ raise exceptions.FailedToGenerateResponseError(f"Request failed (CurlError): {e}") from e
249
+ except Exception as e:
250
+ raise exceptions.FailedToGenerateResponseError(f"Request failed: {e}")
251
+
252
+ def chat(
253
+ self,
254
+ prompt: str,
255
+ optimizer: str = None,
256
+ conversationally: bool = False,
257
+ ) -> str:
258
+ return self.get_message(
259
+ self.ask(
260
+ prompt, optimizer=optimizer, conversationally=conversationally
261
+ )
262
+ )
263
+
264
+ def get_message(self, response: dict) -> str:
265
+ assert isinstance(response, dict), "Response should be of dict data-type only"
266
+ # Extractor handles formatting
267
+ return response.get("text", "").replace('\\n', '\n').replace('\\n\\n', '\n\n')
268
+
269
+ if __name__ == "__main__":
270
+ print("-" * 100)
271
+ print(f"{'Model':<50} {'Status':<10} {'Response'}")
272
+ print("-" * 100)
273
+
274
+ test_prompt = "Say 'Hello' in one word"
275
+
276
+ # Test each model
277
+ for model in SciraAI.AVAILABLE_MODELS:
278
+ print(f"\rTesting {model}...", end="")
279
+
280
+ try:
281
+ test_ai = SciraAI(model=model, timeout=120) # Increased timeout
282
+ response = test_ai.chat(test_prompt)
283
+
284
+ if response and len(response.strip()) > 0:
285
+ status = "✓"
286
+ # Clean and truncate response
287
+ clean_text = response.strip().encode('utf-8', errors='ignore').decode('utf-8')
288
+ display_text = clean_text[:50] + "..." if len(clean_text) > 50 else clean_text
289
+ else:
290
+ status = "✗"
291
+ display_text = "Empty or invalid response"
292
+
293
+ print(f"\r{model:<50} {status:<10} {display_text}")
294
+ except Exception as e:
295
+ error_msg = str(e)
296
+ # Truncate very long error messages
297
+ if len(error_msg) > 100:
298
+ error_msg = error_msg[:97] + "..."
299
+ print(f"\r{model:<50} {'✗':<10} Error: {error_msg}")