webscout 8.2.9__py3-none-any.whl → 2026.1.19__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 (413) hide show
  1. webscout/AIauto.py +524 -251
  2. webscout/AIbase.py +247 -319
  3. webscout/AIutel.py +68 -703
  4. webscout/Bard.py +1072 -1026
  5. webscout/Extra/GitToolkit/__init__.py +10 -10
  6. webscout/Extra/GitToolkit/gitapi/__init__.py +20 -12
  7. webscout/Extra/GitToolkit/gitapi/gist.py +142 -0
  8. webscout/Extra/GitToolkit/gitapi/organization.py +91 -0
  9. webscout/Extra/GitToolkit/gitapi/repository.py +308 -195
  10. webscout/Extra/GitToolkit/gitapi/search.py +162 -0
  11. webscout/Extra/GitToolkit/gitapi/trending.py +236 -0
  12. webscout/Extra/GitToolkit/gitapi/user.py +128 -96
  13. webscout/Extra/GitToolkit/gitapi/utils.py +82 -62
  14. webscout/Extra/YTToolkit/README.md +443 -375
  15. webscout/Extra/YTToolkit/YTdownloader.py +953 -957
  16. webscout/Extra/YTToolkit/__init__.py +3 -3
  17. webscout/Extra/YTToolkit/transcriber.py +595 -476
  18. webscout/Extra/YTToolkit/ytapi/README.md +230 -44
  19. webscout/Extra/YTToolkit/ytapi/__init__.py +22 -6
  20. webscout/Extra/YTToolkit/ytapi/captions.py +190 -0
  21. webscout/Extra/YTToolkit/ytapi/channel.py +302 -307
  22. webscout/Extra/YTToolkit/ytapi/errors.py +13 -13
  23. webscout/Extra/YTToolkit/ytapi/extras.py +178 -118
  24. webscout/Extra/YTToolkit/ytapi/hashtag.py +120 -0
  25. webscout/Extra/YTToolkit/ytapi/https.py +89 -88
  26. webscout/Extra/YTToolkit/ytapi/patterns.py +61 -61
  27. webscout/Extra/YTToolkit/ytapi/playlist.py +59 -59
  28. webscout/Extra/YTToolkit/ytapi/pool.py +8 -8
  29. webscout/Extra/YTToolkit/ytapi/query.py +143 -40
  30. webscout/Extra/YTToolkit/ytapi/shorts.py +122 -0
  31. webscout/Extra/YTToolkit/ytapi/stream.py +68 -63
  32. webscout/Extra/YTToolkit/ytapi/suggestions.py +97 -0
  33. webscout/Extra/YTToolkit/ytapi/utils.py +66 -62
  34. webscout/Extra/YTToolkit/ytapi/video.py +403 -232
  35. webscout/Extra/__init__.py +2 -3
  36. webscout/Extra/gguf.py +1298 -684
  37. webscout/Extra/tempmail/README.md +487 -487
  38. webscout/Extra/tempmail/__init__.py +28 -28
  39. webscout/Extra/tempmail/async_utils.py +143 -141
  40. webscout/Extra/tempmail/base.py +172 -161
  41. webscout/Extra/tempmail/cli.py +191 -187
  42. webscout/Extra/tempmail/emailnator.py +88 -84
  43. webscout/Extra/tempmail/mail_tm.py +378 -361
  44. webscout/Extra/tempmail/temp_mail_io.py +304 -292
  45. webscout/Extra/weather.py +196 -194
  46. webscout/Extra/weather_ascii.py +17 -15
  47. webscout/Provider/AISEARCH/PERPLEXED_search.py +175 -0
  48. webscout/Provider/AISEARCH/Perplexity.py +292 -333
  49. webscout/Provider/AISEARCH/README.md +106 -279
  50. webscout/Provider/AISEARCH/__init__.py +16 -9
  51. webscout/Provider/AISEARCH/brave_search.py +298 -0
  52. webscout/Provider/AISEARCH/iask_search.py +357 -410
  53. webscout/Provider/AISEARCH/monica_search.py +200 -220
  54. webscout/Provider/AISEARCH/webpilotai_search.py +242 -255
  55. webscout/Provider/Algion.py +413 -0
  56. webscout/Provider/Andi.py +74 -69
  57. webscout/Provider/Apriel.py +313 -0
  58. webscout/Provider/Ayle.py +323 -0
  59. webscout/Provider/ChatSandbox.py +329 -342
  60. webscout/Provider/ClaudeOnline.py +365 -0
  61. webscout/Provider/Cohere.py +232 -208
  62. webscout/Provider/DeepAI.py +367 -0
  63. webscout/Provider/Deepinfra.py +467 -340
  64. webscout/Provider/EssentialAI.py +217 -0
  65. webscout/Provider/ExaAI.py +274 -261
  66. webscout/Provider/Gemini.py +175 -169
  67. webscout/Provider/GithubChat.py +385 -369
  68. webscout/Provider/Gradient.py +286 -0
  69. webscout/Provider/Groq.py +556 -801
  70. webscout/Provider/HadadXYZ.py +323 -0
  71. webscout/Provider/HeckAI.py +392 -375
  72. webscout/Provider/HuggingFace.py +387 -0
  73. webscout/Provider/IBM.py +340 -0
  74. webscout/Provider/Jadve.py +317 -291
  75. webscout/Provider/K2Think.py +306 -0
  76. webscout/Provider/Koboldai.py +221 -384
  77. webscout/Provider/Netwrck.py +273 -270
  78. webscout/Provider/Nvidia.py +310 -0
  79. webscout/Provider/OPENAI/DeepAI.py +489 -0
  80. webscout/Provider/OPENAI/K2Think.py +423 -0
  81. webscout/Provider/OPENAI/PI.py +463 -0
  82. webscout/Provider/OPENAI/README.md +890 -952
  83. webscout/Provider/OPENAI/TogetherAI.py +405 -0
  84. webscout/Provider/OPENAI/TwoAI.py +255 -357
  85. webscout/Provider/OPENAI/__init__.py +148 -40
  86. webscout/Provider/OPENAI/ai4chat.py +348 -293
  87. webscout/Provider/OPENAI/akashgpt.py +436 -0
  88. webscout/Provider/OPENAI/algion.py +303 -0
  89. webscout/Provider/OPENAI/{exachat.py → ayle.py} +365 -444
  90. webscout/Provider/OPENAI/base.py +253 -249
  91. webscout/Provider/OPENAI/cerebras.py +296 -0
  92. webscout/Provider/OPENAI/chatgpt.py +870 -556
  93. webscout/Provider/OPENAI/chatsandbox.py +233 -173
  94. webscout/Provider/OPENAI/deepinfra.py +403 -322
  95. webscout/Provider/OPENAI/e2b.py +2370 -1414
  96. webscout/Provider/OPENAI/elmo.py +278 -0
  97. webscout/Provider/OPENAI/exaai.py +452 -417
  98. webscout/Provider/OPENAI/freeassist.py +446 -0
  99. webscout/Provider/OPENAI/gradient.py +448 -0
  100. webscout/Provider/OPENAI/groq.py +380 -364
  101. webscout/Provider/OPENAI/hadadxyz.py +292 -0
  102. webscout/Provider/OPENAI/heckai.py +333 -308
  103. webscout/Provider/OPENAI/huggingface.py +321 -0
  104. webscout/Provider/OPENAI/ibm.py +425 -0
  105. webscout/Provider/OPENAI/llmchat.py +253 -0
  106. webscout/Provider/OPENAI/llmchatco.py +378 -335
  107. webscout/Provider/OPENAI/meta.py +541 -0
  108. webscout/Provider/OPENAI/netwrck.py +374 -357
  109. webscout/Provider/OPENAI/nvidia.py +317 -0
  110. webscout/Provider/OPENAI/oivscode.py +348 -287
  111. webscout/Provider/OPENAI/openrouter.py +328 -0
  112. webscout/Provider/OPENAI/pydantic_imports.py +1 -172
  113. webscout/Provider/OPENAI/sambanova.py +397 -0
  114. webscout/Provider/OPENAI/sonus.py +305 -304
  115. webscout/Provider/OPENAI/textpollinations.py +370 -339
  116. webscout/Provider/OPENAI/toolbaz.py +375 -413
  117. webscout/Provider/OPENAI/typefully.py +419 -355
  118. webscout/Provider/OPENAI/typliai.py +279 -0
  119. webscout/Provider/OPENAI/utils.py +314 -318
  120. webscout/Provider/OPENAI/wisecat.py +359 -387
  121. webscout/Provider/OPENAI/writecream.py +185 -163
  122. webscout/Provider/OPENAI/x0gpt.py +462 -365
  123. webscout/Provider/OPENAI/zenmux.py +380 -0
  124. webscout/Provider/OpenRouter.py +386 -0
  125. webscout/Provider/Openai.py +337 -496
  126. webscout/Provider/PI.py +443 -429
  127. webscout/Provider/QwenLM.py +346 -254
  128. webscout/Provider/STT/__init__.py +28 -0
  129. webscout/Provider/STT/base.py +303 -0
  130. webscout/Provider/STT/elevenlabs.py +264 -0
  131. webscout/Provider/Sambanova.py +317 -0
  132. webscout/Provider/TTI/README.md +69 -82
  133. webscout/Provider/TTI/__init__.py +37 -7
  134. webscout/Provider/TTI/base.py +147 -64
  135. webscout/Provider/TTI/claudeonline.py +393 -0
  136. webscout/Provider/TTI/magicstudio.py +292 -201
  137. webscout/Provider/TTI/miragic.py +180 -0
  138. webscout/Provider/TTI/pollinations.py +331 -221
  139. webscout/Provider/TTI/together.py +334 -0
  140. webscout/Provider/TTI/utils.py +14 -11
  141. webscout/Provider/TTS/README.md +186 -192
  142. webscout/Provider/TTS/__init__.py +43 -10
  143. webscout/Provider/TTS/base.py +523 -159
  144. webscout/Provider/TTS/deepgram.py +286 -156
  145. webscout/Provider/TTS/elevenlabs.py +189 -111
  146. webscout/Provider/TTS/freetts.py +218 -0
  147. webscout/Provider/TTS/murfai.py +288 -113
  148. webscout/Provider/TTS/openai_fm.py +364 -129
  149. webscout/Provider/TTS/parler.py +203 -111
  150. webscout/Provider/TTS/qwen.py +334 -0
  151. webscout/Provider/TTS/sherpa.py +286 -0
  152. webscout/Provider/TTS/speechma.py +693 -580
  153. webscout/Provider/TTS/streamElements.py +275 -333
  154. webscout/Provider/TTS/utils.py +280 -280
  155. webscout/Provider/TextPollinationsAI.py +331 -308
  156. webscout/Provider/TogetherAI.py +450 -0
  157. webscout/Provider/TwoAI.py +309 -475
  158. webscout/Provider/TypliAI.py +311 -305
  159. webscout/Provider/UNFINISHED/ChatHub.py +219 -209
  160. webscout/Provider/{OPENAI/glider.py → UNFINISHED/ChutesAI.py} +331 -326
  161. webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +300 -295
  162. webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +218 -198
  163. webscout/Provider/UNFINISHED/Qodo.py +481 -0
  164. webscout/Provider/{MCPCore.py → UNFINISHED/XenAI.py} +330 -315
  165. webscout/Provider/UNFINISHED/Youchat.py +347 -330
  166. webscout/Provider/UNFINISHED/aihumanizer.py +41 -0
  167. webscout/Provider/UNFINISHED/grammerchecker.py +37 -0
  168. webscout/Provider/UNFINISHED/liner.py +342 -0
  169. webscout/Provider/UNFINISHED/liner_api_request.py +246 -263
  170. webscout/Provider/{samurai.py → UNFINISHED/samurai.py} +231 -224
  171. webscout/Provider/WiseCat.py +256 -233
  172. webscout/Provider/WrDoChat.py +390 -370
  173. webscout/Provider/__init__.py +115 -174
  174. webscout/Provider/ai4chat.py +181 -174
  175. webscout/Provider/akashgpt.py +330 -335
  176. webscout/Provider/cerebras.py +397 -290
  177. webscout/Provider/cleeai.py +236 -213
  178. webscout/Provider/elmo.py +291 -283
  179. webscout/Provider/geminiapi.py +343 -208
  180. webscout/Provider/julius.py +245 -223
  181. webscout/Provider/learnfastai.py +333 -325
  182. webscout/Provider/llama3mitril.py +230 -215
  183. webscout/Provider/llmchat.py +308 -258
  184. webscout/Provider/llmchatco.py +321 -306
  185. webscout/Provider/meta.py +996 -801
  186. webscout/Provider/oivscode.py +332 -309
  187. webscout/Provider/searchchat.py +316 -292
  188. webscout/Provider/sonus.py +264 -258
  189. webscout/Provider/toolbaz.py +359 -353
  190. webscout/Provider/turboseek.py +332 -266
  191. webscout/Provider/typefully.py +262 -202
  192. webscout/Provider/x0gpt.py +332 -299
  193. webscout/__init__.py +31 -39
  194. webscout/__main__.py +5 -5
  195. webscout/cli.py +585 -524
  196. webscout/client.py +1497 -70
  197. webscout/conversation.py +140 -436
  198. webscout/exceptions.py +383 -362
  199. webscout/litagent/__init__.py +29 -29
  200. webscout/litagent/agent.py +492 -455
  201. webscout/litagent/constants.py +60 -60
  202. webscout/models.py +505 -181
  203. webscout/optimizers.py +74 -420
  204. webscout/prompt_manager.py +376 -288
  205. webscout/sanitize.py +1514 -0
  206. webscout/scout/README.md +452 -404
  207. webscout/scout/__init__.py +8 -8
  208. webscout/scout/core/__init__.py +7 -7
  209. webscout/scout/core/crawler.py +330 -210
  210. webscout/scout/core/scout.py +800 -607
  211. webscout/scout/core/search_result.py +51 -96
  212. webscout/scout/core/text_analyzer.py +64 -63
  213. webscout/scout/core/text_utils.py +412 -277
  214. webscout/scout/core/web_analyzer.py +54 -52
  215. webscout/scout/element.py +872 -478
  216. webscout/scout/parsers/__init__.py +70 -69
  217. webscout/scout/parsers/html5lib_parser.py +182 -172
  218. webscout/scout/parsers/html_parser.py +238 -236
  219. webscout/scout/parsers/lxml_parser.py +203 -178
  220. webscout/scout/utils.py +38 -37
  221. webscout/search/__init__.py +47 -0
  222. webscout/search/base.py +201 -0
  223. webscout/search/bing_main.py +45 -0
  224. webscout/search/brave_main.py +92 -0
  225. webscout/search/duckduckgo_main.py +57 -0
  226. webscout/search/engines/__init__.py +127 -0
  227. webscout/search/engines/bing/__init__.py +15 -0
  228. webscout/search/engines/bing/base.py +35 -0
  229. webscout/search/engines/bing/images.py +114 -0
  230. webscout/search/engines/bing/news.py +96 -0
  231. webscout/search/engines/bing/suggestions.py +36 -0
  232. webscout/search/engines/bing/text.py +109 -0
  233. webscout/search/engines/brave/__init__.py +19 -0
  234. webscout/search/engines/brave/base.py +47 -0
  235. webscout/search/engines/brave/images.py +213 -0
  236. webscout/search/engines/brave/news.py +353 -0
  237. webscout/search/engines/brave/suggestions.py +318 -0
  238. webscout/search/engines/brave/text.py +167 -0
  239. webscout/search/engines/brave/videos.py +364 -0
  240. webscout/search/engines/duckduckgo/__init__.py +25 -0
  241. webscout/search/engines/duckduckgo/answers.py +80 -0
  242. webscout/search/engines/duckduckgo/base.py +189 -0
  243. webscout/search/engines/duckduckgo/images.py +100 -0
  244. webscout/search/engines/duckduckgo/maps.py +183 -0
  245. webscout/search/engines/duckduckgo/news.py +70 -0
  246. webscout/search/engines/duckduckgo/suggestions.py +22 -0
  247. webscout/search/engines/duckduckgo/text.py +221 -0
  248. webscout/search/engines/duckduckgo/translate.py +48 -0
  249. webscout/search/engines/duckduckgo/videos.py +80 -0
  250. webscout/search/engines/duckduckgo/weather.py +84 -0
  251. webscout/search/engines/mojeek.py +61 -0
  252. webscout/search/engines/wikipedia.py +77 -0
  253. webscout/search/engines/yahoo/__init__.py +41 -0
  254. webscout/search/engines/yahoo/answers.py +19 -0
  255. webscout/search/engines/yahoo/base.py +34 -0
  256. webscout/search/engines/yahoo/images.py +323 -0
  257. webscout/search/engines/yahoo/maps.py +19 -0
  258. webscout/search/engines/yahoo/news.py +258 -0
  259. webscout/search/engines/yahoo/suggestions.py +140 -0
  260. webscout/search/engines/yahoo/text.py +273 -0
  261. webscout/search/engines/yahoo/translate.py +19 -0
  262. webscout/search/engines/yahoo/videos.py +302 -0
  263. webscout/search/engines/yahoo/weather.py +220 -0
  264. webscout/search/engines/yandex.py +67 -0
  265. webscout/search/engines/yep/__init__.py +13 -0
  266. webscout/search/engines/yep/base.py +34 -0
  267. webscout/search/engines/yep/images.py +101 -0
  268. webscout/search/engines/yep/suggestions.py +38 -0
  269. webscout/search/engines/yep/text.py +99 -0
  270. webscout/search/http_client.py +172 -0
  271. webscout/search/results.py +141 -0
  272. webscout/search/yahoo_main.py +57 -0
  273. webscout/search/yep_main.py +48 -0
  274. webscout/server/__init__.py +48 -0
  275. webscout/server/config.py +78 -0
  276. webscout/server/exceptions.py +69 -0
  277. webscout/server/providers.py +286 -0
  278. webscout/server/request_models.py +131 -0
  279. webscout/server/request_processing.py +404 -0
  280. webscout/server/routes.py +642 -0
  281. webscout/server/server.py +351 -0
  282. webscout/server/ui_templates.py +1171 -0
  283. webscout/swiftcli/__init__.py +79 -95
  284. webscout/swiftcli/core/__init__.py +7 -7
  285. webscout/swiftcli/core/cli.py +574 -297
  286. webscout/swiftcli/core/context.py +98 -104
  287. webscout/swiftcli/core/group.py +268 -241
  288. webscout/swiftcli/decorators/__init__.py +28 -28
  289. webscout/swiftcli/decorators/command.py +243 -221
  290. webscout/swiftcli/decorators/options.py +247 -220
  291. webscout/swiftcli/decorators/output.py +392 -252
  292. webscout/swiftcli/exceptions.py +21 -21
  293. webscout/swiftcli/plugins/__init__.py +9 -9
  294. webscout/swiftcli/plugins/base.py +134 -135
  295. webscout/swiftcli/plugins/manager.py +269 -269
  296. webscout/swiftcli/utils/__init__.py +58 -59
  297. webscout/swiftcli/utils/formatting.py +251 -252
  298. webscout/swiftcli/utils/parsing.py +368 -267
  299. webscout/update_checker.py +280 -136
  300. webscout/utils.py +28 -14
  301. webscout/version.py +2 -1
  302. webscout/version.py.bak +3 -0
  303. webscout/zeroart/__init__.py +218 -135
  304. webscout/zeroart/base.py +70 -66
  305. webscout/zeroart/effects.py +155 -101
  306. webscout/zeroart/fonts.py +1799 -1239
  307. webscout-2026.1.19.dist-info/METADATA +638 -0
  308. webscout-2026.1.19.dist-info/RECORD +312 -0
  309. {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/WHEEL +1 -1
  310. {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/entry_points.txt +1 -1
  311. webscout/DWEBS.py +0 -520
  312. webscout/Extra/Act.md +0 -309
  313. webscout/Extra/GitToolkit/gitapi/README.md +0 -110
  314. webscout/Extra/autocoder/__init__.py +0 -9
  315. webscout/Extra/autocoder/autocoder.py +0 -1105
  316. webscout/Extra/autocoder/autocoder_utiles.py +0 -332
  317. webscout/Extra/gguf.md +0 -430
  318. webscout/Extra/weather.md +0 -281
  319. webscout/Litlogger/README.md +0 -10
  320. webscout/Litlogger/__init__.py +0 -15
  321. webscout/Litlogger/formats.py +0 -4
  322. webscout/Litlogger/handlers.py +0 -103
  323. webscout/Litlogger/levels.py +0 -13
  324. webscout/Litlogger/logger.py +0 -92
  325. webscout/Provider/AI21.py +0 -177
  326. webscout/Provider/AISEARCH/DeepFind.py +0 -254
  327. webscout/Provider/AISEARCH/felo_search.py +0 -202
  328. webscout/Provider/AISEARCH/genspark_search.py +0 -324
  329. webscout/Provider/AISEARCH/hika_search.py +0 -186
  330. webscout/Provider/AISEARCH/scira_search.py +0 -298
  331. webscout/Provider/Aitopia.py +0 -316
  332. webscout/Provider/AllenAI.py +0 -440
  333. webscout/Provider/Blackboxai.py +0 -791
  334. webscout/Provider/ChatGPTClone.py +0 -237
  335. webscout/Provider/ChatGPTGratis.py +0 -194
  336. webscout/Provider/Cloudflare.py +0 -324
  337. webscout/Provider/ExaChat.py +0 -358
  338. webscout/Provider/Flowith.py +0 -217
  339. webscout/Provider/FreeGemini.py +0 -250
  340. webscout/Provider/Glider.py +0 -225
  341. webscout/Provider/HF_space/__init__.py +0 -0
  342. webscout/Provider/HF_space/qwen_qwen2.py +0 -206
  343. webscout/Provider/HuggingFaceChat.py +0 -469
  344. webscout/Provider/Hunyuan.py +0 -283
  345. webscout/Provider/LambdaChat.py +0 -411
  346. webscout/Provider/Llama3.py +0 -259
  347. webscout/Provider/Nemotron.py +0 -218
  348. webscout/Provider/OLLAMA.py +0 -396
  349. webscout/Provider/OPENAI/BLACKBOXAI.py +0 -766
  350. webscout/Provider/OPENAI/Cloudflare.py +0 -378
  351. webscout/Provider/OPENAI/FreeGemini.py +0 -283
  352. webscout/Provider/OPENAI/NEMOTRON.py +0 -232
  353. webscout/Provider/OPENAI/Qwen3.py +0 -283
  354. webscout/Provider/OPENAI/api.py +0 -969
  355. webscout/Provider/OPENAI/c4ai.py +0 -373
  356. webscout/Provider/OPENAI/chatgptclone.py +0 -494
  357. webscout/Provider/OPENAI/copilot.py +0 -242
  358. webscout/Provider/OPENAI/flowith.py +0 -162
  359. webscout/Provider/OPENAI/freeaichat.py +0 -359
  360. webscout/Provider/OPENAI/mcpcore.py +0 -389
  361. webscout/Provider/OPENAI/multichat.py +0 -376
  362. webscout/Provider/OPENAI/opkfc.py +0 -496
  363. webscout/Provider/OPENAI/scirachat.py +0 -477
  364. webscout/Provider/OPENAI/standardinput.py +0 -433
  365. webscout/Provider/OPENAI/typegpt.py +0 -364
  366. webscout/Provider/OPENAI/uncovrAI.py +0 -463
  367. webscout/Provider/OPENAI/venice.py +0 -431
  368. webscout/Provider/OPENAI/yep.py +0 -382
  369. webscout/Provider/OpenGPT.py +0 -209
  370. webscout/Provider/Perplexitylabs.py +0 -415
  371. webscout/Provider/Reka.py +0 -214
  372. webscout/Provider/StandardInput.py +0 -290
  373. webscout/Provider/TTI/aiarta.py +0 -365
  374. webscout/Provider/TTI/artbit.py +0 -0
  375. webscout/Provider/TTI/fastflux.py +0 -200
  376. webscout/Provider/TTI/piclumen.py +0 -203
  377. webscout/Provider/TTI/pixelmuse.py +0 -225
  378. webscout/Provider/TTS/gesserit.py +0 -128
  379. webscout/Provider/TTS/sthir.py +0 -94
  380. webscout/Provider/TeachAnything.py +0 -229
  381. webscout/Provider/UNFINISHED/puterjs.py +0 -635
  382. webscout/Provider/UNFINISHED/test_lmarena.py +0 -119
  383. webscout/Provider/Venice.py +0 -258
  384. webscout/Provider/VercelAI.py +0 -253
  385. webscout/Provider/Writecream.py +0 -246
  386. webscout/Provider/WritingMate.py +0 -269
  387. webscout/Provider/asksteve.py +0 -220
  388. webscout/Provider/chatglm.py +0 -215
  389. webscout/Provider/copilot.py +0 -425
  390. webscout/Provider/freeaichat.py +0 -285
  391. webscout/Provider/granite.py +0 -235
  392. webscout/Provider/hermes.py +0 -266
  393. webscout/Provider/koala.py +0 -170
  394. webscout/Provider/lmarena.py +0 -198
  395. webscout/Provider/multichat.py +0 -364
  396. webscout/Provider/scira_chat.py +0 -299
  397. webscout/Provider/scnet.py +0 -243
  398. webscout/Provider/talkai.py +0 -194
  399. webscout/Provider/typegpt.py +0 -289
  400. webscout/Provider/uncovr.py +0 -368
  401. webscout/Provider/yep.py +0 -389
  402. webscout/litagent/Readme.md +0 -276
  403. webscout/litprinter/__init__.py +0 -59
  404. webscout/swiftcli/Readme.md +0 -323
  405. webscout/tempid.py +0 -128
  406. webscout/webscout_search.py +0 -1184
  407. webscout/webscout_search_async.py +0 -654
  408. webscout/yep_search.py +0 -347
  409. webscout/zeroart/README.md +0 -89
  410. webscout-8.2.9.dist-info/METADATA +0 -1033
  411. webscout-8.2.9.dist-info/RECORD +0 -289
  412. {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/licenses/LICENSE.md +0 -0
  413. {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,642 @@
1
+ """
2
+ API routes for the Webscout server.
3
+ """
4
+
5
+ import time
6
+ import uuid
7
+
8
+ from fastapi import Body, FastAPI, Query, Request
9
+ from fastapi.exceptions import RequestValidationError
10
+ from fastapi.responses import JSONResponse
11
+ from litprinter import ic
12
+ from starlette.exceptions import HTTPException as StarletteHTTPException
13
+ from starlette.status import (
14
+ HTTP_500_INTERNAL_SERVER_ERROR,
15
+ )
16
+
17
+ from webscout.search.engines import ENGINES
18
+
19
+ from .config import AppConfig
20
+ from .exceptions import APIError
21
+ from .providers import (
22
+ get_provider_instance,
23
+ get_tti_provider_instance,
24
+ resolve_provider_and_model,
25
+ resolve_tti_provider_and_model,
26
+ )
27
+ from .request_models import ChatCompletionRequest, ImageGenerationRequest, ModelListResponse
28
+ from .request_processing import (
29
+ handle_non_streaming_response,
30
+ handle_streaming_response,
31
+ prepare_provider_params,
32
+ process_messages,
33
+ )
34
+
35
+
36
+ class Api:
37
+ """API route handler class."""
38
+
39
+ def __init__(self, app: FastAPI) -> None:
40
+ self.app = app
41
+
42
+ def register_validation_exception_handler(self):
43
+ """Register comprehensive exception handlers."""
44
+ from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY, HTTP_500_INTERNAL_SERVER_ERROR
45
+
46
+ from .exceptions import APIError
47
+
48
+ github_footer = "If you believe this is a bug, please pull an issue at https://github.com/OEvortex/Webscout."
49
+
50
+ @self.app.exception_handler(APIError)
51
+ async def api_error_handler(request, exc: APIError):
52
+ ic.configureOutput(prefix='ERROR| ')
53
+ ic(f"API Error: {exc.message} (Status: {exc.status_code})")
54
+ # Patch: add footer to error content before creating JSONResponse
55
+ error_response = exc.to_response()
56
+ # If the response is a JSONResponse, patch its content dict before returning
57
+ if hasattr(error_response, 'body') and hasattr(error_response, 'media_type'):
58
+ # Try to decode the body to dict and add footer if possible
59
+ try:
60
+ import json
61
+ body_bytes = bytes(error_response.body) if hasattr(error_response, 'body') else b""
62
+ content_dict = json.loads(body_bytes.decode())
63
+ if "error" in content_dict:
64
+ content_dict["error"]["footer"] = github_footer
65
+ return JSONResponse(status_code=error_response.status_code, content=content_dict)
66
+ except Exception:
67
+ pass
68
+ return error_response
69
+
70
+ @self.app.exception_handler(RequestValidationError)
71
+ async def validation_exception_handler(request, exc: RequestValidationError):
72
+ errors = exc.errors()
73
+ error_messages = []
74
+ body = await request.body()
75
+ not body or body.strip() in (b"", b"null", b"{}")
76
+ for error in errors:
77
+ loc = error.get("loc", [])
78
+ loc_str = " -> ".join(str(item) for item in loc)
79
+ msg = error.get("msg", "Validation error")
80
+ error_messages.append({
81
+ "loc": loc,
82
+ "message": f"{msg} at {loc_str}",
83
+ "type": error.get("type", "validation_error")
84
+ })
85
+ content = {
86
+ "error": {
87
+ "message": "Request validation error.",
88
+ "details": error_messages,
89
+ "type": "validation_error",
90
+ "footer": github_footer
91
+ }
92
+ }
93
+ return JSONResponse(status_code=HTTP_422_UNPROCESSABLE_ENTITY, content=content)
94
+
95
+ @self.app.exception_handler(StarletteHTTPException)
96
+ async def http_exception_handler(request, exc: StarletteHTTPException):
97
+ content = {
98
+ "error": {
99
+ "message": exc.detail or "HTTP error occurred.",
100
+ "type": "http_error",
101
+ "footer": github_footer
102
+ }
103
+ }
104
+ return JSONResponse(status_code=exc.status_code, content=content)
105
+
106
+ @self.app.exception_handler(Exception)
107
+ async def general_exception_handler(request, exc: Exception):
108
+ ic.configureOutput(prefix='ERROR| ')
109
+ ic(f"Unhandled server error: {exc}")
110
+ content = {
111
+ "error": {
112
+ "message": f"Internal server error: {str(exc)}",
113
+ "type": "server_error",
114
+ "footer": github_footer
115
+ }
116
+ }
117
+ return JSONResponse(status_code=HTTP_500_INTERNAL_SERVER_ERROR, content=content)
118
+
119
+ def register_routes(self):
120
+ """Register all API routes."""
121
+ self._register_health_route()
122
+ self._register_model_routes()
123
+ self._register_chat_routes()
124
+ self._register_websearch_routes()
125
+
126
+ def _register_health_route(self):
127
+ """Register health check route."""
128
+ @self.app.get("/monitor/health", include_in_schema=False)
129
+ async def health_check():
130
+ """Health check endpoint for monitoring."""
131
+ return {"status": "healthy", "service": "webscout-api", "version": "0.2.0"}
132
+
133
+ def _register_model_routes(self):
134
+ """Register model listing routes."""
135
+ @self.app.get(
136
+ "/v1/models",
137
+ response_model=ModelListResponse,
138
+ tags=["Chat Completions"],
139
+ description="List all available chat completion models."
140
+ )
141
+ async def list_models():
142
+ models = []
143
+ for model_name, provider_class in AppConfig.provider_map.items():
144
+ if "/" not in model_name:
145
+ continue # Skip provider names
146
+ if any(m["id"] == model_name for m in models):
147
+ continue
148
+ models.append({
149
+ "id": model_name,
150
+ "object": "model",
151
+ "created": int(time.time()),
152
+ "owned_by": 'webscout' # Set owned_by to webscout
153
+ })
154
+ # Sort models alphabetically by the part after the first '/'
155
+ models = sorted(models, key=lambda m: m["id"].split("/", 1)[1].lower())
156
+ return {
157
+ "object": "list",
158
+ "data": models
159
+ }
160
+
161
+ @self.app.get(
162
+ "/v1/models/{model}",
163
+ response_model=dict,
164
+ tags=["Chat Completions"],
165
+ description="Retrieve model instance details."
166
+ )
167
+ async def retrieve_model(model: str):
168
+ """Retrieve model instance details."""
169
+ try:
170
+ # Check if model resolves to a valid provider/model pair
171
+ resolve_provider_and_model(model)
172
+ return {
173
+ "id": model,
174
+ "object": "model",
175
+ "created": int(time.time()),
176
+ "owned_by": "webscout"
177
+ }
178
+ except APIError:
179
+ raise
180
+ except Exception:
181
+ raise APIError(f"Model {model} not found", 404, "model_not_found", param="model")
182
+
183
+ @self.app.get(
184
+ "/v1/providers",
185
+ tags=["Chat Completions"],
186
+ description="Get details about available chat completion providers including supported models and parameters."
187
+ )
188
+ async def list_providers():
189
+ """Get information about all available chat completion providers."""
190
+ providers = {}
191
+
192
+ # Extract unique provider names (exclude model mappings)
193
+ provider_names = set()
194
+ for key, provider_class in AppConfig.provider_map.items():
195
+ if "/" not in key: # Provider name, not model mapping
196
+ provider_names.add(key)
197
+
198
+ for provider_name in sorted(provider_names):
199
+ provider_class = AppConfig.provider_map[provider_name]
200
+
201
+ # Get available models for this provider
202
+ models = []
203
+ for key, cls in AppConfig.provider_map.items():
204
+ if key.startswith(f"{provider_name}/"):
205
+ model_name = key.split("/", 1)[1]
206
+ models.append(model_name)
207
+
208
+ # Sort models
209
+ models = sorted(models)
210
+
211
+ # Get supported parameters (common OpenAI-compatible parameters)
212
+ supported_params = [
213
+ "model", "messages", "max_tokens", "temperature", "top_p",
214
+ "presence_penalty", "frequency_penalty", "stop", "stream", "user"
215
+ ]
216
+
217
+ providers[provider_name] = {
218
+ "name": provider_name,
219
+ "class": provider_class.__name__,
220
+ "models": models,
221
+ "parameters": supported_params,
222
+ "model_count": len(models)
223
+ }
224
+
225
+ return {
226
+ "providers": providers,
227
+ "total_providers": len(providers)
228
+ }
229
+
230
+ @self.app.get(
231
+ "/v1/TTI/models",
232
+ response_model=ModelListResponse,
233
+ tags=["Image Generation"],
234
+ description="List all available text-to-image (TTI) models."
235
+ )
236
+ async def list_tti_models():
237
+ models = []
238
+ for model_name, provider_class in AppConfig.tti_provider_map.items():
239
+ if "/" not in model_name:
240
+ continue # Skip provider names
241
+ if any(m["id"] == model_name for m in models):
242
+ continue
243
+ models.append({
244
+ "id": model_name,
245
+ "object": "model",
246
+ "created": int(time.time()),
247
+ "owned_by": 'webscout' # Set owned_by to webscout
248
+ })
249
+ # Sort models alphabetically by the part after the first '/'
250
+ models = sorted(models, key=lambda m: m["id"].split("/", 1)[1].lower())
251
+ return {
252
+ "object": "list",
253
+ "data": models
254
+ }
255
+
256
+ @self.app.get(
257
+ "/v1/TTI/providers",
258
+ tags=["Image Generation"],
259
+ description="Get details about available text-to-image (TTI) providers including supported models and parameters."
260
+ )
261
+ async def list_tti_providers():
262
+ """Get information about all available TTI providers."""
263
+ providers = {}
264
+
265
+ # Extract unique provider names (exclude model mappings)
266
+ provider_names = set()
267
+ for key, provider_class in AppConfig.tti_provider_map.items():
268
+ if "/" not in key: # Provider name, not model mapping
269
+ provider_names.add(key)
270
+
271
+ for provider_name in sorted(provider_names):
272
+ provider_class = AppConfig.tti_provider_map[provider_name]
273
+
274
+ # Get available models for this provider
275
+ models = []
276
+ for key, cls in AppConfig.tti_provider_map.items():
277
+ if key.startswith(f"{provider_name}/"):
278
+ model_name = key.split("/", 1)[1]
279
+ models.append(model_name)
280
+
281
+ # Sort models
282
+ models = sorted(models)
283
+
284
+ # Get supported parameters (common TTI parameters)
285
+ supported_params = [
286
+ "prompt", "model", "n", "size", "response_format", "user",
287
+ "style", "aspect_ratio", "timeout", "image_format", "seed"
288
+ ]
289
+
290
+ providers[provider_name] = {
291
+ "name": provider_name,
292
+ "class": provider_class.__name__,
293
+ "models": models,
294
+ "parameters": supported_params,
295
+ "model_count": len(models)
296
+ }
297
+
298
+ return {
299
+ "providers": providers,
300
+ "total_providers": len(providers)
301
+ }
302
+
303
+ def _register_chat_routes(self):
304
+ """Register chat completion routes."""
305
+ @self.app.post(
306
+ "/v1/chat/completions",
307
+ response_model_exclude_none=True,
308
+ response_model_exclude_unset=True,
309
+ tags=["Chat Completions"],
310
+ description="Generate chat completions using the specified model.",
311
+ openapi_extra={
312
+ "requestBody": {
313
+ "content": {
314
+ "application/json": {
315
+ "schema": {
316
+ "$ref": "#/components/schemas/ChatCompletionRequest"
317
+ },
318
+ "example": ChatCompletionRequest.Config.schema_extra["example"]
319
+ }
320
+ }
321
+ }
322
+ }
323
+ )
324
+ async def chat_completions(
325
+ request: Request,
326
+ chat_request: ChatCompletionRequest = Body(...)
327
+ ):
328
+ """Handle chat completion requests with comprehensive error handling."""
329
+ start_time = time.time()
330
+ request_id = f"chatcmpl-{uuid.uuid4()}"
331
+
332
+ try:
333
+ ic.configureOutput(prefix='INFO| ')
334
+ ic(f"Processing chat completion request {request_id} for model: {chat_request.model}")
335
+
336
+ # Resolve provider and model
337
+ provider_class, model_name = resolve_provider_and_model(chat_request.model)
338
+
339
+ # Initialize provider with caching and error handling
340
+ try:
341
+ provider = get_provider_instance(provider_class)
342
+ ic.configureOutput(prefix='DEBUG| ')
343
+ ic(f"Using provider instance: {provider_class.__name__}")
344
+ except Exception as e:
345
+ ic.configureOutput(prefix='ERROR| ')
346
+ ic(f"Failed to initialize provider {provider_class.__name__}: {e}")
347
+ raise APIError(
348
+ f"Failed to initialize provider {provider_class.__name__}: {e}",
349
+ HTTP_500_INTERNAL_SERVER_ERROR,
350
+ "provider_error"
351
+ )
352
+
353
+ # Process and validate messages
354
+ processed_messages = process_messages(chat_request.messages)
355
+
356
+ # Prepare parameters for provider
357
+ params = prepare_provider_params(chat_request, model_name, processed_messages)
358
+
359
+ # Extract client IP address
360
+ client_ip = request.client.host if request.client else "unknown"
361
+ if "x-forwarded-for" in request.headers:
362
+ client_ip = request.headers["x-forwarded-for"].split(",")[0].strip()
363
+ elif "x-real-ip" in request.headers:
364
+ client_ip = request.headers["x-real-ip"]
365
+
366
+ # Extract question from messages (last user message)
367
+ question = ""
368
+ for msg in reversed(processed_messages):
369
+ if msg.get("role") == "user":
370
+ content = msg.get("content", "")
371
+ if isinstance(content, str):
372
+ question = content
373
+ elif isinstance(content, list) and content:
374
+ # Handle content with multiple parts (text, images, etc.)
375
+ for part in content:
376
+ if isinstance(part, dict) and part.get("type") == "text":
377
+ question = part.get("text", "")
378
+ break
379
+ break
380
+
381
+ # Handle streaming vs non-streaming
382
+ if chat_request.stream:
383
+ return await handle_streaming_response(
384
+ provider, params, request_id, client_ip, question, model_name, start_time,
385
+ provider_class.__name__, request
386
+ )
387
+ else:
388
+ return await handle_non_streaming_response(
389
+ provider, params, request_id, start_time, client_ip, question, model_name,
390
+ provider_class.__name__, request
391
+ )
392
+
393
+ except APIError:
394
+ # Re-raise API errors as-is
395
+ raise
396
+ except Exception as e:
397
+ ic.configureOutput(prefix='ERROR| ')
398
+ ic(f"Unexpected error in chat completion {request_id}: {e}")
399
+ raise APIError(
400
+ f"Internal server error: {str(e)}",
401
+ HTTP_500_INTERNAL_SERVER_ERROR,
402
+ "internal_error"
403
+ )
404
+
405
+
406
+ @self.app.post(
407
+ "/v1/images/generations",
408
+ tags=["Image Generation"],
409
+ description="Generate images from text prompts using the specified TTI model."
410
+ )
411
+ async def image_generations(
412
+ image_request: ImageGenerationRequest = Body(...)
413
+ ):
414
+ """Handle image generation requests."""
415
+ start_time = time.time()
416
+ request_id = f"img-{uuid.uuid4()}"
417
+
418
+ try:
419
+ ic.configureOutput(prefix='INFO| ')
420
+ ic(f"Processing image generation request {request_id} for model: {image_request.model}")
421
+
422
+ # Resolve TTI provider and model
423
+ provider_class, model_name = resolve_tti_provider_and_model(image_request.model)
424
+
425
+ # Initialize TTI provider
426
+ try:
427
+ provider = get_tti_provider_instance(provider_class)
428
+ ic.configureOutput(prefix='DEBUG| ')
429
+ ic(f"Using TTI provider instance: {provider_class.__name__}")
430
+ except APIError as e:
431
+ # Add helpful footer for provider errors
432
+ return JSONResponse(
433
+ status_code=e.status_code,
434
+ content={
435
+ "error": {
436
+ "message": e.message,
437
+ "type": e.error_type,
438
+ "footer": "If you believe this is a bug, please pull an issue at https://github.com/OEvortex/Webscout."
439
+ }
440
+ }
441
+ )
442
+ except Exception as e:
443
+ ic.configureOutput(prefix='ERROR| ')
444
+ ic(f"Failed to initialize TTI provider {provider_class.__name__}: {e}")
445
+ raise APIError(
446
+ f"Failed to initialize TTI provider {provider_class.__name__}: {e}",
447
+ HTTP_500_INTERNAL_SERVER_ERROR,
448
+ "provider_error"
449
+ )
450
+
451
+ # Prepare parameters for TTI provider
452
+ params = {
453
+ "prompt": image_request.prompt,
454
+ "model": model_name,
455
+ "n": image_request.n,
456
+ "size": image_request.size,
457
+ "response_format": image_request.response_format,
458
+ }
459
+
460
+ # Add optional parameters
461
+ optional_params = ["user", "style", "aspect_ratio", "timeout", "image_format", "seed"]
462
+ for param in optional_params:
463
+ value = getattr(image_request, param, None)
464
+ if value is not None:
465
+ params[param] = value
466
+
467
+ # Generate images
468
+ response = provider.images.create(**params)
469
+
470
+ # Standardize response format
471
+ if hasattr(response, "model_dump"):
472
+ response_data = response.model_dump(exclude_none=True)
473
+ elif hasattr(response, "dict"):
474
+ response_data = response.dict(exclude_none=True)
475
+ elif isinstance(response, dict):
476
+ response_data = response
477
+ else:
478
+ raise APIError(
479
+ "Invalid response format from TTI provider",
480
+ HTTP_500_INTERNAL_SERVER_ERROR,
481
+ "provider_error"
482
+ )
483
+
484
+ elapsed = time.time() - start_time
485
+ ic.configureOutput(prefix='INFO| ')
486
+ ic(f"Completed image generation request {request_id} in {elapsed:.2f}s")
487
+
488
+ return response_data
489
+ except APIError:
490
+ raise
491
+ except Exception as e:
492
+ ic.configureOutput(prefix='ERROR| ')
493
+ ic(f"Unexpected error in image generation {request_id}: {e}")
494
+ raise APIError(
495
+ f"Internal server error: {str(e)}",
496
+ HTTP_500_INTERNAL_SERVER_ERROR,
497
+ "internal_error"
498
+ )
499
+
500
+
501
+
502
+ def _register_websearch_routes(self):
503
+ """Register web search endpoint."""
504
+
505
+ @self.app.get(
506
+ "/search",
507
+ tags=["Web search"],
508
+ description="Unified web search endpoint supporting all available search engines with various search types including text, news, images, videos (Brave, DuckDuckGo, Yahoo), suggestions (Brave, Bing, DuckDuckGo, Yep, Yahoo), answers, maps, translate, and weather."
509
+ )
510
+ async def websearch(
511
+ q: str = Query(..., description="Search query"),
512
+ engine: str = Query("duckduckgo", description=f"Search engine: {', '.join(sorted(set(name for cat in ENGINES.values() for name in cat)))}"),
513
+ max_results: int = Query(10, description="Maximum number of results"),
514
+ region: str = Query("all", description="Region code (optional)"),
515
+ safesearch: str = Query("moderate", description="Safe search: on, moderate, off"),
516
+ type: str = Query("text", description="Search type: text, news, images, videos, suggestions, answers, maps, translate, weather"),
517
+ place: str = Query(None, description="Place for maps search"),
518
+ street: str = Query(None, description="Street for maps search"),
519
+ city: str = Query(None, description="City for maps search"),
520
+ county: str = Query(None, description="County for maps search"),
521
+ state: str = Query(None, description="State for maps search"),
522
+ country: str = Query(None, description="Country for maps search"),
523
+ postalcode: str = Query(None, description="Postal code for maps search"),
524
+ latitude: str = Query(None, description="Latitude for maps search"),
525
+ longitude: str = Query(None, description="Longitude for maps search"),
526
+ radius: int = Query(0, description="Radius for maps search"),
527
+ from_: str = Query(None, description="Source language for translate"),
528
+ to: str = Query("en", description="Target language for translate"),
529
+ language: str = Query("en", description="Language for weather"),
530
+ ):
531
+ """Unified web search endpoint."""
532
+ github_footer = "If you believe this is a bug, please pull an issue at https://github.com/pyscout/Webscout."
533
+ try:
534
+ # Dynamically support all engines in ENGINES
535
+ found = False
536
+ for category, engines in ENGINES.items():
537
+ if engine in engines:
538
+ found = True
539
+ engine_cls = engines[engine]
540
+ searcher = engine_cls()
541
+ # Try to call the appropriate method based on 'type'
542
+ if hasattr(searcher, "run"):
543
+ method = getattr(searcher, "run")
544
+ # Some engines may require different params
545
+ try:
546
+ if type in ("text", "images", "news", "videos"):
547
+ results = method(keywords=q, region=region, safesearch=safesearch, max_results=max_results)
548
+ elif type == "suggestions":
549
+ # Suggestions method might have different signature
550
+ try:
551
+ results = method(q, region=region, max_results=max_results)
552
+ except TypeError:
553
+ # Fallback for engines that don't accept region
554
+ results = method(q, max_results=max_results)
555
+ elif type == "answers":
556
+ results = method(keywords=q)
557
+ elif type == "maps":
558
+ results = method(keywords=q, place=place, street=street, city=city, county=county, state=state, country=country, postalcode=postalcode, latitude=latitude, longitude=longitude, radius=radius, max_results=max_results)
559
+ elif type == "translate":
560
+ results = method(keywords=q, from_=from_, to=to)
561
+ elif type == "weather":
562
+ results = method(location=q, language=language)
563
+ else:
564
+ return {"error": f"{engine} does not support type '{type}'.", "footer": github_footer}
565
+ # Try to serialize results if needed
566
+ if isinstance(results, list) and results and hasattr(results[0], "__dict__"):
567
+ results = [r.__dict__ for r in results]
568
+ return {"engine": engine, "type": type, "results": results}
569
+ except Exception as ex:
570
+ return {"error": f"Error running {engine}.{type}: {ex}", "footer": github_footer}
571
+ else:
572
+ return {"error": f"{engine} does not support type '{type}'.", "footer": github_footer}
573
+ if not found:
574
+ return {"error": f"Unknown engine. Use one of: {', '.join(sorted(set(name for cat in ENGINES.values() for name in cat)))}.", "footer": github_footer}
575
+ except Exception as e:
576
+ # Special handling for rate limit errors
577
+ msg = str(e)
578
+ if "429" in msg or "rate limit" in msg.lower():
579
+ return {
580
+ "error": "You have hit the search rate limit. Please try again later.",
581
+ "details": msg,
582
+ "code": 429,
583
+ "footer": github_footer
584
+ }
585
+ return {
586
+ "error": f"Search request failed: {msg}",
587
+ "footer": github_footer
588
+ }
589
+
590
+ @self.app.get(
591
+ "/search/provider",
592
+ tags=["Web search"],
593
+ description="Get details about available search providers including supported categories and parameters."
594
+ )
595
+ async def get_search_providers():
596
+ """Get information about all available search providers."""
597
+ providers = {}
598
+
599
+ # Collect all unique engine names
600
+ all_engines = set()
601
+ for category_engines in ENGINES.values():
602
+ all_engines.update(category_engines.keys())
603
+
604
+ for engine_name in sorted(all_engines):
605
+ # Find all categories this engine supports
606
+ categories = []
607
+ for category, engines in ENGINES.items():
608
+ if engine_name in engines:
609
+ categories.append(category)
610
+
611
+ # Get supported parameters based on categories
612
+ supported_params = ["q"] # query is always supported
613
+
614
+ if "text" in categories or "images" in categories or "news" in categories or "videos" in categories:
615
+ supported_params.extend(["max_results", "region", "safesearch"])
616
+
617
+ if "suggestions" in categories:
618
+ supported_params.extend(["region"])
619
+
620
+ if "maps" in categories:
621
+ supported_params.extend(["place", "street", "city", "county", "state", "country", "postalcode", "latitude", "longitude", "radius", "max_results"])
622
+
623
+ if "translate" in categories:
624
+ supported_params.extend(["from_", "to"])
625
+
626
+ if "weather" in categories:
627
+ supported_params.extend(["language"])
628
+
629
+ # Remove duplicates
630
+ supported_params = list(set(supported_params))
631
+
632
+ providers[engine_name] = {
633
+ "name": engine_name,
634
+ "categories": sorted(categories),
635
+ "supported_types": sorted(categories), # types are the same as categories
636
+ "parameters": sorted(supported_params)
637
+ }
638
+
639
+ return {
640
+ "providers": providers,
641
+ "total_providers": len(providers)
642
+ }