webscout 8.2.7__py3-none-any.whl → 8.2.8__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 (323) hide show
  1. webscout/AIauto.py +1 -1
  2. webscout/AIutel.py +298 -249
  3. webscout/Extra/Act.md +309 -0
  4. webscout/Extra/GitToolkit/__init__.py +10 -0
  5. webscout/Extra/GitToolkit/gitapi/README.md +110 -0
  6. webscout/Extra/GitToolkit/gitapi/__init__.py +12 -0
  7. webscout/Extra/GitToolkit/gitapi/repository.py +195 -0
  8. webscout/Extra/GitToolkit/gitapi/user.py +96 -0
  9. webscout/Extra/GitToolkit/gitapi/utils.py +62 -0
  10. webscout/Extra/YTToolkit/README.md +375 -0
  11. webscout/Extra/YTToolkit/YTdownloader.py +957 -0
  12. webscout/Extra/YTToolkit/__init__.py +3 -0
  13. webscout/Extra/YTToolkit/transcriber.py +476 -0
  14. webscout/Extra/YTToolkit/ytapi/README.md +44 -0
  15. webscout/Extra/YTToolkit/ytapi/__init__.py +6 -0
  16. webscout/Extra/YTToolkit/ytapi/channel.py +307 -0
  17. webscout/Extra/YTToolkit/ytapi/errors.py +13 -0
  18. webscout/Extra/YTToolkit/ytapi/extras.py +118 -0
  19. webscout/Extra/YTToolkit/ytapi/https.py +88 -0
  20. webscout/Extra/YTToolkit/ytapi/patterns.py +61 -0
  21. webscout/Extra/YTToolkit/ytapi/playlist.py +59 -0
  22. webscout/Extra/YTToolkit/ytapi/pool.py +8 -0
  23. webscout/Extra/YTToolkit/ytapi/query.py +40 -0
  24. webscout/Extra/YTToolkit/ytapi/stream.py +63 -0
  25. webscout/Extra/YTToolkit/ytapi/utils.py +62 -0
  26. webscout/Extra/YTToolkit/ytapi/video.py +232 -0
  27. webscout/Extra/__init__.py +7 -0
  28. webscout/Extra/autocoder/__init__.py +9 -0
  29. webscout/Extra/autocoder/autocoder.py +1105 -0
  30. webscout/Extra/autocoder/autocoder_utiles.py +332 -0
  31. webscout/Extra/gguf.md +430 -0
  32. webscout/Extra/gguf.py +684 -0
  33. webscout/Extra/tempmail/README.md +488 -0
  34. webscout/Extra/tempmail/__init__.py +28 -0
  35. webscout/Extra/tempmail/async_utils.py +141 -0
  36. webscout/Extra/tempmail/base.py +161 -0
  37. webscout/Extra/tempmail/cli.py +187 -0
  38. webscout/Extra/tempmail/emailnator.py +84 -0
  39. webscout/Extra/tempmail/mail_tm.py +361 -0
  40. webscout/Extra/tempmail/temp_mail_io.py +292 -0
  41. webscout/Extra/weather.md +281 -0
  42. webscout/Extra/weather.py +194 -0
  43. webscout/Extra/weather_ascii.py +76 -0
  44. webscout/Litlogger/Readme.md +175 -0
  45. webscout/Litlogger/__init__.py +67 -0
  46. webscout/Litlogger/core/__init__.py +6 -0
  47. webscout/Litlogger/core/level.py +23 -0
  48. webscout/Litlogger/core/logger.py +165 -0
  49. webscout/Litlogger/handlers/__init__.py +12 -0
  50. webscout/Litlogger/handlers/console.py +33 -0
  51. webscout/Litlogger/handlers/file.py +143 -0
  52. webscout/Litlogger/handlers/network.py +173 -0
  53. webscout/Litlogger/styles/__init__.py +7 -0
  54. webscout/Litlogger/styles/colors.py +249 -0
  55. webscout/Litlogger/styles/formats.py +458 -0
  56. webscout/Litlogger/styles/text.py +87 -0
  57. webscout/Litlogger/utils/__init__.py +6 -0
  58. webscout/Litlogger/utils/detectors.py +153 -0
  59. webscout/Litlogger/utils/formatters.py +200 -0
  60. webscout/Provider/AI21.py +177 -0
  61. webscout/Provider/AISEARCH/DeepFind.py +254 -0
  62. webscout/Provider/AISEARCH/Perplexity.py +359 -0
  63. webscout/Provider/AISEARCH/README.md +279 -0
  64. webscout/Provider/AISEARCH/__init__.py +9 -0
  65. webscout/Provider/AISEARCH/felo_search.py +228 -0
  66. webscout/Provider/AISEARCH/genspark_search.py +350 -0
  67. webscout/Provider/AISEARCH/hika_search.py +198 -0
  68. webscout/Provider/AISEARCH/iask_search.py +436 -0
  69. webscout/Provider/AISEARCH/monica_search.py +246 -0
  70. webscout/Provider/AISEARCH/scira_search.py +324 -0
  71. webscout/Provider/AISEARCH/webpilotai_search.py +281 -0
  72. webscout/Provider/Aitopia.py +316 -0
  73. webscout/Provider/AllenAI.py +440 -0
  74. webscout/Provider/Andi.py +228 -0
  75. webscout/Provider/Blackboxai.py +673 -0
  76. webscout/Provider/ChatGPTClone.py +237 -0
  77. webscout/Provider/ChatGPTGratis.py +194 -0
  78. webscout/Provider/ChatSandbox.py +342 -0
  79. webscout/Provider/Cloudflare.py +324 -0
  80. webscout/Provider/Cohere.py +208 -0
  81. webscout/Provider/Deepinfra.py +340 -0
  82. webscout/Provider/ExaAI.py +261 -0
  83. webscout/Provider/ExaChat.py +358 -0
  84. webscout/Provider/Flowith.py +217 -0
  85. webscout/Provider/FreeGemini.py +250 -0
  86. webscout/Provider/Gemini.py +169 -0
  87. webscout/Provider/GithubChat.py +370 -0
  88. webscout/Provider/GizAI.py +295 -0
  89. webscout/Provider/Glider.py +225 -0
  90. webscout/Provider/Groq.py +801 -0
  91. webscout/Provider/HF_space/__init__.py +0 -0
  92. webscout/Provider/HF_space/qwen_qwen2.py +206 -0
  93. webscout/Provider/HeckAI.py +285 -0
  94. webscout/Provider/HuggingFaceChat.py +469 -0
  95. webscout/Provider/Hunyuan.py +283 -0
  96. webscout/Provider/Jadve.py +291 -0
  97. webscout/Provider/Koboldai.py +384 -0
  98. webscout/Provider/LambdaChat.py +411 -0
  99. webscout/Provider/Llama3.py +259 -0
  100. webscout/Provider/MCPCore.py +315 -0
  101. webscout/Provider/Marcus.py +198 -0
  102. webscout/Provider/Nemotron.py +218 -0
  103. webscout/Provider/Netwrck.py +270 -0
  104. webscout/Provider/OLLAMA.py +396 -0
  105. webscout/Provider/OPENAI/BLACKBOXAI.py +735 -0
  106. webscout/Provider/OPENAI/Cloudflare.py +378 -0
  107. webscout/Provider/OPENAI/FreeGemini.py +282 -0
  108. webscout/Provider/OPENAI/NEMOTRON.py +244 -0
  109. webscout/Provider/OPENAI/README.md +1253 -0
  110. webscout/Provider/OPENAI/__init__.py +36 -0
  111. webscout/Provider/OPENAI/ai4chat.py +293 -0
  112. webscout/Provider/OPENAI/api.py +810 -0
  113. webscout/Provider/OPENAI/base.py +249 -0
  114. webscout/Provider/OPENAI/c4ai.py +373 -0
  115. webscout/Provider/OPENAI/chatgpt.py +556 -0
  116. webscout/Provider/OPENAI/chatgptclone.py +488 -0
  117. webscout/Provider/OPENAI/chatsandbox.py +172 -0
  118. webscout/Provider/OPENAI/deepinfra.py +319 -0
  119. webscout/Provider/OPENAI/e2b.py +1356 -0
  120. webscout/Provider/OPENAI/exaai.py +411 -0
  121. webscout/Provider/OPENAI/exachat.py +443 -0
  122. webscout/Provider/OPENAI/flowith.py +162 -0
  123. webscout/Provider/OPENAI/freeaichat.py +359 -0
  124. webscout/Provider/OPENAI/glider.py +323 -0
  125. webscout/Provider/OPENAI/groq.py +361 -0
  126. webscout/Provider/OPENAI/heckai.py +307 -0
  127. webscout/Provider/OPENAI/llmchatco.py +335 -0
  128. webscout/Provider/OPENAI/mcpcore.py +383 -0
  129. webscout/Provider/OPENAI/multichat.py +376 -0
  130. webscout/Provider/OPENAI/netwrck.py +356 -0
  131. webscout/Provider/OPENAI/opkfc.py +496 -0
  132. webscout/Provider/OPENAI/scirachat.py +471 -0
  133. webscout/Provider/OPENAI/sonus.py +303 -0
  134. webscout/Provider/OPENAI/standardinput.py +433 -0
  135. webscout/Provider/OPENAI/textpollinations.py +339 -0
  136. webscout/Provider/OPENAI/toolbaz.py +413 -0
  137. webscout/Provider/OPENAI/typefully.py +355 -0
  138. webscout/Provider/OPENAI/typegpt.py +358 -0
  139. webscout/Provider/OPENAI/uncovrAI.py +462 -0
  140. webscout/Provider/OPENAI/utils.py +307 -0
  141. webscout/Provider/OPENAI/venice.py +425 -0
  142. webscout/Provider/OPENAI/wisecat.py +381 -0
  143. webscout/Provider/OPENAI/writecream.py +163 -0
  144. webscout/Provider/OPENAI/x0gpt.py +378 -0
  145. webscout/Provider/OPENAI/yep.py +356 -0
  146. webscout/Provider/OpenGPT.py +209 -0
  147. webscout/Provider/Openai.py +496 -0
  148. webscout/Provider/PI.py +429 -0
  149. webscout/Provider/Perplexitylabs.py +415 -0
  150. webscout/Provider/QwenLM.py +254 -0
  151. webscout/Provider/Reka.py +214 -0
  152. webscout/Provider/StandardInput.py +290 -0
  153. webscout/Provider/TTI/AiForce/README.md +159 -0
  154. webscout/Provider/TTI/AiForce/__init__.py +22 -0
  155. webscout/Provider/TTI/AiForce/async_aiforce.py +224 -0
  156. webscout/Provider/TTI/AiForce/sync_aiforce.py +245 -0
  157. webscout/Provider/TTI/FreeAIPlayground/README.md +99 -0
  158. webscout/Provider/TTI/FreeAIPlayground/__init__.py +9 -0
  159. webscout/Provider/TTI/FreeAIPlayground/async_freeaiplayground.py +181 -0
  160. webscout/Provider/TTI/FreeAIPlayground/sync_freeaiplayground.py +180 -0
  161. webscout/Provider/TTI/ImgSys/README.md +174 -0
  162. webscout/Provider/TTI/ImgSys/__init__.py +23 -0
  163. webscout/Provider/TTI/ImgSys/async_imgsys.py +202 -0
  164. webscout/Provider/TTI/ImgSys/sync_imgsys.py +195 -0
  165. webscout/Provider/TTI/MagicStudio/README.md +101 -0
  166. webscout/Provider/TTI/MagicStudio/__init__.py +2 -0
  167. webscout/Provider/TTI/MagicStudio/async_magicstudio.py +111 -0
  168. webscout/Provider/TTI/MagicStudio/sync_magicstudio.py +109 -0
  169. webscout/Provider/TTI/Nexra/README.md +155 -0
  170. webscout/Provider/TTI/Nexra/__init__.py +22 -0
  171. webscout/Provider/TTI/Nexra/async_nexra.py +286 -0
  172. webscout/Provider/TTI/Nexra/sync_nexra.py +258 -0
  173. webscout/Provider/TTI/PollinationsAI/README.md +146 -0
  174. webscout/Provider/TTI/PollinationsAI/__init__.py +23 -0
  175. webscout/Provider/TTI/PollinationsAI/async_pollinations.py +311 -0
  176. webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +265 -0
  177. webscout/Provider/TTI/README.md +128 -0
  178. webscout/Provider/TTI/__init__.py +12 -0
  179. webscout/Provider/TTI/aiarta/README.md +134 -0
  180. webscout/Provider/TTI/aiarta/__init__.py +2 -0
  181. webscout/Provider/TTI/aiarta/async_aiarta.py +482 -0
  182. webscout/Provider/TTI/aiarta/sync_aiarta.py +440 -0
  183. webscout/Provider/TTI/artbit/README.md +100 -0
  184. webscout/Provider/TTI/artbit/__init__.py +22 -0
  185. webscout/Provider/TTI/artbit/async_artbit.py +155 -0
  186. webscout/Provider/TTI/artbit/sync_artbit.py +148 -0
  187. webscout/Provider/TTI/fastflux/README.md +129 -0
  188. webscout/Provider/TTI/fastflux/__init__.py +22 -0
  189. webscout/Provider/TTI/fastflux/async_fastflux.py +261 -0
  190. webscout/Provider/TTI/fastflux/sync_fastflux.py +252 -0
  191. webscout/Provider/TTI/huggingface/README.md +114 -0
  192. webscout/Provider/TTI/huggingface/__init__.py +22 -0
  193. webscout/Provider/TTI/huggingface/async_huggingface.py +199 -0
  194. webscout/Provider/TTI/huggingface/sync_huggingface.py +195 -0
  195. webscout/Provider/TTI/piclumen/README.md +161 -0
  196. webscout/Provider/TTI/piclumen/__init__.py +23 -0
  197. webscout/Provider/TTI/piclumen/async_piclumen.py +268 -0
  198. webscout/Provider/TTI/piclumen/sync_piclumen.py +233 -0
  199. webscout/Provider/TTI/pixelmuse/README.md +79 -0
  200. webscout/Provider/TTI/pixelmuse/__init__.py +4 -0
  201. webscout/Provider/TTI/pixelmuse/async_pixelmuse.py +249 -0
  202. webscout/Provider/TTI/pixelmuse/sync_pixelmuse.py +182 -0
  203. webscout/Provider/TTI/talkai/README.md +139 -0
  204. webscout/Provider/TTI/talkai/__init__.py +4 -0
  205. webscout/Provider/TTI/talkai/async_talkai.py +229 -0
  206. webscout/Provider/TTI/talkai/sync_talkai.py +207 -0
  207. webscout/Provider/TTS/README.md +192 -0
  208. webscout/Provider/TTS/__init__.py +9 -0
  209. webscout/Provider/TTS/base.py +159 -0
  210. webscout/Provider/TTS/deepgram.py +156 -0
  211. webscout/Provider/TTS/elevenlabs.py +111 -0
  212. webscout/Provider/TTS/gesserit.py +128 -0
  213. webscout/Provider/TTS/murfai.py +113 -0
  214. webscout/Provider/TTS/parler.py +111 -0
  215. webscout/Provider/TTS/speechma.py +580 -0
  216. webscout/Provider/TTS/sthir.py +94 -0
  217. webscout/Provider/TTS/streamElements.py +333 -0
  218. webscout/Provider/TTS/utils.py +280 -0
  219. webscout/Provider/TeachAnything.py +229 -0
  220. webscout/Provider/TextPollinationsAI.py +308 -0
  221. webscout/Provider/TwoAI.py +280 -0
  222. webscout/Provider/TypliAI.py +305 -0
  223. webscout/Provider/UNFINISHED/ChatHub.py +209 -0
  224. webscout/Provider/UNFINISHED/Youchat.py +330 -0
  225. webscout/Provider/UNFINISHED/liner_api_request.py +263 -0
  226. webscout/Provider/UNFINISHED/oivscode.py +351 -0
  227. webscout/Provider/UNFINISHED/test_lmarena.py +119 -0
  228. webscout/Provider/Venice.py +258 -0
  229. webscout/Provider/VercelAI.py +253 -0
  230. webscout/Provider/WiseCat.py +233 -0
  231. webscout/Provider/WrDoChat.py +370 -0
  232. webscout/Provider/Writecream.py +246 -0
  233. webscout/Provider/WritingMate.py +269 -0
  234. webscout/Provider/__init__.py +172 -0
  235. webscout/Provider/ai4chat.py +149 -0
  236. webscout/Provider/akashgpt.py +335 -0
  237. webscout/Provider/asksteve.py +220 -0
  238. webscout/Provider/cerebras.py +290 -0
  239. webscout/Provider/chatglm.py +215 -0
  240. webscout/Provider/cleeai.py +213 -0
  241. webscout/Provider/copilot.py +425 -0
  242. webscout/Provider/elmo.py +283 -0
  243. webscout/Provider/freeaichat.py +285 -0
  244. webscout/Provider/geminiapi.py +208 -0
  245. webscout/Provider/granite.py +235 -0
  246. webscout/Provider/hermes.py +266 -0
  247. webscout/Provider/julius.py +223 -0
  248. webscout/Provider/koala.py +170 -0
  249. webscout/Provider/learnfastai.py +325 -0
  250. webscout/Provider/llama3mitril.py +215 -0
  251. webscout/Provider/llmchat.py +258 -0
  252. webscout/Provider/llmchatco.py +306 -0
  253. webscout/Provider/lmarena.py +198 -0
  254. webscout/Provider/meta.py +801 -0
  255. webscout/Provider/multichat.py +364 -0
  256. webscout/Provider/samurai.py +223 -0
  257. webscout/Provider/scira_chat.py +299 -0
  258. webscout/Provider/scnet.py +243 -0
  259. webscout/Provider/searchchat.py +292 -0
  260. webscout/Provider/sonus.py +258 -0
  261. webscout/Provider/talkai.py +194 -0
  262. webscout/Provider/toolbaz.py +353 -0
  263. webscout/Provider/turboseek.py +266 -0
  264. webscout/Provider/typefully.py +202 -0
  265. webscout/Provider/typegpt.py +289 -0
  266. webscout/Provider/uncovr.py +368 -0
  267. webscout/Provider/x0gpt.py +299 -0
  268. webscout/Provider/yep.py +389 -0
  269. webscout/__init__.py +4 -2
  270. webscout/cli.py +3 -28
  271. webscout/conversation.py +35 -35
  272. webscout/litagent/Readme.md +276 -0
  273. webscout/litagent/__init__.py +29 -0
  274. webscout/litagent/agent.py +455 -0
  275. webscout/litagent/constants.py +60 -0
  276. webscout/litprinter/__init__.py +59 -0
  277. webscout/scout/README.md +402 -0
  278. webscout/scout/__init__.py +8 -0
  279. webscout/scout/core/__init__.py +7 -0
  280. webscout/scout/core/crawler.py +140 -0
  281. webscout/scout/core/scout.py +568 -0
  282. webscout/scout/core/search_result.py +96 -0
  283. webscout/scout/core/text_analyzer.py +63 -0
  284. webscout/scout/core/text_utils.py +277 -0
  285. webscout/scout/core/web_analyzer.py +52 -0
  286. webscout/scout/element.py +460 -0
  287. webscout/scout/parsers/__init__.py +69 -0
  288. webscout/scout/parsers/html5lib_parser.py +172 -0
  289. webscout/scout/parsers/html_parser.py +236 -0
  290. webscout/scout/parsers/lxml_parser.py +178 -0
  291. webscout/scout/utils.py +37 -0
  292. webscout/swiftcli/Readme.md +323 -0
  293. webscout/swiftcli/__init__.py +95 -0
  294. webscout/swiftcli/core/__init__.py +7 -0
  295. webscout/swiftcli/core/cli.py +297 -0
  296. webscout/swiftcli/core/context.py +104 -0
  297. webscout/swiftcli/core/group.py +241 -0
  298. webscout/swiftcli/decorators/__init__.py +28 -0
  299. webscout/swiftcli/decorators/command.py +221 -0
  300. webscout/swiftcli/decorators/options.py +220 -0
  301. webscout/swiftcli/decorators/output.py +252 -0
  302. webscout/swiftcli/exceptions.py +21 -0
  303. webscout/swiftcli/plugins/__init__.py +9 -0
  304. webscout/swiftcli/plugins/base.py +135 -0
  305. webscout/swiftcli/plugins/manager.py +262 -0
  306. webscout/swiftcli/utils/__init__.py +59 -0
  307. webscout/swiftcli/utils/formatting.py +252 -0
  308. webscout/swiftcli/utils/parsing.py +267 -0
  309. webscout/version.py +1 -1
  310. webscout/webscout_search.py +2 -182
  311. webscout/webscout_search_async.py +1 -179
  312. webscout/zeroart/README.md +89 -0
  313. webscout/zeroart/__init__.py +135 -0
  314. webscout/zeroart/base.py +66 -0
  315. webscout/zeroart/effects.py +101 -0
  316. webscout/zeroart/fonts.py +1239 -0
  317. {webscout-8.2.7.dist-info → webscout-8.2.8.dist-info}/METADATA +115 -60
  318. webscout-8.2.8.dist-info/RECORD +334 -0
  319. {webscout-8.2.7.dist-info → webscout-8.2.8.dist-info}/WHEEL +1 -1
  320. webscout-8.2.7.dist-info/RECORD +0 -26
  321. {webscout-8.2.7.dist-info → webscout-8.2.8.dist-info}/entry_points.txt +0 -0
  322. {webscout-8.2.7.dist-info → webscout-8.2.8.dist-info}/licenses/LICENSE.md +0 -0
  323. {webscout-8.2.7.dist-info → webscout-8.2.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,249 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List, Dict, Optional, Union, Generator, Any, TypedDict, Callable
3
+ import json
4
+ import logging
5
+ from dataclasses import dataclass
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ # Import the utils for response structures
11
+ from webscout.Provider.OPENAI.utils import ChatCompletion, ChatCompletionChunk, Choice, ChatCompletionMessage, ToolCall, ToolFunction
12
+
13
+ # Define tool-related structures
14
+ class ToolDefinition(TypedDict):
15
+ """Definition of a tool that can be called by the AI"""
16
+ type: str
17
+ function: Dict[str, Any]
18
+
19
+ class FunctionParameters(TypedDict):
20
+ """Parameters for a function"""
21
+ type: str
22
+ properties: Dict[str, Dict[str, Any]]
23
+ required: List[str]
24
+
25
+ class FunctionDefinition(TypedDict):
26
+ """Definition of a function that can be called by the AI"""
27
+ name: str
28
+ description: str
29
+ parameters: FunctionParameters
30
+
31
+ @dataclass
32
+ class Tool:
33
+ """Tool class that can be passed to the provider"""
34
+ name: str
35
+ description: str
36
+ parameters: Dict[str, Dict[str, Any]]
37
+ required_params: List[str] = None
38
+ implementation: Optional[Callable] = None
39
+
40
+ def to_dict(self) -> ToolDefinition:
41
+ """Convert to OpenAI tool definition format"""
42
+ function_def = {
43
+ "name": self.name,
44
+ "description": self.description,
45
+ "parameters": {
46
+ "type": "object",
47
+ "properties": self.parameters,
48
+ "required": self.required_params or list(self.parameters.keys())
49
+ }
50
+ }
51
+
52
+ return {
53
+ "type": "function",
54
+ "function": function_def
55
+ }
56
+
57
+ def execute(self, arguments: Dict[str, Any]) -> Any:
58
+ """Execute the tool with the given arguments"""
59
+ if not self.implementation:
60
+ return f"Tool '{self.name}' does not have an implementation."
61
+
62
+ try:
63
+ return self.implementation(**arguments)
64
+ except Exception as e:
65
+ logger.error(f"Error executing tool '{self.name}': {str(e)}")
66
+ return f"Error executing tool '{self.name}': {str(e)}"
67
+
68
+ class BaseCompletions(ABC):
69
+ @abstractmethod
70
+ def create(
71
+ self,
72
+ *,
73
+ model: str,
74
+ messages: List[Dict[str, Any]], # Changed to Any to support complex message structures
75
+ max_tokens: Optional[int] = None,
76
+ stream: bool = False,
77
+ temperature: Optional[float] = None,
78
+ top_p: Optional[float] = None,
79
+ tools: Optional[List[Union[Tool, Dict[str, Any]]]] = None, # Support for tool definitions
80
+ tool_choice: Optional[Union[str, Dict[str, Any]]] = None, # Support for tool_choice parameter
81
+ **kwargs: Any
82
+ ) -> Union[ChatCompletion, Generator[ChatCompletionChunk, None, None]]:
83
+ """
84
+ Abstract method to create chat completions with tool support.
85
+
86
+ Args:
87
+ model: The model to use for completion
88
+ messages: List of message dictionaries
89
+ max_tokens: Maximum number of tokens to generate
90
+ stream: Whether to stream the response
91
+ temperature: Sampling temperature
92
+ top_p: Nucleus sampling parameter
93
+ tools: List of tool definitions available for the model to use
94
+ tool_choice: Control over which tool the model should use
95
+ **kwargs: Additional model-specific parameters
96
+
97
+ Returns:
98
+ Either a completion object or a generator of completion chunks if streaming
99
+ """
100
+ raise NotImplementedError
101
+
102
+ def format_tool_calls(self, tools: List[Union[Tool, Dict[str, Any]]]) -> List[Dict[str, Any]]:
103
+ """Convert tools to the format expected by the provider"""
104
+ formatted_tools = []
105
+
106
+ for tool in tools:
107
+ if isinstance(tool, Tool):
108
+ formatted_tools.append(tool.to_dict())
109
+ elif isinstance(tool, dict):
110
+ # Assume already formatted correctly
111
+ formatted_tools.append(tool)
112
+ else:
113
+ logger.warning(f"Skipping invalid tool type: {type(tool)}")
114
+
115
+ return formatted_tools
116
+
117
+ def process_tool_calls(self, tool_calls: List[Dict[str, Any]], available_tools: Dict[str, Tool]) -> List[Dict[str, Any]]:
118
+ """
119
+ Process tool calls and execute the relevant tools.
120
+
121
+ Args:
122
+ tool_calls: List of tool calls from the model
123
+ available_tools: Dictionary mapping tool names to their implementations
124
+
125
+ Returns:
126
+ List of results from executing the tools
127
+ """
128
+ results = []
129
+
130
+ for call in tool_calls:
131
+ try:
132
+ function_call = call.get("function", {})
133
+ tool_name = function_call.get("name")
134
+ arguments_str = function_call.get("arguments", "{}")
135
+
136
+ # Parse arguments
137
+ try:
138
+ if isinstance(arguments_str, str):
139
+ arguments = json.loads(arguments_str)
140
+ else:
141
+ arguments = arguments_str
142
+ except json.JSONDecodeError:
143
+ results.append({
144
+ "tool_call_id": call.get("id"),
145
+ "result": f"Error: Could not parse arguments JSON: {arguments_str}"
146
+ })
147
+ continue
148
+
149
+ # Execute the tool if available
150
+ if tool_name in available_tools:
151
+ tool_result = available_tools[tool_name].execute(arguments)
152
+ results.append({
153
+ "tool_call_id": call.get("id"),
154
+ "result": str(tool_result)
155
+ })
156
+ else:
157
+ results.append({
158
+ "tool_call_id": call.get("id"),
159
+ "result": f"Error: Tool '{tool_name}' not found."
160
+ })
161
+ except Exception as e:
162
+ logger.error(f"Error processing tool call: {str(e)}")
163
+ results.append({
164
+ "tool_call_id": call.get("id", "unknown"),
165
+ "result": f"Error processing tool call: {str(e)}"
166
+ })
167
+
168
+ return results
169
+
170
+
171
+ class BaseChat(ABC):
172
+ completions: BaseCompletions
173
+
174
+
175
+ class OpenAICompatibleProvider(ABC):
176
+ """
177
+ Abstract Base Class for providers mimicking the OpenAI Python client structure.
178
+ Requires a nested 'chat.completions' structure with tool support.
179
+ """
180
+ chat: BaseChat
181
+ available_tools: Dict[str, Tool] = {} # Dictionary of available tools
182
+ supports_tools: bool = False # Whether the provider supports tools
183
+ supports_tool_choice: bool = False # Whether the provider supports tool_choice
184
+
185
+ @abstractmethod
186
+ def __init__(self, api_key: Optional[str] = None, tools: Optional[List[Tool]] = None, **kwargs: Any):
187
+ """
188
+ Initialize the provider, potentially with an API key and tools.
189
+
190
+ Args:
191
+ api_key: Optional API key for the provider
192
+ tools: Optional list of tools to make available to the provider
193
+ **kwargs: Additional provider-specific parameters
194
+ """
195
+ self.available_tools = {}
196
+ if tools:
197
+ self.register_tools(tools)
198
+ raise NotImplementedError
199
+
200
+ @property
201
+ @abstractmethod
202
+ def models(self):
203
+ """
204
+ Property that returns an object with a .list() method returning available models.
205
+ Subclasses must implement this property.
206
+ """
207
+ pass
208
+
209
+ def register_tools(self, tools: List[Tool]) -> None:
210
+ """
211
+ Register tools with the provider.
212
+
213
+ Args:
214
+ tools: List of Tool objects to register
215
+ """
216
+ for tool in tools:
217
+ self.available_tools[tool.name] = tool
218
+
219
+ def get_tool_by_name(self, name: str) -> Optional[Tool]:
220
+ """Get a tool by name"""
221
+ return self.available_tools.get(name)
222
+
223
+ def format_tool_response(self, messages: List[Dict[str, Any]], tool_results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
224
+ """
225
+ Format tool results as messages to be sent back to the provider.
226
+
227
+ Args:
228
+ messages: The original messages
229
+ tool_results: Results from executing tools
230
+
231
+ Returns:
232
+ Updated message list with tool results
233
+ """
234
+ updated_messages = messages.copy()
235
+
236
+ # Find the assistant message with tool calls
237
+ for i, msg in enumerate(reversed(updated_messages)):
238
+ if msg.get("role") == "assistant" and "tool_calls" in msg:
239
+ # For each tool result, add a tool message
240
+ for result in tool_results:
241
+ tool_message = {
242
+ "role": "tool",
243
+ "tool_call_id": result["tool_call_id"],
244
+ "content": result["result"]
245
+ }
246
+ updated_messages.append(tool_message)
247
+ break
248
+
249
+ return updated_messages
@@ -0,0 +1,373 @@
1
+ import time
2
+ import uuid
3
+ import requests
4
+ import json
5
+ import re
6
+ from typing import List, Dict, Optional, Union, Generator, Any
7
+
8
+ # Import base classes and utility structures
9
+ from .base import OpenAICompatibleProvider, BaseChat, BaseCompletions
10
+ from .utils import (
11
+ ChatCompletionChunk, ChatCompletion, Choice, ChoiceDelta,
12
+ ChatCompletionMessage, CompletionUsage,
13
+ get_system_prompt, get_last_user_message, format_prompt # Import format_prompt
14
+ )
15
+
16
+ # Attempt to import LitAgent, fallback if not available
17
+ try:
18
+ from webscout.litagent import LitAgent
19
+ except ImportError:
20
+ # Define a dummy LitAgent if webscout is not installed or accessible
21
+ class LitAgent:
22
+ def generate_fingerprint(self, browser: str = "chrome") -> Dict[str, Any]:
23
+ # Return minimal default headers if LitAgent is unavailable
24
+ print("Warning: LitAgent not found. Using default minimal headers.")
25
+ return {
26
+ "accept": "*/*",
27
+ "accept_language": "en-US,en;q=0.9",
28
+ "platform": "Windows",
29
+ "sec_ch_ua": '"Not/A)Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"',
30
+ "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
31
+ "browser_type": browser,
32
+ }
33
+ def random(self) -> str:
34
+ # Return a default user agent if LitAgent is unavailable
35
+ print("Warning: LitAgent not found. Using default user agent.")
36
+ return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"
37
+
38
+
39
+ class Completions(BaseCompletions):
40
+ def __init__(self, client: 'C4AI'):
41
+ self._client = client
42
+
43
+ def create(
44
+ self,
45
+ *,
46
+ model: str,
47
+ messages: List[Dict[str, str]],
48
+ max_tokens: Optional[int] = 2000,
49
+ stream: bool = False,
50
+ temperature: Optional[float] = None,
51
+ top_p: Optional[float] = None,
52
+ **kwargs: Any
53
+ ) -> Union[ChatCompletion, Generator[ChatCompletionChunk, None, None]]:
54
+ """
55
+ Creates a model response for the given chat conversation.
56
+ Mimics openai.chat.completions.create
57
+ """
58
+ # Extract system prompt using utility function
59
+ system_prompt = get_system_prompt(messages)
60
+ if not system_prompt:
61
+ system_prompt = "You are a helpful assistant."
62
+
63
+ # Format the conversation history using format_prompt
64
+ # Note: C4AI API might expect only the *last* user message here.
65
+ # Sending the full history might cause issues.
66
+ # We exclude the system prompt from format_prompt as it's sent separately.
67
+ # We also set do_continue=True as C4AI adds its own assistant prompt implicitly.
68
+ conversation_prompt = format_prompt(messages, include_system=False, do_continue=True)
69
+
70
+ if not conversation_prompt:
71
+ # Fallback to last user message if formatted prompt is empty
72
+ last_user_message = get_last_user_message(messages)
73
+ if not last_user_message:
74
+ raise ValueError("No user message found or formatted prompt is empty.")
75
+ conversation_prompt = last_user_message
76
+
77
+ # Create or get conversation ID
78
+ if model not in self._client._conversation_data:
79
+ conversation_id = self._client.create_conversation(model, system_prompt)
80
+ if not conversation_id:
81
+ raise IOError(f"Failed to create conversation with model {model}")
82
+ else:
83
+ conversation_id = self._client._conversation_data[model]["conversationId"]
84
+ self._client._conversation_data[model]["messageId"] = self._client.fetch_message_id(conversation_id)
85
+
86
+ request_id = f"chatcmpl-{uuid.uuid4()}"
87
+ created_time = int(time.time())
88
+
89
+ # Pass the formatted conversation prompt
90
+ if stream:
91
+ return self._create_stream(request_id, created_time, model, conversation_id, conversation_prompt, system_prompt)
92
+ else:
93
+ return self._create_non_stream(request_id, created_time, model, conversation_id, conversation_prompt, system_prompt)
94
+
95
+ def _create_stream(
96
+ self, request_id: str, created_time: int, model: str, conversation_id: str, prompt: str, system_prompt: str
97
+ ) -> Generator[ChatCompletionChunk, None, None]:
98
+ try:
99
+ message_id = self._client._conversation_data[model]["messageId"]
100
+ url = f"{self._client.url}/api/chat/message"
101
+ payload = {
102
+ "conversationId": conversation_id,
103
+ "messageId": message_id,
104
+ "model": model,
105
+ "prompt": prompt, # Use the formatted conversation history as prompt
106
+ "preprompt": system_prompt,
107
+ "temperature": 0.7,
108
+ "top_p": 1,
109
+ "top_k": 50,
110
+ "max_tokens": self._client.max_tokens_to_sample,
111
+ "stop": [],
112
+ "stream": True
113
+ }
114
+
115
+ response = self._client.session.post(
116
+ url,
117
+ headers=self._client.headers,
118
+ json=payload,
119
+ stream=True,
120
+ timeout=self._client.timeout
121
+ )
122
+ response.raise_for_status()
123
+
124
+ full_text = ""
125
+ for line in response.iter_lines():
126
+ if line:
127
+ line = line.decode('utf-8')
128
+ if line.startswith('data: '):
129
+ data = line[6:]
130
+ if data == "[DONE]":
131
+ break
132
+
133
+ try:
134
+ json_data = json.loads(data)
135
+ delta_text = json_data.get('text', '')
136
+ new_content = delta_text[len(full_text):]
137
+ full_text = delta_text
138
+ delta = ChoiceDelta(content=new_content)
139
+ choice = Choice(index=0, delta=delta, finish_reason=None)
140
+ chunk = ChatCompletionChunk(
141
+ id=request_id,
142
+ choices=[choice],
143
+ created=created_time,
144
+ model=model
145
+ )
146
+ yield chunk
147
+ except json.JSONDecodeError:
148
+ continue
149
+
150
+ delta = ChoiceDelta(content=None)
151
+ choice = Choice(index=0, delta=delta, finish_reason="stop")
152
+ chunk = ChatCompletionChunk(
153
+ id=request_id,
154
+ choices=[choice],
155
+ created=created_time,
156
+ model=model
157
+ )
158
+ yield chunk
159
+
160
+ except Exception as e:
161
+ print(f"Error during C4AI stream request: {e}")
162
+ raise IOError(f"C4AI request failed: {e}") from e
163
+
164
+ def _create_non_stream(
165
+ self, request_id: str, created_time: int, model: str, conversation_id: str, prompt: str, system_prompt: str
166
+ ) -> ChatCompletion:
167
+ try:
168
+ message_id = self._client._conversation_data[model]["messageId"]
169
+ url = f"{self._client.url}/api/chat/message"
170
+ payload = {
171
+ "conversationId": conversation_id,
172
+ "messageId": message_id,
173
+ "model": model,
174
+ "prompt": prompt, # Use the formatted conversation history as prompt
175
+ "preprompt": system_prompt,
176
+ "temperature": 0.7,
177
+ "top_p": 1,
178
+ "top_k": 50,
179
+ "max_tokens": self._client.max_tokens_to_sample,
180
+ "stop": [],
181
+ "stream": False
182
+ }
183
+
184
+ response = self._client.session.post(
185
+ url,
186
+ headers=self._client.headers,
187
+ json=payload,
188
+ timeout=self._client.timeout
189
+ )
190
+ response.raise_for_status()
191
+
192
+ data = response.json()
193
+ response_text = data.get('text', '')
194
+ message = ChatCompletionMessage(role="assistant", content=response_text)
195
+ choice = Choice(index=0, message=message, finish_reason="stop")
196
+ # Estimate tokens based on the formatted prompt
197
+ prompt_tokens = len(prompt) // 4
198
+ completion_tokens = len(response_text) // 4
199
+ usage = CompletionUsage(
200
+ prompt_tokens=prompt_tokens,
201
+ completion_tokens=completion_tokens,
202
+ total_tokens=prompt_tokens + completion_tokens
203
+ )
204
+ completion = ChatCompletion(
205
+ id=request_id,
206
+ choices=[choice],
207
+ created=created_time,
208
+ model=model,
209
+ usage=usage
210
+ )
211
+ return completion
212
+
213
+ except Exception as e:
214
+ print(f"Error during C4AI non-stream request: {e}")
215
+ raise IOError(f"C4AI request failed: {e}") from e
216
+
217
+ class Chat(BaseChat):
218
+ def __init__(self, client: 'C4AI'):
219
+ self.completions = Completions(client)
220
+
221
+ class C4AI(OpenAICompatibleProvider):
222
+ """
223
+ OpenAI-compatible client for C4AI API.
224
+
225
+ Usage:
226
+ client = C4AI()
227
+ response = client.chat.completions.create(
228
+ model="command-a-03-2025",
229
+ messages=[{"role": "user", "content": "Hello!"}]
230
+ )
231
+ """
232
+
233
+ AVAILABLE_MODELS = [
234
+ 'command-a-03-2025',
235
+ 'command-r-plus-08-2024',
236
+ 'command-r-08-2024',
237
+ 'command-r-plus',
238
+ 'command-r',
239
+ 'command-r7b-12-2024',
240
+ 'command-r7b-arabic-02-2025'
241
+ ]
242
+
243
+ def __init__(
244
+ self,
245
+ timeout: Optional[int] = None,
246
+ browser: str = "chrome"
247
+ ):
248
+ """
249
+ Initialize the C4AI client.
250
+
251
+ Args:
252
+ timeout: Request timeout in seconds.
253
+ browser: Browser name for LitAgent to generate User-Agent.
254
+ """
255
+ self.timeout = timeout
256
+ self.url = "https://cohereforai-c4ai-command.hf.space"
257
+ self.session = requests.Session()
258
+ self.max_tokens_to_sample = 2000
259
+
260
+ agent = LitAgent()
261
+ fingerprint = agent.generate_fingerprint(browser)
262
+
263
+ self.headers = {
264
+ "Content-Type": "application/json",
265
+ "User-Agent": fingerprint["user_agent"],
266
+ "Accept": "*/*",
267
+ "Accept-Encoding": "gzip, deflate, br, zstd",
268
+ "Accept-Language": fingerprint["accept_language"],
269
+ "Origin": "https://cohereforai-c4ai-command.hf.space",
270
+ "Referer": "https://cohereforai-c4ai-command.hf.space/",
271
+ "Sec-Ch-Ua": fingerprint["sec_ch_ua"] or "\"Chromium\";v=\"120\"",
272
+ "Sec-Ch-Ua-Mobile": "?0",
273
+ "Sec-Ch-Ua-Platform": f"\"{fingerprint['platform']}\"",
274
+ "Sec-Fetch-Dest": "empty",
275
+ "Sec-Fetch-Mode": "cors",
276
+ "Sec-Fetch-Site": "same-origin",
277
+ "DNT": "1",
278
+ "Priority": "u=1, i"
279
+ }
280
+
281
+ self._conversation_data = {}
282
+ self.chat = Chat(self)
283
+ self.update_available_models()
284
+
285
+ def update_available_models(self):
286
+ """Update the list of available models from the server."""
287
+ try:
288
+ response = requests.get("https://cohereforai-c4ai-command.hf.space/")
289
+ text = response.text
290
+ models_match = re.search(r'models:(\[.+?\]),oldModels:', text)
291
+
292
+ if not models_match:
293
+ return
294
+
295
+ models_text = models_match.group(1)
296
+ models_text = re.sub(r',parameters:{[^}]+?}', '', models_text)
297
+ models_text = models_text.replace('void 0', 'null')
298
+
299
+ def add_quotation_mark(match):
300
+ return f'{match.group(1)}"{match.group(2)}":'
301
+
302
+ models_text = re.sub(r'([{,])([A-Za-z0-9_]+?):', add_quotation_mark, models_text)
303
+
304
+ models_data = json.loads(models_text)
305
+ self.AVAILABLE_MODELS = [model["id"] for model in models_data]
306
+ except Exception:
307
+ pass
308
+
309
+ def create_conversation(self, model: str, system_prompt: str):
310
+ """Create a new conversation with the specified model."""
311
+ url = f"{self.url}/api/conversation"
312
+ payload = {
313
+ "model": model,
314
+ "preprompt": system_prompt,
315
+ }
316
+
317
+ try:
318
+ response = self.session.post(
319
+ url,
320
+ headers=self.headers,
321
+ json=payload,
322
+ timeout=self.timeout
323
+ )
324
+ response.raise_for_status()
325
+
326
+ data = response.json()
327
+ conversation_id = data.get("conversationId")
328
+
329
+ if conversation_id:
330
+ self._conversation_data[model] = {
331
+ "conversationId": conversation_id,
332
+ "messageId": self.fetch_message_id(conversation_id)
333
+ }
334
+ return conversation_id
335
+
336
+ return None
337
+
338
+ except Exception as e:
339
+ print(f"Error creating conversation: {e}")
340
+ return None
341
+
342
+ def fetch_message_id(self, conversation_id: str):
343
+ """Fetch the latest message ID for a conversation."""
344
+ url = f"{self.url}/api/conversation/{conversation_id}"
345
+
346
+ try:
347
+ response = self.session.get(
348
+ url,
349
+ headers=self.headers,
350
+ timeout=self.timeout
351
+ )
352
+ response.raise_for_status()
353
+
354
+ json_data = response.json()
355
+
356
+ if json_data.get("nodes", []) and json_data["nodes"][-1].get("type") == "error":
357
+ return str(uuid.uuid4())
358
+
359
+ data = json_data["nodes"][1]["data"]
360
+ keys = data[data[0]["messages"]]
361
+ message_keys = data[keys[-1]]
362
+ message_id = data[message_keys["id"]]
363
+
364
+ return message_id
365
+
366
+ except Exception:
367
+ return str(uuid.uuid4())
368
+ @property
369
+ def models(self):
370
+ class _ModelList:
371
+ def list(inner_self):
372
+ return type(self).AVAILABLE_MODELS
373
+ return _ModelList()