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.
- webscout/AIauto.py +524 -251
- webscout/AIbase.py +247 -319
- webscout/AIutel.py +68 -703
- webscout/Bard.py +1072 -1026
- webscout/Extra/GitToolkit/__init__.py +10 -10
- webscout/Extra/GitToolkit/gitapi/__init__.py +20 -12
- webscout/Extra/GitToolkit/gitapi/gist.py +142 -0
- webscout/Extra/GitToolkit/gitapi/organization.py +91 -0
- webscout/Extra/GitToolkit/gitapi/repository.py +308 -195
- webscout/Extra/GitToolkit/gitapi/search.py +162 -0
- webscout/Extra/GitToolkit/gitapi/trending.py +236 -0
- webscout/Extra/GitToolkit/gitapi/user.py +128 -96
- webscout/Extra/GitToolkit/gitapi/utils.py +82 -62
- webscout/Extra/YTToolkit/README.md +443 -375
- webscout/Extra/YTToolkit/YTdownloader.py +953 -957
- webscout/Extra/YTToolkit/__init__.py +3 -3
- webscout/Extra/YTToolkit/transcriber.py +595 -476
- webscout/Extra/YTToolkit/ytapi/README.md +230 -44
- webscout/Extra/YTToolkit/ytapi/__init__.py +22 -6
- webscout/Extra/YTToolkit/ytapi/captions.py +190 -0
- webscout/Extra/YTToolkit/ytapi/channel.py +302 -307
- webscout/Extra/YTToolkit/ytapi/errors.py +13 -13
- webscout/Extra/YTToolkit/ytapi/extras.py +178 -118
- webscout/Extra/YTToolkit/ytapi/hashtag.py +120 -0
- webscout/Extra/YTToolkit/ytapi/https.py +89 -88
- webscout/Extra/YTToolkit/ytapi/patterns.py +61 -61
- webscout/Extra/YTToolkit/ytapi/playlist.py +59 -59
- webscout/Extra/YTToolkit/ytapi/pool.py +8 -8
- webscout/Extra/YTToolkit/ytapi/query.py +143 -40
- webscout/Extra/YTToolkit/ytapi/shorts.py +122 -0
- webscout/Extra/YTToolkit/ytapi/stream.py +68 -63
- webscout/Extra/YTToolkit/ytapi/suggestions.py +97 -0
- webscout/Extra/YTToolkit/ytapi/utils.py +66 -62
- webscout/Extra/YTToolkit/ytapi/video.py +403 -232
- webscout/Extra/__init__.py +2 -3
- webscout/Extra/gguf.py +1298 -684
- webscout/Extra/tempmail/README.md +487 -487
- webscout/Extra/tempmail/__init__.py +28 -28
- webscout/Extra/tempmail/async_utils.py +143 -141
- webscout/Extra/tempmail/base.py +172 -161
- webscout/Extra/tempmail/cli.py +191 -187
- webscout/Extra/tempmail/emailnator.py +88 -84
- webscout/Extra/tempmail/mail_tm.py +378 -361
- webscout/Extra/tempmail/temp_mail_io.py +304 -292
- webscout/Extra/weather.py +196 -194
- webscout/Extra/weather_ascii.py +17 -15
- webscout/Provider/AISEARCH/PERPLEXED_search.py +175 -0
- webscout/Provider/AISEARCH/Perplexity.py +292 -333
- webscout/Provider/AISEARCH/README.md +106 -279
- webscout/Provider/AISEARCH/__init__.py +16 -9
- webscout/Provider/AISEARCH/brave_search.py +298 -0
- webscout/Provider/AISEARCH/iask_search.py +357 -410
- webscout/Provider/AISEARCH/monica_search.py +200 -220
- webscout/Provider/AISEARCH/webpilotai_search.py +242 -255
- webscout/Provider/Algion.py +413 -0
- webscout/Provider/Andi.py +74 -69
- webscout/Provider/Apriel.py +313 -0
- webscout/Provider/Ayle.py +323 -0
- webscout/Provider/ChatSandbox.py +329 -342
- webscout/Provider/ClaudeOnline.py +365 -0
- webscout/Provider/Cohere.py +232 -208
- webscout/Provider/DeepAI.py +367 -0
- webscout/Provider/Deepinfra.py +467 -340
- webscout/Provider/EssentialAI.py +217 -0
- webscout/Provider/ExaAI.py +274 -261
- webscout/Provider/Gemini.py +175 -169
- webscout/Provider/GithubChat.py +385 -369
- webscout/Provider/Gradient.py +286 -0
- webscout/Provider/Groq.py +556 -801
- webscout/Provider/HadadXYZ.py +323 -0
- webscout/Provider/HeckAI.py +392 -375
- webscout/Provider/HuggingFace.py +387 -0
- webscout/Provider/IBM.py +340 -0
- webscout/Provider/Jadve.py +317 -291
- webscout/Provider/K2Think.py +306 -0
- webscout/Provider/Koboldai.py +221 -384
- webscout/Provider/Netwrck.py +273 -270
- webscout/Provider/Nvidia.py +310 -0
- webscout/Provider/OPENAI/DeepAI.py +489 -0
- webscout/Provider/OPENAI/K2Think.py +423 -0
- webscout/Provider/OPENAI/PI.py +463 -0
- webscout/Provider/OPENAI/README.md +890 -952
- webscout/Provider/OPENAI/TogetherAI.py +405 -0
- webscout/Provider/OPENAI/TwoAI.py +255 -357
- webscout/Provider/OPENAI/__init__.py +148 -40
- webscout/Provider/OPENAI/ai4chat.py +348 -293
- webscout/Provider/OPENAI/akashgpt.py +436 -0
- webscout/Provider/OPENAI/algion.py +303 -0
- webscout/Provider/OPENAI/{exachat.py → ayle.py} +365 -444
- webscout/Provider/OPENAI/base.py +253 -249
- webscout/Provider/OPENAI/cerebras.py +296 -0
- webscout/Provider/OPENAI/chatgpt.py +870 -556
- webscout/Provider/OPENAI/chatsandbox.py +233 -173
- webscout/Provider/OPENAI/deepinfra.py +403 -322
- webscout/Provider/OPENAI/e2b.py +2370 -1414
- webscout/Provider/OPENAI/elmo.py +278 -0
- webscout/Provider/OPENAI/exaai.py +452 -417
- webscout/Provider/OPENAI/freeassist.py +446 -0
- webscout/Provider/OPENAI/gradient.py +448 -0
- webscout/Provider/OPENAI/groq.py +380 -364
- webscout/Provider/OPENAI/hadadxyz.py +292 -0
- webscout/Provider/OPENAI/heckai.py +333 -308
- webscout/Provider/OPENAI/huggingface.py +321 -0
- webscout/Provider/OPENAI/ibm.py +425 -0
- webscout/Provider/OPENAI/llmchat.py +253 -0
- webscout/Provider/OPENAI/llmchatco.py +378 -335
- webscout/Provider/OPENAI/meta.py +541 -0
- webscout/Provider/OPENAI/netwrck.py +374 -357
- webscout/Provider/OPENAI/nvidia.py +317 -0
- webscout/Provider/OPENAI/oivscode.py +348 -287
- webscout/Provider/OPENAI/openrouter.py +328 -0
- webscout/Provider/OPENAI/pydantic_imports.py +1 -172
- webscout/Provider/OPENAI/sambanova.py +397 -0
- webscout/Provider/OPENAI/sonus.py +305 -304
- webscout/Provider/OPENAI/textpollinations.py +370 -339
- webscout/Provider/OPENAI/toolbaz.py +375 -413
- webscout/Provider/OPENAI/typefully.py +419 -355
- webscout/Provider/OPENAI/typliai.py +279 -0
- webscout/Provider/OPENAI/utils.py +314 -318
- webscout/Provider/OPENAI/wisecat.py +359 -387
- webscout/Provider/OPENAI/writecream.py +185 -163
- webscout/Provider/OPENAI/x0gpt.py +462 -365
- webscout/Provider/OPENAI/zenmux.py +380 -0
- webscout/Provider/OpenRouter.py +386 -0
- webscout/Provider/Openai.py +337 -496
- webscout/Provider/PI.py +443 -429
- webscout/Provider/QwenLM.py +346 -254
- webscout/Provider/STT/__init__.py +28 -0
- webscout/Provider/STT/base.py +303 -0
- webscout/Provider/STT/elevenlabs.py +264 -0
- webscout/Provider/Sambanova.py +317 -0
- webscout/Provider/TTI/README.md +69 -82
- webscout/Provider/TTI/__init__.py +37 -7
- webscout/Provider/TTI/base.py +147 -64
- webscout/Provider/TTI/claudeonline.py +393 -0
- webscout/Provider/TTI/magicstudio.py +292 -201
- webscout/Provider/TTI/miragic.py +180 -0
- webscout/Provider/TTI/pollinations.py +331 -221
- webscout/Provider/TTI/together.py +334 -0
- webscout/Provider/TTI/utils.py +14 -11
- webscout/Provider/TTS/README.md +186 -192
- webscout/Provider/TTS/__init__.py +43 -10
- webscout/Provider/TTS/base.py +523 -159
- webscout/Provider/TTS/deepgram.py +286 -156
- webscout/Provider/TTS/elevenlabs.py +189 -111
- webscout/Provider/TTS/freetts.py +218 -0
- webscout/Provider/TTS/murfai.py +288 -113
- webscout/Provider/TTS/openai_fm.py +364 -129
- webscout/Provider/TTS/parler.py +203 -111
- webscout/Provider/TTS/qwen.py +334 -0
- webscout/Provider/TTS/sherpa.py +286 -0
- webscout/Provider/TTS/speechma.py +693 -580
- webscout/Provider/TTS/streamElements.py +275 -333
- webscout/Provider/TTS/utils.py +280 -280
- webscout/Provider/TextPollinationsAI.py +331 -308
- webscout/Provider/TogetherAI.py +450 -0
- webscout/Provider/TwoAI.py +309 -475
- webscout/Provider/TypliAI.py +311 -305
- webscout/Provider/UNFINISHED/ChatHub.py +219 -209
- webscout/Provider/{OPENAI/glider.py → UNFINISHED/ChutesAI.py} +331 -326
- webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +300 -295
- webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +218 -198
- webscout/Provider/UNFINISHED/Qodo.py +481 -0
- webscout/Provider/{MCPCore.py → UNFINISHED/XenAI.py} +330 -315
- webscout/Provider/UNFINISHED/Youchat.py +347 -330
- webscout/Provider/UNFINISHED/aihumanizer.py +41 -0
- webscout/Provider/UNFINISHED/grammerchecker.py +37 -0
- webscout/Provider/UNFINISHED/liner.py +342 -0
- webscout/Provider/UNFINISHED/liner_api_request.py +246 -263
- webscout/Provider/{samurai.py → UNFINISHED/samurai.py} +231 -224
- webscout/Provider/WiseCat.py +256 -233
- webscout/Provider/WrDoChat.py +390 -370
- webscout/Provider/__init__.py +115 -174
- webscout/Provider/ai4chat.py +181 -174
- webscout/Provider/akashgpt.py +330 -335
- webscout/Provider/cerebras.py +397 -290
- webscout/Provider/cleeai.py +236 -213
- webscout/Provider/elmo.py +291 -283
- webscout/Provider/geminiapi.py +343 -208
- webscout/Provider/julius.py +245 -223
- webscout/Provider/learnfastai.py +333 -325
- webscout/Provider/llama3mitril.py +230 -215
- webscout/Provider/llmchat.py +308 -258
- webscout/Provider/llmchatco.py +321 -306
- webscout/Provider/meta.py +996 -801
- webscout/Provider/oivscode.py +332 -309
- webscout/Provider/searchchat.py +316 -292
- webscout/Provider/sonus.py +264 -258
- webscout/Provider/toolbaz.py +359 -353
- webscout/Provider/turboseek.py +332 -266
- webscout/Provider/typefully.py +262 -202
- webscout/Provider/x0gpt.py +332 -299
- webscout/__init__.py +31 -39
- webscout/__main__.py +5 -5
- webscout/cli.py +585 -524
- webscout/client.py +1497 -70
- webscout/conversation.py +140 -436
- webscout/exceptions.py +383 -362
- webscout/litagent/__init__.py +29 -29
- webscout/litagent/agent.py +492 -455
- webscout/litagent/constants.py +60 -60
- webscout/models.py +505 -181
- webscout/optimizers.py +74 -420
- webscout/prompt_manager.py +376 -288
- webscout/sanitize.py +1514 -0
- webscout/scout/README.md +452 -404
- webscout/scout/__init__.py +8 -8
- webscout/scout/core/__init__.py +7 -7
- webscout/scout/core/crawler.py +330 -210
- webscout/scout/core/scout.py +800 -607
- webscout/scout/core/search_result.py +51 -96
- webscout/scout/core/text_analyzer.py +64 -63
- webscout/scout/core/text_utils.py +412 -277
- webscout/scout/core/web_analyzer.py +54 -52
- webscout/scout/element.py +872 -478
- webscout/scout/parsers/__init__.py +70 -69
- webscout/scout/parsers/html5lib_parser.py +182 -172
- webscout/scout/parsers/html_parser.py +238 -236
- webscout/scout/parsers/lxml_parser.py +203 -178
- webscout/scout/utils.py +38 -37
- webscout/search/__init__.py +47 -0
- webscout/search/base.py +201 -0
- webscout/search/bing_main.py +45 -0
- webscout/search/brave_main.py +92 -0
- webscout/search/duckduckgo_main.py +57 -0
- webscout/search/engines/__init__.py +127 -0
- webscout/search/engines/bing/__init__.py +15 -0
- webscout/search/engines/bing/base.py +35 -0
- webscout/search/engines/bing/images.py +114 -0
- webscout/search/engines/bing/news.py +96 -0
- webscout/search/engines/bing/suggestions.py +36 -0
- webscout/search/engines/bing/text.py +109 -0
- webscout/search/engines/brave/__init__.py +19 -0
- webscout/search/engines/brave/base.py +47 -0
- webscout/search/engines/brave/images.py +213 -0
- webscout/search/engines/brave/news.py +353 -0
- webscout/search/engines/brave/suggestions.py +318 -0
- webscout/search/engines/brave/text.py +167 -0
- webscout/search/engines/brave/videos.py +364 -0
- webscout/search/engines/duckduckgo/__init__.py +25 -0
- webscout/search/engines/duckduckgo/answers.py +80 -0
- webscout/search/engines/duckduckgo/base.py +189 -0
- webscout/search/engines/duckduckgo/images.py +100 -0
- webscout/search/engines/duckduckgo/maps.py +183 -0
- webscout/search/engines/duckduckgo/news.py +70 -0
- webscout/search/engines/duckduckgo/suggestions.py +22 -0
- webscout/search/engines/duckduckgo/text.py +221 -0
- webscout/search/engines/duckduckgo/translate.py +48 -0
- webscout/search/engines/duckduckgo/videos.py +80 -0
- webscout/search/engines/duckduckgo/weather.py +84 -0
- webscout/search/engines/mojeek.py +61 -0
- webscout/search/engines/wikipedia.py +77 -0
- webscout/search/engines/yahoo/__init__.py +41 -0
- webscout/search/engines/yahoo/answers.py +19 -0
- webscout/search/engines/yahoo/base.py +34 -0
- webscout/search/engines/yahoo/images.py +323 -0
- webscout/search/engines/yahoo/maps.py +19 -0
- webscout/search/engines/yahoo/news.py +258 -0
- webscout/search/engines/yahoo/suggestions.py +140 -0
- webscout/search/engines/yahoo/text.py +273 -0
- webscout/search/engines/yahoo/translate.py +19 -0
- webscout/search/engines/yahoo/videos.py +302 -0
- webscout/search/engines/yahoo/weather.py +220 -0
- webscout/search/engines/yandex.py +67 -0
- webscout/search/engines/yep/__init__.py +13 -0
- webscout/search/engines/yep/base.py +34 -0
- webscout/search/engines/yep/images.py +101 -0
- webscout/search/engines/yep/suggestions.py +38 -0
- webscout/search/engines/yep/text.py +99 -0
- webscout/search/http_client.py +172 -0
- webscout/search/results.py +141 -0
- webscout/search/yahoo_main.py +57 -0
- webscout/search/yep_main.py +48 -0
- webscout/server/__init__.py +48 -0
- webscout/server/config.py +78 -0
- webscout/server/exceptions.py +69 -0
- webscout/server/providers.py +286 -0
- webscout/server/request_models.py +131 -0
- webscout/server/request_processing.py +404 -0
- webscout/server/routes.py +642 -0
- webscout/server/server.py +351 -0
- webscout/server/ui_templates.py +1171 -0
- webscout/swiftcli/__init__.py +79 -95
- webscout/swiftcli/core/__init__.py +7 -7
- webscout/swiftcli/core/cli.py +574 -297
- webscout/swiftcli/core/context.py +98 -104
- webscout/swiftcli/core/group.py +268 -241
- webscout/swiftcli/decorators/__init__.py +28 -28
- webscout/swiftcli/decorators/command.py +243 -221
- webscout/swiftcli/decorators/options.py +247 -220
- webscout/swiftcli/decorators/output.py +392 -252
- webscout/swiftcli/exceptions.py +21 -21
- webscout/swiftcli/plugins/__init__.py +9 -9
- webscout/swiftcli/plugins/base.py +134 -135
- webscout/swiftcli/plugins/manager.py +269 -269
- webscout/swiftcli/utils/__init__.py +58 -59
- webscout/swiftcli/utils/formatting.py +251 -252
- webscout/swiftcli/utils/parsing.py +368 -267
- webscout/update_checker.py +280 -136
- webscout/utils.py +28 -14
- webscout/version.py +2 -1
- webscout/version.py.bak +3 -0
- webscout/zeroart/__init__.py +218 -135
- webscout/zeroart/base.py +70 -66
- webscout/zeroart/effects.py +155 -101
- webscout/zeroart/fonts.py +1799 -1239
- webscout-2026.1.19.dist-info/METADATA +638 -0
- webscout-2026.1.19.dist-info/RECORD +312 -0
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/WHEEL +1 -1
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/entry_points.txt +1 -1
- webscout/DWEBS.py +0 -520
- webscout/Extra/Act.md +0 -309
- webscout/Extra/GitToolkit/gitapi/README.md +0 -110
- webscout/Extra/autocoder/__init__.py +0 -9
- webscout/Extra/autocoder/autocoder.py +0 -1105
- webscout/Extra/autocoder/autocoder_utiles.py +0 -332
- webscout/Extra/gguf.md +0 -430
- webscout/Extra/weather.md +0 -281
- webscout/Litlogger/README.md +0 -10
- webscout/Litlogger/__init__.py +0 -15
- webscout/Litlogger/formats.py +0 -4
- webscout/Litlogger/handlers.py +0 -103
- webscout/Litlogger/levels.py +0 -13
- webscout/Litlogger/logger.py +0 -92
- webscout/Provider/AI21.py +0 -177
- webscout/Provider/AISEARCH/DeepFind.py +0 -254
- webscout/Provider/AISEARCH/felo_search.py +0 -202
- webscout/Provider/AISEARCH/genspark_search.py +0 -324
- webscout/Provider/AISEARCH/hika_search.py +0 -186
- webscout/Provider/AISEARCH/scira_search.py +0 -298
- webscout/Provider/Aitopia.py +0 -316
- webscout/Provider/AllenAI.py +0 -440
- webscout/Provider/Blackboxai.py +0 -791
- webscout/Provider/ChatGPTClone.py +0 -237
- webscout/Provider/ChatGPTGratis.py +0 -194
- webscout/Provider/Cloudflare.py +0 -324
- webscout/Provider/ExaChat.py +0 -358
- webscout/Provider/Flowith.py +0 -217
- webscout/Provider/FreeGemini.py +0 -250
- webscout/Provider/Glider.py +0 -225
- webscout/Provider/HF_space/__init__.py +0 -0
- webscout/Provider/HF_space/qwen_qwen2.py +0 -206
- webscout/Provider/HuggingFaceChat.py +0 -469
- webscout/Provider/Hunyuan.py +0 -283
- webscout/Provider/LambdaChat.py +0 -411
- webscout/Provider/Llama3.py +0 -259
- webscout/Provider/Nemotron.py +0 -218
- webscout/Provider/OLLAMA.py +0 -396
- webscout/Provider/OPENAI/BLACKBOXAI.py +0 -766
- webscout/Provider/OPENAI/Cloudflare.py +0 -378
- webscout/Provider/OPENAI/FreeGemini.py +0 -283
- webscout/Provider/OPENAI/NEMOTRON.py +0 -232
- webscout/Provider/OPENAI/Qwen3.py +0 -283
- webscout/Provider/OPENAI/api.py +0 -969
- webscout/Provider/OPENAI/c4ai.py +0 -373
- webscout/Provider/OPENAI/chatgptclone.py +0 -494
- webscout/Provider/OPENAI/copilot.py +0 -242
- webscout/Provider/OPENAI/flowith.py +0 -162
- webscout/Provider/OPENAI/freeaichat.py +0 -359
- webscout/Provider/OPENAI/mcpcore.py +0 -389
- webscout/Provider/OPENAI/multichat.py +0 -376
- webscout/Provider/OPENAI/opkfc.py +0 -496
- webscout/Provider/OPENAI/scirachat.py +0 -477
- webscout/Provider/OPENAI/standardinput.py +0 -433
- webscout/Provider/OPENAI/typegpt.py +0 -364
- webscout/Provider/OPENAI/uncovrAI.py +0 -463
- webscout/Provider/OPENAI/venice.py +0 -431
- webscout/Provider/OPENAI/yep.py +0 -382
- webscout/Provider/OpenGPT.py +0 -209
- webscout/Provider/Perplexitylabs.py +0 -415
- webscout/Provider/Reka.py +0 -214
- webscout/Provider/StandardInput.py +0 -290
- webscout/Provider/TTI/aiarta.py +0 -365
- webscout/Provider/TTI/artbit.py +0 -0
- webscout/Provider/TTI/fastflux.py +0 -200
- webscout/Provider/TTI/piclumen.py +0 -203
- webscout/Provider/TTI/pixelmuse.py +0 -225
- webscout/Provider/TTS/gesserit.py +0 -128
- webscout/Provider/TTS/sthir.py +0 -94
- webscout/Provider/TeachAnything.py +0 -229
- webscout/Provider/UNFINISHED/puterjs.py +0 -635
- webscout/Provider/UNFINISHED/test_lmarena.py +0 -119
- webscout/Provider/Venice.py +0 -258
- webscout/Provider/VercelAI.py +0 -253
- webscout/Provider/Writecream.py +0 -246
- webscout/Provider/WritingMate.py +0 -269
- webscout/Provider/asksteve.py +0 -220
- webscout/Provider/chatglm.py +0 -215
- webscout/Provider/copilot.py +0 -425
- webscout/Provider/freeaichat.py +0 -285
- webscout/Provider/granite.py +0 -235
- webscout/Provider/hermes.py +0 -266
- webscout/Provider/koala.py +0 -170
- webscout/Provider/lmarena.py +0 -198
- webscout/Provider/multichat.py +0 -364
- webscout/Provider/scira_chat.py +0 -299
- webscout/Provider/scnet.py +0 -243
- webscout/Provider/talkai.py +0 -194
- webscout/Provider/typegpt.py +0 -289
- webscout/Provider/uncovr.py +0 -368
- webscout/Provider/yep.py +0 -389
- webscout/litagent/Readme.md +0 -276
- webscout/litprinter/__init__.py +0 -59
- webscout/swiftcli/Readme.md +0 -323
- webscout/tempid.py +0 -128
- webscout/webscout_search.py +0 -1184
- webscout/webscout_search_async.py +0 -654
- webscout/yep_search.py +0 -347
- webscout/zeroart/README.md +0 -89
- webscout-8.2.9.dist-info/METADATA +0 -1033
- webscout-8.2.9.dist-info/RECORD +0 -289
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/top_level.txt +0 -0
|
@@ -1,635 +0,0 @@
|
|
|
1
|
-
import random
|
|
2
|
-
import re
|
|
3
|
-
import time
|
|
4
|
-
from typing import Dict, List, Optional, Any, Generator, Tuple, Union
|
|
5
|
-
import uuid
|
|
6
|
-
from curl_cffi.requests import Session
|
|
7
|
-
from curl_cffi import CurlError
|
|
8
|
-
from webscout.AIutel import Conversation as JsonConversation
|
|
9
|
-
from webscout import exceptions
|
|
10
|
-
from webscout.Provider.Blackboxai import to_data_uri
|
|
11
|
-
|
|
12
|
-
class RateLimitError(Exception):
|
|
13
|
-
pass
|
|
14
|
-
|
|
15
|
-
class ModelNotFoundError(Exception):
|
|
16
|
-
pass
|
|
17
|
-
|
|
18
|
-
Messages = List[Dict[str, Any]]
|
|
19
|
-
MediaListType = List[Tuple[Any, str]]
|
|
20
|
-
Result = Generator[Union[str, JsonConversation, Any], None, None]
|
|
21
|
-
|
|
22
|
-
class AuthData:
|
|
23
|
-
def __init__(self):
|
|
24
|
-
self.auth_token: Optional[str] = None
|
|
25
|
-
self.app_token: Optional[str] = None
|
|
26
|
-
self.created_at: float = time.time()
|
|
27
|
-
self.tokens_valid: bool = False
|
|
28
|
-
self.rate_limited_until: float = 0
|
|
29
|
-
def is_valid(self, expiration_time: int) -> bool:
|
|
30
|
-
return (self.auth_token and self.app_token and self.tokens_valid and time.time() - self.created_at < expiration_time)
|
|
31
|
-
def invalidate(self):
|
|
32
|
-
self.tokens_valid = False
|
|
33
|
-
def set_rate_limit(self, seconds: int = 60):
|
|
34
|
-
self.rate_limited_until = time.time() + seconds
|
|
35
|
-
def is_rate_limited(self) -> bool:
|
|
36
|
-
return time.time() < self.rate_limited_until
|
|
37
|
-
|
|
38
|
-
class Conversation(JsonConversation):
|
|
39
|
-
message_history: Messages = []
|
|
40
|
-
def __init__(self, model: str):
|
|
41
|
-
self.model = model
|
|
42
|
-
self.message_history = []
|
|
43
|
-
self._auth_data: Dict[str, AuthData] = {}
|
|
44
|
-
def get_auth_for_model(self, model: str, provider) -> AuthData:
|
|
45
|
-
if model not in self._auth_data:
|
|
46
|
-
self._auth_data[model] = AuthData()
|
|
47
|
-
return self._auth_data[model]
|
|
48
|
-
def get_dict(self) -> Dict[str, Any]:
|
|
49
|
-
return {"model": self.model, "message_history": self.message_history}
|
|
50
|
-
def __getstate__(self) -> Dict[str, Any]:
|
|
51
|
-
state = self.__dict__.copy()
|
|
52
|
-
return state
|
|
53
|
-
def __setstate__(self, state: Dict[str, Any]) -> None:
|
|
54
|
-
self.__dict__.update(state)
|
|
55
|
-
|
|
56
|
-
class PuterJS():
|
|
57
|
-
label = "Puter.js"
|
|
58
|
-
url = "https://docs.puter.com/playground"
|
|
59
|
-
api_endpoint = "https://api.puter.com/drivers/call"
|
|
60
|
-
working = True
|
|
61
|
-
needs_auth = False
|
|
62
|
-
supports_stream = False
|
|
63
|
-
supports_system_message = True
|
|
64
|
-
supports_message_history = True
|
|
65
|
-
default_model = 'gpt-4o'
|
|
66
|
-
default_vision_model = default_model
|
|
67
|
-
openai_models = [default_vision_model]
|
|
68
|
-
claude_models = []
|
|
69
|
-
mistral_models = []
|
|
70
|
-
xai_models = []
|
|
71
|
-
deepseek_models = []
|
|
72
|
-
gemini_models = []
|
|
73
|
-
openrouter_models = []
|
|
74
|
-
vision_models = [*openai_models]
|
|
75
|
-
models = vision_models
|
|
76
|
-
TOKEN_EXPIRATION = 30 * 60
|
|
77
|
-
MAX_RETRIES = 3
|
|
78
|
-
RETRY_DELAY = 5
|
|
79
|
-
RATE_LIMIT_DELAY = 60
|
|
80
|
-
Conversation = Conversation
|
|
81
|
-
AuthData = AuthData
|
|
82
|
-
_shared_auth_data = {}
|
|
83
|
-
|
|
84
|
-
@staticmethod
|
|
85
|
-
def get_driver_for_model(model: str) -> str:
|
|
86
|
-
if model in PuterJS.openai_models:
|
|
87
|
-
return "openai-completion"
|
|
88
|
-
elif model in PuterJS.claude_models:
|
|
89
|
-
return "claude"
|
|
90
|
-
elif model in PuterJS.mistral_models:
|
|
91
|
-
return "mistral"
|
|
92
|
-
elif model in PuterJS.xai_models:
|
|
93
|
-
return "xai"
|
|
94
|
-
elif model in PuterJS.deepseek_models:
|
|
95
|
-
return "deepseek"
|
|
96
|
-
elif model in PuterJS.gemini_models:
|
|
97
|
-
return "gemini"
|
|
98
|
-
elif model in PuterJS.openrouter_models:
|
|
99
|
-
return "openrouter"
|
|
100
|
-
else:
|
|
101
|
-
return "openai-completion"
|
|
102
|
-
|
|
103
|
-
@staticmethod
|
|
104
|
-
def format_messages_with_images(messages: Messages, media: MediaListType = None) -> Messages:
|
|
105
|
-
if not media:
|
|
106
|
-
return messages
|
|
107
|
-
|
|
108
|
-
formatted_messages = messages.copy()
|
|
109
|
-
|
|
110
|
-
for i in range(len(formatted_messages) - 1, -1, -1):
|
|
111
|
-
if formatted_messages[i]["role"] == "user":
|
|
112
|
-
user_msg = formatted_messages[i]
|
|
113
|
-
|
|
114
|
-
if isinstance(user_msg["content"], str):
|
|
115
|
-
text_content = user_msg["content"]
|
|
116
|
-
user_msg["content"] = [{"type": "text", "text": text_content}]
|
|
117
|
-
elif not isinstance(user_msg["content"], list):
|
|
118
|
-
user_msg["content"] = []
|
|
119
|
-
|
|
120
|
-
for image_data, image_name in media:
|
|
121
|
-
if isinstance(image_data, str) and (image_data.startswith("http://") or image_data.startswith("https://")):
|
|
122
|
-
user_msg["content"].append({
|
|
123
|
-
"type": "image_url",
|
|
124
|
-
"image_url": {"url": image_data}
|
|
125
|
-
})
|
|
126
|
-
else:
|
|
127
|
-
image_uri = to_data_uri(image_data)
|
|
128
|
-
user_msg["content"].append({
|
|
129
|
-
"type": "image_url",
|
|
130
|
-
"image_url": {"url": image_uri}
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
formatted_messages[i] = user_msg
|
|
134
|
-
break
|
|
135
|
-
|
|
136
|
-
return formatted_messages
|
|
137
|
-
|
|
138
|
-
@classmethod
|
|
139
|
-
def _create_temporary_account(cls, session: Session, proxy: str = None) -> Dict[str, str]:
|
|
140
|
-
signup_headers = {
|
|
141
|
-
"Content-Type": "application/json",
|
|
142
|
-
"host": "puter.com",
|
|
143
|
-
"connection": "keep-alive",
|
|
144
|
-
"sec-ch-ua-platform": "macOS",
|
|
145
|
-
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
|
|
146
|
-
"sec-ch-ua": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
|
|
147
|
-
"sec-ch-ua-mobile": "?0",
|
|
148
|
-
"accept": "*/*",
|
|
149
|
-
"origin": "https://puter.com",
|
|
150
|
-
"sec-fetch-site": "same-site",
|
|
151
|
-
"sec-fetch-mode": "cors",
|
|
152
|
-
"sec-fetch-dest": "empty",
|
|
153
|
-
"referer": "https://puter.com/",
|
|
154
|
-
"accept-encoding": "gzip",
|
|
155
|
-
"accept-language": "en-US,en;q=0.9"
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
signup_data = {
|
|
159
|
-
"is_temp": True,
|
|
160
|
-
"client_id": str(uuid.uuid4())
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
for attempt in range(cls.MAX_RETRIES):
|
|
164
|
-
try:
|
|
165
|
-
response = session.post(
|
|
166
|
-
"https://puter.com/signup",
|
|
167
|
-
headers=signup_headers,
|
|
168
|
-
json=signup_data,
|
|
169
|
-
proxies={"https": proxy} if proxy else None,
|
|
170
|
-
timeout=30
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
if response.status_code == 429:
|
|
174
|
-
retry_after = int(response.headers.get('Retry-After', cls.RATE_LIMIT_DELAY))
|
|
175
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
176
|
-
time.sleep(retry_after)
|
|
177
|
-
continue
|
|
178
|
-
else:
|
|
179
|
-
raise RateLimitError(f"Rate limited by Puter.js API. Try again after {retry_after} seconds.")
|
|
180
|
-
|
|
181
|
-
if response.status_code != 200:
|
|
182
|
-
error_text = response.text
|
|
183
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
184
|
-
time.sleep(cls.RETRY_DELAY * (attempt + 1))
|
|
185
|
-
continue
|
|
186
|
-
else:
|
|
187
|
-
raise Exception(f"Failed to create temporary account. Status: {response.status_code}, Details: {error_text}")
|
|
188
|
-
|
|
189
|
-
try:
|
|
190
|
-
return response.json()
|
|
191
|
-
except Exception as e:
|
|
192
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
193
|
-
time.sleep(cls.RETRY_DELAY)
|
|
194
|
-
continue
|
|
195
|
-
else:
|
|
196
|
-
raise Exception(f"Failed to parse signup response as JSON: {e}")
|
|
197
|
-
|
|
198
|
-
except Exception as e:
|
|
199
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
200
|
-
time.sleep(cls.RETRY_DELAY * (2 ** attempt))
|
|
201
|
-
continue
|
|
202
|
-
else:
|
|
203
|
-
raise e
|
|
204
|
-
|
|
205
|
-
raise Exception("Failed to create temporary account after multiple retries")
|
|
206
|
-
|
|
207
|
-
@classmethod
|
|
208
|
-
def _get_app_token(cls, session: Session, auth_token: str, proxy: str = None) -> Dict[str, str]:
|
|
209
|
-
app_token_headers = {
|
|
210
|
-
"host": "api.puter.com",
|
|
211
|
-
"connection": "keep-alive",
|
|
212
|
-
"authorization": f"Bearer {auth_token}",
|
|
213
|
-
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
|
|
214
|
-
"accept": "*/*",
|
|
215
|
-
"origin": "https://puter.com",
|
|
216
|
-
"sec-fetch-site": "same-site",
|
|
217
|
-
"sec-fetch-mode": "cors",
|
|
218
|
-
"sec-fetch-dest": "empty",
|
|
219
|
-
"referer": "https://puter.com/",
|
|
220
|
-
"accept-encoding": "gzip",
|
|
221
|
-
"accept-language": "en-US,en;q=0.9",
|
|
222
|
-
"content-type": "application/json"
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
origins = ["http://docs.puter.com", "https://docs.puter.com", "https://puter.com"]
|
|
226
|
-
app_token_data = {"origin": random.choice(origins)}
|
|
227
|
-
|
|
228
|
-
for attempt in range(cls.MAX_RETRIES):
|
|
229
|
-
try:
|
|
230
|
-
response = session.post(
|
|
231
|
-
"https://api.puter.com/auth/get-user-app-token",
|
|
232
|
-
headers=app_token_headers,
|
|
233
|
-
json=app_token_data,
|
|
234
|
-
proxies={"https": proxy} if proxy else None,
|
|
235
|
-
timeout=30
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
if response.status_code == 429:
|
|
239
|
-
retry_after = int(response.headers.get('Retry-After', cls.RATE_LIMIT_DELAY))
|
|
240
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
241
|
-
time.sleep(retry_after)
|
|
242
|
-
continue
|
|
243
|
-
else:
|
|
244
|
-
raise RateLimitError(f"Rate limited by Puter.js API. Try again after {retry_after} seconds.")
|
|
245
|
-
|
|
246
|
-
if response.status_code != 200:
|
|
247
|
-
error_text = response.text
|
|
248
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
249
|
-
time.sleep(cls.RETRY_DELAY * (attempt + 1))
|
|
250
|
-
continue
|
|
251
|
-
else:
|
|
252
|
-
raise Exception(f"Failed to get app token. Status: {response.status_code}, Details: {error_text}")
|
|
253
|
-
|
|
254
|
-
try:
|
|
255
|
-
return response.json()
|
|
256
|
-
except Exception as e:
|
|
257
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
258
|
-
time.sleep(cls.RETRY_DELAY)
|
|
259
|
-
continue
|
|
260
|
-
else:
|
|
261
|
-
raise Exception(f"Failed to parse app token response as JSON: {e}")
|
|
262
|
-
|
|
263
|
-
except Exception as e:
|
|
264
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
265
|
-
time.sleep(cls.RETRY_DELAY * (2 ** attempt))
|
|
266
|
-
continue
|
|
267
|
-
else:
|
|
268
|
-
raise e
|
|
269
|
-
|
|
270
|
-
raise Exception("Failed to get app token after multiple retries")
|
|
271
|
-
|
|
272
|
-
@classmethod
|
|
273
|
-
def get_model(cls, model: str) -> str:
|
|
274
|
-
"""Get the internal model name from the user-provided model name."""
|
|
275
|
-
|
|
276
|
-
if not model:
|
|
277
|
-
return cls.default_model
|
|
278
|
-
|
|
279
|
-
# Check if the model exists directly in our models list
|
|
280
|
-
if model in cls.models:
|
|
281
|
-
return model
|
|
282
|
-
|
|
283
|
-
# Check if there's an alias for this model
|
|
284
|
-
if model in cls.model_aliases:
|
|
285
|
-
alias = cls.model_aliases[model]
|
|
286
|
-
# If the alias is a list, randomly select one of the options
|
|
287
|
-
if isinstance(alias, list):
|
|
288
|
-
selected_model = random.choice(alias)
|
|
289
|
-
return selected_model
|
|
290
|
-
return alias
|
|
291
|
-
|
|
292
|
-
raise ModelNotFoundError(f"Model {model} not found")
|
|
293
|
-
|
|
294
|
-
@classmethod
|
|
295
|
-
def create_completion(
|
|
296
|
-
cls,
|
|
297
|
-
model: str,
|
|
298
|
-
messages: Messages,
|
|
299
|
-
proxy: str = None,
|
|
300
|
-
stream: bool = None, # Ignored in synchronous version
|
|
301
|
-
conversation: Optional[JsonConversation] = None,
|
|
302
|
-
return_conversation: bool = False,
|
|
303
|
-
media: MediaListType = None, # Add media parameter for images
|
|
304
|
-
**kwargs
|
|
305
|
-
) -> Result:
|
|
306
|
-
model = cls.get_model(model)
|
|
307
|
-
|
|
308
|
-
# Check if we need to use a vision model
|
|
309
|
-
has_images = False
|
|
310
|
-
if media is not None and len(media) > 0:
|
|
311
|
-
has_images = True
|
|
312
|
-
# If images are present and model doesn't support vision, switch to default vision model
|
|
313
|
-
if model not in cls.vision_models:
|
|
314
|
-
model = cls.default_vision_model
|
|
315
|
-
|
|
316
|
-
# Check for image URLs in messages
|
|
317
|
-
if not has_images:
|
|
318
|
-
for msg in messages:
|
|
319
|
-
if msg["role"] == "user":
|
|
320
|
-
content = msg.get("content", "")
|
|
321
|
-
if isinstance(content, list):
|
|
322
|
-
for item in content:
|
|
323
|
-
if item.get("type") == "image_url":
|
|
324
|
-
has_images = True
|
|
325
|
-
if model not in cls.vision_models:
|
|
326
|
-
model = cls.default_vision_model
|
|
327
|
-
break
|
|
328
|
-
elif isinstance(content, str):
|
|
329
|
-
# Check for URLs in the text
|
|
330
|
-
urls = re.findall(r'https?://\S+\.(jpg|jpeg|png|gif|webp)', content, re.IGNORECASE)
|
|
331
|
-
if urls:
|
|
332
|
-
has_images = True
|
|
333
|
-
if model not in cls.vision_models:
|
|
334
|
-
model = cls.default_vision_model
|
|
335
|
-
break
|
|
336
|
-
|
|
337
|
-
# Check if the conversation is of the correct type
|
|
338
|
-
if conversation is not None and not isinstance(conversation, cls.Conversation):
|
|
339
|
-
# Convert generic JsonConversation to our specific Conversation class
|
|
340
|
-
new_conversation = cls.Conversation(model)
|
|
341
|
-
new_conversation.message_history = conversation.message_history.copy() if hasattr(conversation, 'message_history') else messages.copy()
|
|
342
|
-
conversation = new_conversation
|
|
343
|
-
|
|
344
|
-
# Initialize or update conversation
|
|
345
|
-
if conversation is None:
|
|
346
|
-
conversation = cls.Conversation(model)
|
|
347
|
-
# Format messages with images if needed
|
|
348
|
-
if has_images and media:
|
|
349
|
-
conversation.message_history = cls.format_messages_with_images(messages, media)
|
|
350
|
-
else:
|
|
351
|
-
conversation.message_history = messages.copy()
|
|
352
|
-
else:
|
|
353
|
-
# Update message history with new messages
|
|
354
|
-
if has_images and media:
|
|
355
|
-
formatted_messages = cls.format_messages_with_images(messages, media)
|
|
356
|
-
for msg in formatted_messages:
|
|
357
|
-
if msg not in conversation.message_history:
|
|
358
|
-
conversation.message_history.append(msg)
|
|
359
|
-
else:
|
|
360
|
-
for msg in messages:
|
|
361
|
-
if msg not in conversation.message_history:
|
|
362
|
-
conversation.message_history.append(msg)
|
|
363
|
-
|
|
364
|
-
# Get the authentication data for this specific model
|
|
365
|
-
auth_data = conversation.get_auth_for_model(model, cls)
|
|
366
|
-
|
|
367
|
-
# Check if we can use shared auth data
|
|
368
|
-
if model in cls._shared_auth_data and cls._shared_auth_data[model].is_valid(cls.TOKEN_EXPIRATION):
|
|
369
|
-
# Copy shared auth data to conversation
|
|
370
|
-
shared_auth = cls._shared_auth_data[model]
|
|
371
|
-
auth_data.auth_token = shared_auth.auth_token
|
|
372
|
-
auth_data.app_token = shared_auth.app_token
|
|
373
|
-
auth_data.created_at = shared_auth.created_at
|
|
374
|
-
auth_data.tokens_valid = shared_auth.tokens_valid
|
|
375
|
-
|
|
376
|
-
# Check if rate limited
|
|
377
|
-
if auth_data.is_rate_limited():
|
|
378
|
-
wait_time = auth_data.rate_limited_until - time.time()
|
|
379
|
-
if wait_time > 0:
|
|
380
|
-
yield f"Rate limited. Please try again in {int(wait_time)} seconds."
|
|
381
|
-
return
|
|
382
|
-
|
|
383
|
-
with Session() as session:
|
|
384
|
-
# Step 1: Create a temporary account (if needed)
|
|
385
|
-
if not auth_data.is_valid(cls.TOKEN_EXPIRATION):
|
|
386
|
-
try:
|
|
387
|
-
# Try to authenticate
|
|
388
|
-
signup_data = cls._create_temporary_account(session, proxy)
|
|
389
|
-
auth_data.auth_token = signup_data.get("token")
|
|
390
|
-
|
|
391
|
-
if not auth_data.auth_token:
|
|
392
|
-
yield f"Error: No auth token in response for model {model}"
|
|
393
|
-
return
|
|
394
|
-
|
|
395
|
-
# Get app token
|
|
396
|
-
app_token_data = cls._get_app_token(session, auth_data.auth_token, proxy)
|
|
397
|
-
auth_data.app_token = app_token_data.get("token")
|
|
398
|
-
|
|
399
|
-
if not auth_data.app_token:
|
|
400
|
-
yield f"Error: No app token in response for model {model}"
|
|
401
|
-
return
|
|
402
|
-
|
|
403
|
-
# Mark tokens as valid
|
|
404
|
-
auth_data.created_at = time.time()
|
|
405
|
-
auth_data.tokens_valid = True
|
|
406
|
-
|
|
407
|
-
# Update shared auth data
|
|
408
|
-
cls._shared_auth_data[model] = auth_data
|
|
409
|
-
|
|
410
|
-
except RateLimitError as e:
|
|
411
|
-
# Set rate limit and inform user
|
|
412
|
-
auth_data.set_rate_limit(cls.RATE_LIMIT_DELAY)
|
|
413
|
-
yield str(e)
|
|
414
|
-
return
|
|
415
|
-
except Exception as e:
|
|
416
|
-
yield f"Error during authentication for model {model}: {str(e)}"
|
|
417
|
-
return
|
|
418
|
-
|
|
419
|
-
# Step 3: Make the chat request with proper image handling
|
|
420
|
-
try:
|
|
421
|
-
chat_headers = {
|
|
422
|
-
"host": "api.puter.com",
|
|
423
|
-
"connection": "keep-alive",
|
|
424
|
-
"authorization": f"Bearer {auth_data.app_token}",
|
|
425
|
-
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
|
|
426
|
-
"content-type": "application/json;charset=UTF-8",
|
|
427
|
-
"accept": "*/*",
|
|
428
|
-
"origin": "http://docs.puter.com",
|
|
429
|
-
"sec-fetch-site": "cross-site",
|
|
430
|
-
"sec-fetch-mode": "cors",
|
|
431
|
-
"sec-fetch-dest": "empty",
|
|
432
|
-
"referer": "http://docs.puter.com/",
|
|
433
|
-
"accept-encoding": "gzip",
|
|
434
|
-
"accept-language": "en-US,en;q=0.9"
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
driver = cls.get_driver_for_model(model)
|
|
438
|
-
|
|
439
|
-
processed_messages = conversation.message_history
|
|
440
|
-
|
|
441
|
-
if has_images and not any(isinstance(msg.get("content"), list) for msg in processed_messages):
|
|
442
|
-
for i, msg in enumerate(processed_messages):
|
|
443
|
-
if msg["role"] == "user":
|
|
444
|
-
if media and len(media) > 0:
|
|
445
|
-
processed_messages = cls.format_messages_with_images([msg], media)
|
|
446
|
-
else:
|
|
447
|
-
content = msg.get("content", "")
|
|
448
|
-
if isinstance(content, str):
|
|
449
|
-
urls = re.findall(r'https?://\S+\.(jpg|jpeg|png|gif|webp)', content, re.IGNORECASE)
|
|
450
|
-
if urls:
|
|
451
|
-
text_parts = []
|
|
452
|
-
image_urls = []
|
|
453
|
-
|
|
454
|
-
words = content.split()
|
|
455
|
-
for word in words:
|
|
456
|
-
if re.match(r'https?://\S+\.(jpg|jpeg|png|gif|webp)', word, re.IGNORECASE):
|
|
457
|
-
image_urls.append(word)
|
|
458
|
-
else:
|
|
459
|
-
text_parts.append(word)
|
|
460
|
-
|
|
461
|
-
formatted_content = []
|
|
462
|
-
if text_parts:
|
|
463
|
-
formatted_content.append({
|
|
464
|
-
"type": "text",
|
|
465
|
-
"text": " ".join(text_parts)
|
|
466
|
-
})
|
|
467
|
-
|
|
468
|
-
for url in image_urls:
|
|
469
|
-
formatted_content.append({
|
|
470
|
-
"type": "image_url",
|
|
471
|
-
"image_url": {"url": url}
|
|
472
|
-
})
|
|
473
|
-
|
|
474
|
-
processed_messages[i]["content"] = formatted_content
|
|
475
|
-
break
|
|
476
|
-
|
|
477
|
-
chat_data = {
|
|
478
|
-
"interface": "puter-chat-completion",
|
|
479
|
-
"driver": driver,
|
|
480
|
-
"test_mode": False,
|
|
481
|
-
"method": "complete",
|
|
482
|
-
"args": {
|
|
483
|
-
"messages": processed_messages,
|
|
484
|
-
"model": model,
|
|
485
|
-
"stream": False,
|
|
486
|
-
"max_tokens": kwargs.get("max_tokens", 4096)
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
for key, value in kwargs.items():
|
|
491
|
-
if key not in ["messages", "model", "stream", "max_tokens"]:
|
|
492
|
-
chat_data["args"][key] = value
|
|
493
|
-
|
|
494
|
-
for attempt in range(cls.MAX_RETRIES):
|
|
495
|
-
try:
|
|
496
|
-
response = session.post(
|
|
497
|
-
cls.api_endpoint,
|
|
498
|
-
headers=chat_headers,
|
|
499
|
-
json=chat_data,
|
|
500
|
-
proxies={"https": proxy} if proxy else None,
|
|
501
|
-
timeout=120
|
|
502
|
-
)
|
|
503
|
-
|
|
504
|
-
if response.status_code == 429:
|
|
505
|
-
retry_after = int(response.headers.get('Retry-After', cls.RATE_LIMIT_DELAY))
|
|
506
|
-
auth_data.set_rate_limit(retry_after)
|
|
507
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
508
|
-
time.sleep(min(retry_after, 10))
|
|
509
|
-
continue
|
|
510
|
-
else:
|
|
511
|
-
raise RateLimitError(f"Rate limited by Puter.js API. Try again after {retry_after} seconds.")
|
|
512
|
-
|
|
513
|
-
if response.status_code in [401, 403]:
|
|
514
|
-
error_text = response.text
|
|
515
|
-
auth_data.invalidate()
|
|
516
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
517
|
-
signup_data = cls._create_temporary_account(session, proxy)
|
|
518
|
-
auth_data.auth_token = signup_data.get("token")
|
|
519
|
-
|
|
520
|
-
app_token_data = cls._get_app_token(session, auth_data.auth_token, proxy)
|
|
521
|
-
auth_data.app_token = app_token_data.get("token")
|
|
522
|
-
|
|
523
|
-
auth_data.created_at = time.time()
|
|
524
|
-
auth_data.tokens_valid = True
|
|
525
|
-
|
|
526
|
-
cls._shared_auth_data[model] = auth_data
|
|
527
|
-
|
|
528
|
-
chat_headers["authorization"] = f"Bearer {auth_data.app_token}"
|
|
529
|
-
|
|
530
|
-
continue
|
|
531
|
-
else:
|
|
532
|
-
raise Exception(f"Authentication failed after {cls.MAX_RETRIES} attempts: {error_text}")
|
|
533
|
-
|
|
534
|
-
if response.status_code != 200:
|
|
535
|
-
error_text = response.text
|
|
536
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
537
|
-
time.sleep(cls.RETRY_DELAY * (attempt + 1))
|
|
538
|
-
continue
|
|
539
|
-
else:
|
|
540
|
-
raise Exception(f"Chat request failed. Status: {response.status_code}, Details: {error_text}")
|
|
541
|
-
|
|
542
|
-
try:
|
|
543
|
-
response_json = response.json()
|
|
544
|
-
except Exception as e:
|
|
545
|
-
error_text = response.text
|
|
546
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
547
|
-
time.sleep(cls.RETRY_DELAY)
|
|
548
|
-
continue
|
|
549
|
-
else:
|
|
550
|
-
raise Exception(f"Failed to parse chat response as JSON: {error_text}")
|
|
551
|
-
|
|
552
|
-
if response_json.get("success") is True:
|
|
553
|
-
content = response_json.get("result", {}).get("message", {}).get("content", "")
|
|
554
|
-
|
|
555
|
-
if content:
|
|
556
|
-
conversation.message_history.append({
|
|
557
|
-
"role": "assistant",
|
|
558
|
-
"content": content
|
|
559
|
-
})
|
|
560
|
-
|
|
561
|
-
yield content
|
|
562
|
-
|
|
563
|
-
if return_conversation:
|
|
564
|
-
yield conversation
|
|
565
|
-
|
|
566
|
-
return
|
|
567
|
-
else:
|
|
568
|
-
error_msg = response_json.get("error", {}).get("message", "Unknown error")
|
|
569
|
-
|
|
570
|
-
if "rate" in error_msg.lower() or "limit" in error_msg.lower():
|
|
571
|
-
auth_data.set_rate_limit()
|
|
572
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
573
|
-
time.sleep(cls.RETRY_DELAY)
|
|
574
|
-
continue
|
|
575
|
-
else:
|
|
576
|
-
raise RateLimitError(f"Rate limited: {error_msg}")
|
|
577
|
-
|
|
578
|
-
if "auth" in error_msg.lower() or "token" in error_msg.lower():
|
|
579
|
-
auth_data.invalidate()
|
|
580
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
581
|
-
signup_data = cls._create_temporary_account(session, proxy)
|
|
582
|
-
auth_data.auth_token = signup_data.get("token")
|
|
583
|
-
|
|
584
|
-
app_token_data = cls._get_app_token(session, auth_data.auth_token, proxy)
|
|
585
|
-
auth_data.app_token = app_token_data.get("token")
|
|
586
|
-
|
|
587
|
-
auth_data.created_at = time.time()
|
|
588
|
-
auth_data.tokens_valid = True
|
|
589
|
-
|
|
590
|
-
chat_headers["authorization"] = f"Bearer {auth_data.app_token}"
|
|
591
|
-
|
|
592
|
-
cls._shared_auth_data[model] = auth_data
|
|
593
|
-
|
|
594
|
-
continue
|
|
595
|
-
|
|
596
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
597
|
-
time.sleep(cls.RETRY_DELAY)
|
|
598
|
-
continue
|
|
599
|
-
else:
|
|
600
|
-
yield f"Error: {error_msg}"
|
|
601
|
-
return
|
|
602
|
-
|
|
603
|
-
except RateLimitError as e:
|
|
604
|
-
auth_data.set_rate_limit(cls.RATE_LIMIT_DELAY)
|
|
605
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
606
|
-
time.sleep(min(cls.RATE_LIMIT_DELAY, 10))
|
|
607
|
-
continue
|
|
608
|
-
else:
|
|
609
|
-
yield str(e)
|
|
610
|
-
return
|
|
611
|
-
|
|
612
|
-
except Exception as e:
|
|
613
|
-
if "token" in str(e).lower() or "auth" in str(e).lower():
|
|
614
|
-
auth_data.invalidate()
|
|
615
|
-
|
|
616
|
-
if attempt < cls.MAX_RETRIES - 1:
|
|
617
|
-
time.sleep(cls.RETRY_DELAY * (attempt + 1))
|
|
618
|
-
continue
|
|
619
|
-
else:
|
|
620
|
-
yield f"Error: {str(e)}"
|
|
621
|
-
return
|
|
622
|
-
|
|
623
|
-
except Exception as e:
|
|
624
|
-
if "token" in str(e).lower() or "auth" in str(e).lower():
|
|
625
|
-
auth_data.invalidate()
|
|
626
|
-
|
|
627
|
-
yield f"Error: {str(e)}"
|
|
628
|
-
return
|
|
629
|
-
if __name__ == "__main__":
|
|
630
|
-
# Example usage
|
|
631
|
-
puter = PuterJS()
|
|
632
|
-
messages = [{"role": "user", "content": "Hello, how are you?"}]
|
|
633
|
-
result = puter.create_completion(model="gpt-4o", messages=messages)
|
|
634
|
-
for res in result:
|
|
635
|
-
print(res)
|