camel-ai 0.2.65__py3-none-any.whl → 0.2.83a6__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 camel-ai might be problematic. Click here for more details.

Files changed (509) hide show
  1. camel/__init__.py +3 -3
  2. camel/agents/__init__.py +2 -2
  3. camel/agents/_types.py +9 -4
  4. camel/agents/_utils.py +40 -2
  5. camel/agents/base.py +2 -2
  6. camel/agents/chat_agent.py +5107 -995
  7. camel/agents/critic_agent.py +2 -2
  8. camel/agents/deductive_reasoner_agent.py +56 -56
  9. camel/agents/embodied_agent.py +2 -2
  10. camel/agents/knowledge_graph_agent.py +20 -20
  11. camel/agents/mcp_agent.py +35 -36
  12. camel/agents/multi_hop_generator_agent.py +3 -3
  13. camel/agents/programmed_agent_instruction.py +2 -2
  14. camel/agents/repo_agent.py +4 -3
  15. camel/agents/role_assignment_agent.py +2 -2
  16. camel/agents/search_agent.py +2 -2
  17. camel/agents/task_agent.py +2 -2
  18. camel/agents/tool_agents/__init__.py +2 -2
  19. camel/agents/tool_agents/base.py +2 -2
  20. camel/agents/tool_agents/hugging_face_tool_agent.py +3 -3
  21. camel/benchmarks/__init__.py +2 -2
  22. camel/benchmarks/apibank.py +5 -5
  23. camel/benchmarks/apibench.py +2 -2
  24. camel/benchmarks/base.py +2 -2
  25. camel/benchmarks/browsecomp.py +44 -33
  26. camel/benchmarks/gaia.py +17 -13
  27. camel/benchmarks/mock_website/README.md +1 -3
  28. camel/benchmarks/mock_website/mock_web.py +2 -2
  29. camel/benchmarks/mock_website/requirements.txt +1 -1
  30. camel/benchmarks/mock_website/shopping_mall/app.py +2 -2
  31. camel/benchmarks/mock_website/task.json +1 -1
  32. camel/benchmarks/nexus.py +3 -3
  33. camel/benchmarks/ragbench.py +2 -2
  34. camel/bots/__init__.py +2 -2
  35. camel/bots/discord/__init__.py +2 -2
  36. camel/bots/discord/discord_app.py +2 -2
  37. camel/bots/discord/discord_installation.py +2 -2
  38. camel/bots/discord/discord_store.py +3 -3
  39. camel/bots/slack/__init__.py +2 -2
  40. camel/bots/slack/models.py +4 -4
  41. camel/bots/slack/slack_app.py +2 -2
  42. camel/bots/telegram_bot.py +2 -2
  43. camel/configs/__init__.py +29 -2
  44. camel/configs/aihubmix_config.py +90 -0
  45. camel/configs/aiml_config.py +2 -2
  46. camel/configs/amd_config.py +70 -0
  47. camel/configs/anthropic_config.py +2 -2
  48. camel/configs/base_config.py +2 -2
  49. camel/configs/bedrock_config.py +5 -3
  50. camel/configs/cerebras_config.py +98 -0
  51. camel/configs/cohere_config.py +2 -2
  52. camel/configs/cometapi_config.py +106 -0
  53. camel/configs/crynux_config.py +2 -2
  54. camel/configs/deepseek_config.py +9 -8
  55. camel/configs/function_gemma_config.py +59 -0
  56. camel/configs/gemini_config.py +6 -4
  57. camel/configs/groq_config.py +6 -4
  58. camel/configs/internlm_config.py +6 -4
  59. camel/configs/litellm_config.py +2 -2
  60. camel/configs/lmstudio_config.py +6 -4
  61. camel/configs/minimax_config.py +95 -0
  62. camel/configs/mistral_config.py +2 -2
  63. camel/configs/modelscope_config.py +5 -3
  64. camel/configs/moonshot_config.py +2 -2
  65. camel/configs/nebius_config.py +105 -0
  66. camel/configs/netmind_config.py +2 -2
  67. camel/configs/novita_config.py +2 -2
  68. camel/configs/nvidia_config.py +2 -2
  69. camel/configs/ollama_config.py +2 -2
  70. camel/configs/openai_config.py +5 -3
  71. camel/configs/openrouter_config.py +6 -4
  72. camel/configs/ppio_config.py +2 -2
  73. camel/configs/qianfan_config.py +85 -0
  74. camel/configs/qwen_config.py +2 -2
  75. camel/configs/reka_config.py +2 -2
  76. camel/configs/samba_config.py +6 -4
  77. camel/configs/sglang_config.py +2 -2
  78. camel/configs/siliconflow_config.py +2 -2
  79. camel/configs/togetherai_config.py +2 -2
  80. camel/configs/vllm_config.py +4 -2
  81. camel/configs/watsonx_config.py +2 -2
  82. camel/configs/yi_config.py +6 -4
  83. camel/configs/zhipuai_config.py +6 -4
  84. camel/data_collectors/__init__.py +2 -2
  85. camel/data_collectors/alpaca_collector.py +18 -9
  86. camel/data_collectors/base.py +2 -2
  87. camel/data_collectors/sharegpt_collector.py +2 -2
  88. camel/datagen/__init__.py +2 -2
  89. camel/datagen/cot_datagen.py +3 -3
  90. camel/datagen/evol_instruct/__init__.py +2 -2
  91. camel/datagen/evol_instruct/evol_instruct.py +2 -2
  92. camel/datagen/evol_instruct/scorer.py +12 -12
  93. camel/datagen/evol_instruct/templates.py +16 -16
  94. camel/datagen/self_improving_cot.py +5 -5
  95. camel/datagen/self_instruct/__init__.py +2 -2
  96. camel/datagen/self_instruct/filter/__init__.py +2 -2
  97. camel/datagen/self_instruct/filter/filter_function.py +2 -2
  98. camel/datagen/self_instruct/filter/filter_registry.py +2 -2
  99. camel/datagen/self_instruct/filter/instruction_filter.py +2 -2
  100. camel/datagen/self_instruct/self_instruct.py +2 -2
  101. camel/datagen/self_instruct/templates.py +47 -47
  102. camel/datagen/source2synth/__init__.py +2 -2
  103. camel/datagen/source2synth/data_processor.py +2 -2
  104. camel/datagen/source2synth/models.py +2 -2
  105. camel/datagen/source2synth/user_data_processor_config.py +2 -2
  106. camel/datahubs/__init__.py +2 -2
  107. camel/datahubs/base.py +2 -2
  108. camel/datahubs/huggingface.py +2 -2
  109. camel/datahubs/models.py +2 -2
  110. camel/datasets/__init__.py +2 -2
  111. camel/datasets/base_generator.py +41 -12
  112. camel/datasets/few_shot_generator.py +18 -18
  113. camel/datasets/models.py +2 -2
  114. camel/datasets/self_instruct_generator.py +2 -2
  115. camel/datasets/static_dataset.py +2 -2
  116. camel/embeddings/__init__.py +2 -2
  117. camel/embeddings/azure_embedding.py +2 -2
  118. camel/embeddings/base.py +2 -2
  119. camel/embeddings/gemini_embedding.py +2 -2
  120. camel/embeddings/jina_embedding.py +2 -2
  121. camel/embeddings/mistral_embedding.py +2 -2
  122. camel/embeddings/openai_compatible_embedding.py +2 -2
  123. camel/embeddings/openai_embedding.py +2 -2
  124. camel/embeddings/sentence_transformers_embeddings.py +2 -2
  125. camel/embeddings/together_embedding.py +2 -2
  126. camel/embeddings/vlm_embedding.py +2 -2
  127. camel/environments/__init__.py +14 -2
  128. camel/environments/models.py +2 -2
  129. camel/environments/multi_step.py +2 -2
  130. camel/environments/rlcards_env.py +860 -0
  131. camel/environments/single_step.py +30 -5
  132. camel/environments/tic_tac_toe.py +3 -3
  133. camel/extractors/__init__.py +2 -2
  134. camel/extractors/base.py +2 -2
  135. camel/extractors/python_strategies.py +2 -2
  136. camel/generators.py +2 -2
  137. camel/human.py +2 -2
  138. camel/interpreters/__init__.py +4 -2
  139. camel/interpreters/base.py +2 -2
  140. camel/interpreters/docker/Dockerfile +14 -24
  141. camel/interpreters/docker_interpreter.py +5 -4
  142. camel/interpreters/e2b_interpreter.py +36 -3
  143. camel/interpreters/internal_python_interpreter.py +53 -4
  144. camel/interpreters/interpreter_error.py +2 -2
  145. camel/interpreters/ipython_interpreter.py +2 -2
  146. camel/interpreters/microsandbox_interpreter.py +395 -0
  147. camel/interpreters/subprocess_interpreter.py +2 -2
  148. camel/loaders/__init__.py +13 -4
  149. camel/loaders/apify_reader.py +2 -2
  150. camel/loaders/base_io.py +2 -2
  151. camel/loaders/base_loader.py +85 -0
  152. camel/loaders/chunkr_reader.py +11 -2
  153. camel/loaders/crawl4ai_reader.py +2 -2
  154. camel/loaders/firecrawl_reader.py +6 -6
  155. camel/loaders/jina_url_reader.py +2 -2
  156. camel/loaders/markitdown.py +2 -2
  157. camel/loaders/mineru_extractor.py +2 -2
  158. camel/loaders/mistral_reader.py +2 -2
  159. camel/loaders/scrapegraph_reader.py +2 -2
  160. camel/loaders/unstructured_io.py +2 -2
  161. camel/logger.py +5 -5
  162. camel/memories/__init__.py +2 -2
  163. camel/memories/agent_memories.py +86 -3
  164. camel/memories/base.py +36 -2
  165. camel/memories/blocks/__init__.py +2 -2
  166. camel/memories/blocks/chat_history_block.py +125 -7
  167. camel/memories/blocks/vectordb_block.py +10 -3
  168. camel/memories/context_creators/__init__.py +2 -2
  169. camel/memories/context_creators/score_based.py +109 -230
  170. camel/memories/records.py +90 -10
  171. camel/messages/__init__.py +2 -2
  172. camel/messages/base.py +178 -43
  173. camel/messages/conversion/__init__.py +2 -2
  174. camel/messages/conversion/alpaca.py +2 -2
  175. camel/messages/conversion/conversation_models.py +2 -2
  176. camel/messages/conversion/sharegpt/__init__.py +2 -2
  177. camel/messages/conversion/sharegpt/function_call_formatter.py +2 -2
  178. camel/messages/conversion/sharegpt/hermes/__init__.py +2 -2
  179. camel/messages/conversion/sharegpt/hermes/hermes_function_formatter.py +2 -2
  180. camel/messages/func_message.py +54 -17
  181. camel/models/__init__.py +18 -2
  182. camel/models/_utils.py +3 -3
  183. camel/models/aihubmix_model.py +83 -0
  184. camel/models/aiml_model.py +11 -18
  185. camel/models/amd_model.py +101 -0
  186. camel/models/anthropic_model.py +127 -20
  187. camel/models/aws_bedrock_model.py +12 -35
  188. camel/models/azure_openai_model.py +214 -115
  189. camel/models/base_audio_model.py +5 -3
  190. camel/models/base_model.py +378 -31
  191. camel/models/cerebras_model.py +83 -0
  192. camel/models/cohere_model.py +18 -49
  193. camel/models/cometapi_model.py +83 -0
  194. camel/models/crynux_model.py +11 -18
  195. camel/models/deepseek_model.py +20 -84
  196. camel/models/fish_audio_model.py +8 -2
  197. camel/models/function_gemma_model.py +889 -0
  198. camel/models/gemini_model.py +391 -52
  199. camel/models/groq_model.py +11 -19
  200. camel/models/internlm_model.py +11 -18
  201. camel/models/litellm_model.py +57 -49
  202. camel/models/lmstudio_model.py +17 -20
  203. camel/models/minimax_model.py +83 -0
  204. camel/models/mistral_model.py +20 -47
  205. camel/models/model_factory.py +39 -3
  206. camel/models/model_manager.py +26 -8
  207. camel/models/modelscope_model.py +13 -193
  208. camel/models/moonshot_model.py +183 -21
  209. camel/models/nebius_model.py +83 -0
  210. camel/models/nemotron_model.py +19 -9
  211. camel/models/netmind_model.py +11 -18
  212. camel/models/novita_model.py +11 -18
  213. camel/models/nvidia_model.py +11 -18
  214. camel/models/ollama_model.py +14 -21
  215. camel/models/openai_audio_models.py +2 -2
  216. camel/models/openai_compatible_model.py +190 -71
  217. camel/models/openai_model.py +192 -86
  218. camel/models/openrouter_model.py +11 -19
  219. camel/models/ppio_model.py +11 -18
  220. camel/models/qianfan_model.py +89 -0
  221. camel/models/qwen_model.py +13 -193
  222. camel/models/reka_model.py +23 -49
  223. camel/models/reward/__init__.py +2 -2
  224. camel/models/reward/base_reward_model.py +2 -2
  225. camel/models/reward/evaluator.py +2 -2
  226. camel/models/reward/nemotron_model.py +2 -2
  227. camel/models/reward/skywork_model.py +2 -2
  228. camel/models/samba_model.py +50 -75
  229. camel/models/sglang_model.py +90 -68
  230. camel/models/siliconflow_model.py +12 -35
  231. camel/models/stub_model.py +10 -7
  232. camel/models/togetherai_model.py +11 -18
  233. camel/models/vllm_model.py +10 -18
  234. camel/models/volcano_model.py +158 -19
  235. camel/models/watsonx_model.py +9 -47
  236. camel/models/yi_model.py +11 -18
  237. camel/models/zhipuai_model.py +70 -18
  238. camel/parsers/__init__.py +18 -0
  239. camel/parsers/mcp_tool_call_parser.py +176 -0
  240. camel/personas/__init__.py +2 -2
  241. camel/personas/persona.py +2 -2
  242. camel/personas/persona_hub.py +2 -2
  243. camel/prompts/__init__.py +2 -2
  244. camel/prompts/ai_society.py +2 -2
  245. camel/prompts/base.py +2 -2
  246. camel/prompts/code.py +2 -2
  247. camel/prompts/evaluation.py +2 -2
  248. camel/prompts/generate_text_embedding_data.py +2 -2
  249. camel/prompts/image_craft.py +2 -2
  250. camel/prompts/misalignment.py +2 -2
  251. camel/prompts/multi_condition_image_craft.py +2 -2
  252. camel/prompts/object_recognition.py +2 -2
  253. camel/prompts/persona_hub.py +3 -3
  254. camel/prompts/prompt_templates.py +2 -2
  255. camel/prompts/role_description_prompt_template.py +2 -2
  256. camel/prompts/solution_extraction.py +8 -8
  257. camel/prompts/task_prompt_template.py +2 -2
  258. camel/prompts/translation.py +2 -2
  259. camel/prompts/video_description_prompt.py +3 -3
  260. camel/responses/__init__.py +2 -2
  261. camel/responses/agent_responses.py +2 -2
  262. camel/retrievers/__init__.py +2 -2
  263. camel/retrievers/auto_retriever.py +3 -2
  264. camel/retrievers/base.py +2 -2
  265. camel/retrievers/bm25_retriever.py +2 -2
  266. camel/retrievers/cohere_rerank_retriever.py +2 -2
  267. camel/retrievers/hybrid_retrival.py +2 -2
  268. camel/retrievers/vector_retriever.py +2 -2
  269. camel/runtimes/Dockerfile.multi-toolkit +90 -0
  270. camel/runtimes/__init__.py +2 -2
  271. camel/runtimes/api.py +79 -23
  272. camel/runtimes/base.py +2 -2
  273. camel/runtimes/configs.py +13 -13
  274. camel/runtimes/daytona_runtime.py +17 -18
  275. camel/runtimes/docker_runtime.py +12 -12
  276. camel/runtimes/llm_guard_runtime.py +26 -26
  277. camel/runtimes/remote_http_runtime.py +11 -11
  278. camel/runtimes/ubuntu_docker_runtime.py +2 -2
  279. camel/runtimes/utils/__init__.py +2 -2
  280. camel/runtimes/utils/function_risk_toolkit.py +2 -2
  281. camel/runtimes/utils/ignore_risk_toolkit.py +2 -2
  282. camel/schemas/__init__.py +2 -2
  283. camel/schemas/base.py +2 -2
  284. camel/schemas/openai_converter.py +3 -3
  285. camel/schemas/outlines_converter.py +2 -2
  286. camel/services/agent_openapi_server.py +380 -0
  287. camel/societies/__init__.py +4 -2
  288. camel/societies/babyagi_playing.py +2 -2
  289. camel/societies/role_playing.py +201 -80
  290. camel/societies/workforce/__init__.py +10 -3
  291. camel/societies/workforce/base.py +2 -2
  292. camel/societies/workforce/events.py +145 -0
  293. camel/societies/workforce/prompts.py +259 -33
  294. camel/societies/workforce/role_playing_worker.py +88 -31
  295. camel/societies/workforce/single_agent_worker.py +638 -40
  296. camel/societies/workforce/structured_output_handler.py +512 -0
  297. camel/societies/workforce/task_channel.py +182 -38
  298. camel/societies/workforce/utils.py +780 -65
  299. camel/societies/workforce/worker.py +92 -26
  300. camel/societies/workforce/workflow_memory_manager.py +1746 -0
  301. camel/societies/workforce/workforce.py +5354 -372
  302. camel/societies/workforce/workforce_callback.py +103 -0
  303. camel/societies/workforce/workforce_logger.py +647 -0
  304. camel/societies/workforce/workforce_metrics.py +33 -0
  305. camel/storages/__init__.py +6 -2
  306. camel/storages/graph_storages/__init__.py +2 -2
  307. camel/storages/graph_storages/base.py +2 -2
  308. camel/storages/graph_storages/graph_element.py +2 -2
  309. camel/storages/graph_storages/nebula_graph.py +4 -4
  310. camel/storages/graph_storages/neo4j_graph.py +7 -7
  311. camel/storages/key_value_storages/__init__.py +2 -2
  312. camel/storages/key_value_storages/base.py +2 -2
  313. camel/storages/key_value_storages/in_memory.py +2 -2
  314. camel/storages/key_value_storages/json.py +17 -4
  315. camel/storages/key_value_storages/mem0_cloud.py +50 -49
  316. camel/storages/key_value_storages/redis.py +2 -2
  317. camel/storages/object_storages/__init__.py +2 -2
  318. camel/storages/object_storages/amazon_s3.py +2 -2
  319. camel/storages/object_storages/azure_blob.py +2 -2
  320. camel/storages/object_storages/base.py +2 -2
  321. camel/storages/object_storages/google_cloud.py +3 -3
  322. camel/storages/vectordb_storages/__init__.py +8 -2
  323. camel/storages/vectordb_storages/base.py +2 -2
  324. camel/storages/vectordb_storages/chroma.py +731 -0
  325. camel/storages/vectordb_storages/faiss.py +2 -2
  326. camel/storages/vectordb_storages/milvus.py +2 -2
  327. camel/storages/vectordb_storages/oceanbase.py +15 -15
  328. camel/storages/vectordb_storages/pgvector.py +349 -0
  329. camel/storages/vectordb_storages/qdrant.py +6 -6
  330. camel/storages/vectordb_storages/surreal.py +372 -0
  331. camel/storages/vectordb_storages/tidb.py +11 -8
  332. camel/storages/vectordb_storages/weaviate.py +2 -2
  333. camel/tasks/__init__.py +2 -2
  334. camel/tasks/task.py +348 -26
  335. camel/tasks/task_prompt.py +3 -3
  336. camel/terminators/__init__.py +2 -2
  337. camel/terminators/base.py +2 -2
  338. camel/terminators/response_terminator.py +2 -2
  339. camel/terminators/token_limit_terminator.py +2 -2
  340. camel/toolkits/__init__.py +57 -10
  341. camel/toolkits/aci_toolkit.py +66 -21
  342. camel/toolkits/arxiv_toolkit.py +8 -8
  343. camel/toolkits/ask_news_toolkit.py +2 -2
  344. camel/toolkits/async_browser_toolkit.py +4 -4
  345. camel/toolkits/audio_analysis_toolkit.py +3 -3
  346. camel/toolkits/base.py +106 -6
  347. camel/toolkits/bohrium_toolkit.py +2 -2
  348. camel/toolkits/browser_toolkit.py +34 -21
  349. camel/toolkits/browser_toolkit_commons.py +4 -4
  350. camel/toolkits/code_execution.py +31 -4
  351. camel/toolkits/context_summarizer_toolkit.py +684 -0
  352. camel/toolkits/craw4ai_toolkit.py +93 -0
  353. camel/toolkits/dappier_toolkit.py +12 -8
  354. camel/toolkits/data_commons_toolkit.py +2 -2
  355. camel/toolkits/dingtalk.py +1135 -0
  356. camel/toolkits/earth_science_toolkit.py +5367 -0
  357. camel/toolkits/edgeone_pages_mcp_toolkit.py +49 -0
  358. camel/toolkits/excel_toolkit.py +905 -71
  359. camel/toolkits/file_toolkit.py +1402 -0
  360. camel/toolkits/function_tool.py +205 -27
  361. camel/toolkits/github_toolkit.py +109 -22
  362. camel/toolkits/gmail_toolkit.py +1839 -0
  363. camel/toolkits/google_calendar_toolkit.py +40 -6
  364. camel/toolkits/google_drive_mcp_toolkit.py +54 -0
  365. camel/toolkits/google_maps_toolkit.py +2 -2
  366. camel/toolkits/google_scholar_toolkit.py +2 -2
  367. camel/toolkits/human_toolkit.py +36 -12
  368. camel/toolkits/hybrid_browser_toolkit/__init__.py +18 -0
  369. camel/toolkits/hybrid_browser_toolkit/config_loader.py +185 -0
  370. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +246 -0
  371. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +1958 -0
  372. camel/toolkits/hybrid_browser_toolkit/installer.py +203 -0
  373. camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +4589 -0
  374. camel/toolkits/hybrid_browser_toolkit/ts/package.json +33 -0
  375. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-scripts.js +125 -0
  376. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +1940 -0
  377. camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +233 -0
  378. camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +589 -0
  379. camel/toolkits/hybrid_browser_toolkit/ts/src/index.ts +7 -0
  380. camel/toolkits/hybrid_browser_toolkit/ts/src/parent-child-filter.ts +226 -0
  381. camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +219 -0
  382. camel/toolkits/hybrid_browser_toolkit/ts/src/som-screenshot-injected.ts +543 -0
  383. camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +129 -0
  384. camel/toolkits/hybrid_browser_toolkit/ts/tsconfig.json +27 -0
  385. camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +325 -0
  386. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +1037 -0
  387. camel/toolkits/hybrid_browser_toolkit_py/__init__.py +17 -0
  388. camel/toolkits/hybrid_browser_toolkit_py/actions.py +575 -0
  389. camel/toolkits/hybrid_browser_toolkit_py/agent.py +311 -0
  390. camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +787 -0
  391. camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +490 -0
  392. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +2390 -0
  393. camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +233 -0
  394. camel/toolkits/hybrid_browser_toolkit_py/stealth_script.js +0 -0
  395. camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +1043 -0
  396. camel/toolkits/image_analysis_toolkit.py +3 -6
  397. camel/toolkits/image_generation_toolkit.py +390 -0
  398. camel/toolkits/jina_reranker_toolkit.py +5 -6
  399. camel/toolkits/klavis_toolkit.py +7 -3
  400. camel/toolkits/linkedin_toolkit.py +2 -2
  401. camel/toolkits/markitdown_toolkit.py +104 -0
  402. camel/toolkits/math_toolkit.py +66 -12
  403. camel/toolkits/mcp_toolkit.py +412 -36
  404. camel/toolkits/memory_toolkit.py +7 -3
  405. camel/toolkits/meshy_toolkit.py +2 -2
  406. camel/toolkits/message_agent_toolkit.py +608 -0
  407. camel/toolkits/message_integration.py +728 -0
  408. camel/toolkits/microsoft_outlook_mail_toolkit.py +1885 -0
  409. camel/toolkits/mineru_toolkit.py +2 -2
  410. camel/toolkits/minimax_mcp_toolkit.py +195 -0
  411. camel/toolkits/networkx_toolkit.py +2 -2
  412. camel/toolkits/note_taking_toolkit.py +277 -0
  413. camel/toolkits/notion_mcp_toolkit.py +224 -0
  414. camel/toolkits/notion_toolkit.py +2 -2
  415. camel/toolkits/open_api_specs/biztoc/__init__.py +2 -2
  416. camel/toolkits/open_api_specs/biztoc/ai-plugin.json +1 -1
  417. camel/toolkits/open_api_specs/coursera/__init__.py +2 -2
  418. camel/toolkits/open_api_specs/create_qr_code/__init__.py +2 -2
  419. camel/toolkits/open_api_specs/klarna/__init__.py +2 -2
  420. camel/toolkits/open_api_specs/nasa_apod/__init__.py +2 -2
  421. camel/toolkits/open_api_specs/outschool/__init__.py +2 -2
  422. camel/toolkits/open_api_specs/outschool/ai-plugin.json +1 -1
  423. camel/toolkits/open_api_specs/outschool/openapi.yaml +1 -1
  424. camel/toolkits/open_api_specs/outschool/paths/__init__.py +2 -2
  425. camel/toolkits/open_api_specs/outschool/paths/get_classes.py +2 -2
  426. camel/toolkits/open_api_specs/outschool/paths/search_teachers.py +2 -2
  427. camel/toolkits/open_api_specs/security_config.py +2 -2
  428. camel/toolkits/open_api_specs/speak/__init__.py +2 -2
  429. camel/toolkits/open_api_specs/web_scraper/__init__.py +2 -2
  430. camel/toolkits/open_api_specs/web_scraper/ai-plugin.json +1 -1
  431. camel/toolkits/open_api_specs/web_scraper/paths/__init__.py +2 -2
  432. camel/toolkits/open_api_specs/web_scraper/paths/scraper.py +2 -2
  433. camel/toolkits/open_api_toolkit.py +2 -2
  434. camel/toolkits/openbb_toolkit.py +7 -3
  435. camel/toolkits/origene_mcp_toolkit.py +56 -0
  436. camel/toolkits/page_script.js +53 -53
  437. camel/toolkits/playwright_mcp_toolkit.py +13 -31
  438. camel/toolkits/pptx_toolkit.py +36 -23
  439. camel/toolkits/pubmed_toolkit.py +2 -2
  440. camel/toolkits/pulse_mcp_search_toolkit.py +2 -2
  441. camel/toolkits/pyautogui_toolkit.py +2 -2
  442. camel/toolkits/reddit_toolkit.py +2 -2
  443. camel/toolkits/resend_toolkit.py +168 -0
  444. camel/toolkits/retrieval_toolkit.py +2 -2
  445. camel/toolkits/screenshot_toolkit.py +213 -0
  446. camel/toolkits/search_toolkit.py +606 -156
  447. camel/toolkits/searxng_toolkit.py +2 -2
  448. camel/toolkits/semantic_scholar_toolkit.py +2 -2
  449. camel/toolkits/slack_toolkit.py +108 -58
  450. camel/toolkits/sql_toolkit.py +712 -0
  451. camel/toolkits/stripe_toolkit.py +2 -2
  452. camel/toolkits/sympy_toolkit.py +3 -3
  453. camel/toolkits/task_planning_toolkit.py +5 -5
  454. camel/toolkits/terminal_toolkit/__init__.py +18 -0
  455. camel/toolkits/terminal_toolkit/terminal_toolkit.py +1281 -0
  456. camel/toolkits/terminal_toolkit/utils.py +659 -0
  457. camel/toolkits/thinking_toolkit.py +3 -3
  458. camel/toolkits/twitter_toolkit.py +2 -2
  459. camel/toolkits/vertex_ai_veo_toolkit.py +590 -0
  460. camel/toolkits/video_analysis_toolkit.py +109 -29
  461. camel/toolkits/video_download_toolkit.py +19 -16
  462. camel/toolkits/weather_toolkit.py +2 -2
  463. camel/toolkits/web_deploy_toolkit.py +1219 -0
  464. camel/toolkits/wechat_official_toolkit.py +483 -0
  465. camel/toolkits/whatsapp_toolkit.py +2 -2
  466. camel/toolkits/wolfram_alpha_toolkit.py +2 -2
  467. camel/toolkits/zapier_toolkit.py +7 -3
  468. camel/types/__init__.py +4 -4
  469. camel/types/agents/__init__.py +2 -2
  470. camel/types/agents/tool_calling_record.py +6 -3
  471. camel/types/enums.py +381 -41
  472. camel/types/mcp_registries.py +2 -2
  473. camel/types/openai_types.py +4 -4
  474. camel/types/unified_model_type.py +46 -10
  475. camel/utils/__init__.py +5 -2
  476. camel/utils/agent_context.py +41 -0
  477. camel/utils/async_func.py +2 -2
  478. camel/utils/chunker/__init__.py +2 -2
  479. camel/utils/chunker/base.py +2 -2
  480. camel/utils/chunker/code_chunker.py +2 -2
  481. camel/utils/chunker/uio_chunker.py +2 -2
  482. camel/utils/commons.py +38 -7
  483. camel/utils/constants.py +5 -2
  484. camel/utils/context_utils.py +1134 -0
  485. camel/utils/deduplication.py +2 -2
  486. camel/utils/filename.py +2 -2
  487. camel/utils/langfuse.py +18 -10
  488. camel/utils/mcp.py +140 -6
  489. camel/utils/mcp_client.py +48 -38
  490. camel/utils/message_summarizer.py +148 -0
  491. camel/utils/response_format.py +2 -2
  492. camel/utils/token_counting.py +45 -22
  493. camel/utils/tool_result.py +44 -0
  494. camel/verifiers/__init__.py +2 -2
  495. camel/verifiers/base.py +2 -2
  496. camel/verifiers/math_verifier.py +2 -2
  497. camel/verifiers/models.py +2 -2
  498. camel/verifiers/physics_verifier.py +2 -2
  499. camel/verifiers/python_verifier.py +2 -2
  500. {camel_ai-0.2.65.dist-info → camel_ai-0.2.83a6.dist-info}/METADATA +355 -117
  501. camel_ai-0.2.83a6.dist-info/RECORD +511 -0
  502. {camel_ai-0.2.65.dist-info → camel_ai-0.2.83a6.dist-info}/WHEEL +1 -1
  503. {camel_ai-0.2.65.dist-info → camel_ai-0.2.83a6.dist-info}/licenses/LICENSE +1 -1
  504. camel/loaders/pandas_reader.py +0 -368
  505. camel/toolkits/dalle_toolkit.py +0 -175
  506. camel/toolkits/file_write_toolkit.py +0 -444
  507. camel/toolkits/openai_agent_toolkit.py +0 -135
  508. camel/toolkits/terminal_toolkit.py +0 -1037
  509. camel_ai-0.2.65.dist-info/RECORD +0 -426
@@ -0,0 +1,787 @@
1
+ # ========= Copyright 2023-2026 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2026 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from __future__ import annotations
15
+
16
+ import asyncio
17
+ from collections import deque
18
+ from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Tuple
19
+
20
+ from camel.logger import get_logger
21
+
22
+ from .actions import ActionExecutor
23
+ from .config_loader import ConfigLoader
24
+ from .snapshot import PageSnapshot
25
+
26
+ if TYPE_CHECKING:
27
+ from playwright.async_api import (
28
+ Browser,
29
+ BrowserContext,
30
+ ConsoleMessage,
31
+ Page,
32
+ Playwright,
33
+ )
34
+
35
+ logger = get_logger(__name__)
36
+
37
+
38
+ class TabIdGenerator:
39
+ """Monotonically increasing tab ID generator."""
40
+
41
+ _counter: int = 0
42
+ _lock: ClassVar[asyncio.Lock] = asyncio.Lock()
43
+
44
+ @classmethod
45
+ async def generate_tab_id(cls) -> str:
46
+ """Generate a monotonically increasing tab ID."""
47
+ async with cls._lock:
48
+ cls._counter += 1
49
+ return f"tab-{cls._counter:03d}"
50
+
51
+
52
+ class HybridBrowserSession:
53
+ """Lightweight wrapper around Playwright for
54
+ browsing with multi-tab support.
55
+
56
+ It provides multiple *Page* instances plus helper utilities (snapshot &
57
+ executor). Multiple toolkits or agents can reuse this class without
58
+ duplicating Playwright setup code.
59
+
60
+ This class is a singleton per event-loop and session-id combination.
61
+ """
62
+
63
+ # Class-level registry for singleton instances
64
+ # Format: {(loop_id, session_id): HybridBrowserSession}
65
+ _instances: ClassVar[Dict[Tuple[Any, str], "HybridBrowserSession"]] = {}
66
+ _instances_lock: ClassVar[asyncio.Lock] = asyncio.Lock()
67
+
68
+ _initialized: bool
69
+ _creation_params: Dict[str, Any]
70
+
71
+ def __new__(
72
+ cls,
73
+ *,
74
+ headless: bool = True,
75
+ user_data_dir: Optional[str] = None,
76
+ stealth: bool = False,
77
+ session_id: Optional[str] = None,
78
+ default_timeout: Optional[int] = None,
79
+ short_timeout: Optional[int] = None,
80
+ navigation_timeout: Optional[int] = None,
81
+ network_idle_timeout: Optional[int] = None,
82
+ ) -> "HybridBrowserSession":
83
+ # Create a unique key for this event loop and session combination
84
+ # We defer the event loop lookup to avoid issues with creation
85
+ # outside async context
86
+ instance = super().__new__(cls)
87
+ instance._initialized = False
88
+ instance._session_id = session_id or "default"
89
+ instance._creation_params = {
90
+ "headless": headless,
91
+ "user_data_dir": user_data_dir,
92
+ "stealth": stealth,
93
+ "session_id": session_id,
94
+ "default_timeout": default_timeout,
95
+ "short_timeout": short_timeout,
96
+ "navigation_timeout": navigation_timeout,
97
+ "network_idle_timeout": network_idle_timeout,
98
+ }
99
+ return instance
100
+
101
+ @classmethod
102
+ async def _get_or_create_instance(
103
+ cls,
104
+ instance: "HybridBrowserSession",
105
+ ) -> "HybridBrowserSession":
106
+ """Get or create singleton instance for the current event loop and
107
+ session."""
108
+ try:
109
+ loop = asyncio.get_running_loop()
110
+ loop_id = str(id(loop))
111
+ except RuntimeError:
112
+ # No event loop running, use a unique identifier for sync context
113
+ import threading
114
+
115
+ loop_id = f"sync_{threading.current_thread().ident}"
116
+
117
+ # Ensure session_id is never None for the key
118
+ session_id = (
119
+ instance._session_id
120
+ if instance._session_id is not None
121
+ else "default"
122
+ )
123
+ session_key = (loop_id, session_id)
124
+
125
+ # Use class-level lock to protect the instances registry
126
+ async with cls._instances_lock:
127
+ if session_key in cls._instances:
128
+ existing_instance = cls._instances[session_key]
129
+ logger.debug(
130
+ f"Reusing existing browser session for session_id: "
131
+ f"{session_id}"
132
+ )
133
+ return existing_instance
134
+
135
+ # Register this new instance
136
+ cls._instances[session_key] = instance
137
+ logger.debug(
138
+ f"Created new browser session for session_id: {session_id}"
139
+ )
140
+ return instance
141
+
142
+ def __init__(
143
+ self,
144
+ *,
145
+ headless: bool = True,
146
+ user_data_dir: Optional[str] = None,
147
+ stealth: bool = False,
148
+ session_id: Optional[str] = None,
149
+ default_timeout: Optional[int] = None,
150
+ short_timeout: Optional[int] = None,
151
+ navigation_timeout: Optional[int] = None,
152
+ network_idle_timeout: Optional[int] = None,
153
+ ):
154
+ if self._initialized:
155
+ return
156
+ self._initialized = True
157
+
158
+ self._headless = headless
159
+ self._user_data_dir = user_data_dir
160
+ self._stealth = stealth
161
+ self._session_id = session_id or "default"
162
+
163
+ # Store timeout configuration for ActionExecutor instances and
164
+ # browser operations
165
+ self._default_timeout = default_timeout
166
+ self._short_timeout = short_timeout
167
+ self._navigation_timeout = ConfigLoader.get_navigation_timeout(
168
+ navigation_timeout
169
+ )
170
+ self._network_idle_timeout = ConfigLoader.get_network_idle_timeout(
171
+ network_idle_timeout
172
+ )
173
+
174
+ # Initialize _creation_params to fix linter error
175
+ self._creation_params = {
176
+ "headless": headless,
177
+ "user_data_dir": user_data_dir,
178
+ "stealth": stealth,
179
+ "session_id": session_id,
180
+ "default_timeout": default_timeout,
181
+ "short_timeout": short_timeout,
182
+ "navigation_timeout": navigation_timeout,
183
+ "network_idle_timeout": network_idle_timeout,
184
+ }
185
+
186
+ self._playwright: Optional[Playwright] = None
187
+ self._browser: Optional[Browser] = None
188
+ self._context: Optional[BrowserContext] = None
189
+ self._page: Optional[Page] = None
190
+
191
+ # Dictionary-based tab management with monotonic IDs
192
+ self._pages: Dict[str, Page] = {} # tab_id -> Page object
193
+ self._console_logs: Dict[str, Any] = {} # tab_id -> page logs
194
+ self._current_tab_id: Optional[str] = None # Current active tab ID
195
+ self.log_limit: int = ConfigLoader.get_max_log_limit() or 1000
196
+
197
+ self.snapshot: Optional[PageSnapshot] = None
198
+ self.executor: Optional[ActionExecutor] = None
199
+
200
+ # Protect browser initialisation against concurrent calls
201
+ self._ensure_lock: "asyncio.Lock" = asyncio.Lock()
202
+
203
+ # Load stealth script and config on initialization
204
+ self._stealth_script: Optional[str] = None
205
+ self._stealth_config: Optional[Dict[str, Any]] = None
206
+ if self._stealth:
207
+ self._stealth_script = self._load_stealth_script()
208
+ stealth_config_class = ConfigLoader.get_stealth_config()
209
+ self._stealth_config = stealth_config_class.get_stealth_config()
210
+
211
+ def _load_stealth_script(self) -> str:
212
+ r"""Load the stealth JavaScript script from file."""
213
+ import os
214
+
215
+ script_path = os.path.join(
216
+ os.path.dirname(os.path.abspath(__file__)), "stealth_script.js"
217
+ )
218
+
219
+ try:
220
+ with open(
221
+ script_path, "r", encoding='utf-8', errors='replace'
222
+ ) as f:
223
+ script_content = f.read()
224
+
225
+ if not script_content.strip():
226
+ raise ValueError(f"Stealth script is empty: {script_path}")
227
+
228
+ logger.debug(
229
+ f"Loaded stealth script ({len(script_content)} chars)"
230
+ )
231
+ return script_content
232
+ except FileNotFoundError:
233
+ logger.error(f"Stealth script not found: {script_path}")
234
+ raise FileNotFoundError(f"Stealth script not found: {script_path}")
235
+ except Exception as e:
236
+ logger.error(f"Error loading stealth script: {e}")
237
+ raise RuntimeError(f"Failed to load stealth script: {e}") from e
238
+
239
+ # ------------------------------------------------------------------
240
+ # Multi-tab management methods
241
+ # ------------------------------------------------------------------
242
+ async def create_new_tab(self, url: Optional[str] = None) -> str:
243
+ r"""Create a new tab and optionally navigate to a URL.
244
+
245
+ Args:
246
+ url: Optional URL to navigate to in the new tab
247
+
248
+ Returns:
249
+ str: ID of the newly created tab
250
+ """
251
+ await self.ensure_browser()
252
+
253
+ if self._context is None:
254
+ raise RuntimeError("Browser context is not available")
255
+
256
+ # Generate unique tab ID
257
+ tab_id = await TabIdGenerator.generate_tab_id()
258
+
259
+ # Create new page
260
+ new_page = await self._context.new_page()
261
+
262
+ # Apply stealth modifications if enabled
263
+ if self._stealth and self._stealth_script:
264
+ try:
265
+ await new_page.add_init_script(self._stealth_script)
266
+ logger.debug("Applied stealth script to new tab")
267
+ except Exception as e:
268
+ logger.warning(
269
+ f"Failed to apply stealth script to new tab: {e}"
270
+ )
271
+
272
+ # Store in pages dictionary
273
+ await self._register_new_page(tab_id, new_page)
274
+
275
+ # Navigate if URL provided
276
+ if url:
277
+ try:
278
+ await new_page.goto(url, timeout=self._navigation_timeout)
279
+ await new_page.wait_for_load_state('domcontentloaded')
280
+ except Exception as e:
281
+ logger.warning(f"Failed to navigate new tab to {url}: {e}")
282
+
283
+ logger.info(
284
+ f"Created new tab {tab_id}, total tabs: {len(self._pages)}"
285
+ )
286
+ return tab_id
287
+
288
+ async def _register_new_page(self, tab_id: str, new_page: "Page") -> None:
289
+ r"""Register a page and add console event listerers.
290
+
291
+ Args:
292
+ new_page (Page): The new page object to register.
293
+ """
294
+ # Add new page
295
+ self._pages[tab_id] = new_page
296
+ # Create log for the page
297
+ self._console_logs[tab_id] = deque(maxlen=self.log_limit)
298
+
299
+ # Add event function
300
+ def handle_console_log(msg: ConsoleMessage):
301
+ logs = self._console_logs.get(tab_id)
302
+ if logs is not None:
303
+ logs.append({"type": msg.type, "text": msg.text})
304
+
305
+ # Add event listener for console logs
306
+ new_page.on(event="console", f=handle_console_log)
307
+
308
+ def handle_page_close(page: "Page"):
309
+ self._console_logs.pop(tab_id, None)
310
+
311
+ # Add event listener for cleanup
312
+ new_page.on(event="close", f=handle_page_close)
313
+
314
+ async def register_page(self, new_page: "Page") -> str:
315
+ r"""Register a page that was created externally (e.g., by a click).
316
+
317
+ Args:
318
+ new_page (Page): The new page object to register.
319
+
320
+ Returns:
321
+ str: The ID of the (newly) registered tab.
322
+ """
323
+ # Check if page is already registered
324
+ for tab_id, page in self._pages.items():
325
+ if page is new_page:
326
+ return tab_id
327
+
328
+ # Create new ID for the page
329
+ tab_id = await TabIdGenerator.generate_tab_id()
330
+ await self._register_new_page(tab_id, new_page)
331
+
332
+ logger.info(
333
+ f"Registered new tab {tab_id} (opened by user action). "
334
+ f"Total tabs: {len(self._pages)}"
335
+ )
336
+ return tab_id
337
+
338
+ async def switch_to_tab(self, tab_id: str) -> bool:
339
+ r"""Switch to a specific tab by ID.
340
+
341
+ Args:
342
+ tab_id: ID of the tab to switch to
343
+
344
+ Returns:
345
+ bool: True if successful, False if tab ID is invalid
346
+ """
347
+ if tab_id not in self._pages:
348
+ logger.warning(f"Invalid tab ID: {tab_id}")
349
+ return False
350
+
351
+ page = self._pages[tab_id]
352
+
353
+ # Check if page is still valid
354
+ if page.is_closed():
355
+ logger.warning(f"Tab {tab_id} is closed, removing from registry")
356
+ # Clean up closed tab
357
+ del self._pages[tab_id]
358
+ return False
359
+
360
+ try:
361
+ # Switch to the tab
362
+ self._current_tab_id = tab_id
363
+ self._page = page
364
+
365
+ # Bring the tab to the front in the browser window
366
+ await page.bring_to_front()
367
+
368
+ # Update utilities for new tab
369
+ self.executor = ActionExecutor(
370
+ page,
371
+ self,
372
+ default_timeout=self._default_timeout,
373
+ short_timeout=self._short_timeout,
374
+ )
375
+ self.snapshot = PageSnapshot(page)
376
+
377
+ logger.info(f"Switched to tab {tab_id}")
378
+ return True
379
+
380
+ except Exception as e:
381
+ logger.warning(f"Error switching to tab {tab_id}: {e}")
382
+ return False
383
+
384
+ async def close_tab(self, tab_id: str) -> bool:
385
+ r"""Close a specific tab by ID.
386
+
387
+ Args:
388
+ tab_id: ID of the tab to close
389
+
390
+ Returns:
391
+ bool: True if successful, False if tab ID is invalid
392
+ """
393
+ if tab_id not in self._pages:
394
+ logger.warning(f"Invalid tab ID: {tab_id}")
395
+ return False
396
+
397
+ page = self._pages[tab_id]
398
+
399
+ try:
400
+ # Close the page if not already closed
401
+ if not page.is_closed():
402
+ await page.close()
403
+
404
+ # Remove from our dictionary
405
+ del self._pages[tab_id]
406
+
407
+ # If we closed the current tab, switch to another one
408
+ if tab_id == self._current_tab_id:
409
+ if self._pages:
410
+ # Switch to any available tab (first one we find)
411
+ next_tab_id = next(iter(self._pages.keys()))
412
+ await self.switch_to_tab(next_tab_id)
413
+ else:
414
+ # No tabs left
415
+ self._current_tab_id = None
416
+ self._page = None
417
+ self.executor = None
418
+ self.snapshot = None
419
+
420
+ logger.info(
421
+ f"Closed tab {tab_id}, remaining tabs: {len(self._pages)}"
422
+ )
423
+ return True
424
+
425
+ except Exception as e:
426
+ logger.warning(f"Error closing tab {tab_id}: {e}")
427
+ return False
428
+
429
+ async def get_tab_info(self) -> List[Dict[str, Any]]:
430
+ r"""Get information about all open tabs including IDs.
431
+
432
+ Returns:
433
+ List of dictionaries containing tab information
434
+ """
435
+ tab_info = []
436
+ tabs_to_cleanup = []
437
+
438
+ # Process all tabs in dictionary
439
+ for tab_id, page in list(self._pages.items()):
440
+ try:
441
+ if not page.is_closed():
442
+ title = await page.title()
443
+ url = page.url
444
+ is_current = tab_id == self._current_tab_id
445
+ tab_info.append(
446
+ {
447
+ "tab_id": tab_id,
448
+ "title": title,
449
+ "url": url,
450
+ "is_current": is_current,
451
+ }
452
+ )
453
+ else:
454
+ # Mark for cleanup
455
+ tabs_to_cleanup.append(tab_id)
456
+ except Exception as e:
457
+ logger.warning(f"Error getting info for tab {tab_id}: {e}")
458
+ tabs_to_cleanup.append(tab_id)
459
+
460
+ # Clean up closed/invalid tabs
461
+ for tab_id in tabs_to_cleanup:
462
+ if tab_id in self._pages:
463
+ del self._pages[tab_id]
464
+
465
+ return tab_info
466
+
467
+ async def get_current_tab_id(self) -> Optional[str]:
468
+ r"""Get the id for the current active tab."""
469
+ if not self._current_tab_id or not self._pages:
470
+ return None
471
+ return self._current_tab_id
472
+
473
+ # ------------------------------------------------------------------
474
+ # Browser lifecycle helpers
475
+ # ------------------------------------------------------------------
476
+ async def ensure_browser(self) -> None:
477
+ r"""Ensure browser is ready. Each session_id gets its own browser
478
+ instance."""
479
+ # First, get the singleton instance for this session
480
+ singleton_instance = await self._get_or_create_instance(self)
481
+
482
+ # If this isn't the singleton instance, delegate to the singleton
483
+ if singleton_instance is not self:
484
+ await singleton_instance.ensure_browser()
485
+ # Copy the singleton's browser state to this instance
486
+ self._playwright = singleton_instance._playwright
487
+ self._browser = singleton_instance._browser
488
+ self._context = singleton_instance._context
489
+ self._page = singleton_instance._page
490
+ self._pages = singleton_instance._pages
491
+ self._console_logs = singleton_instance._console_logs
492
+ self._current_tab_id = singleton_instance._current_tab_id
493
+ self.snapshot = singleton_instance.snapshot
494
+ self.executor = singleton_instance.executor
495
+ return
496
+
497
+ # Serialise initialisation to avoid race conditions where multiple
498
+ # concurrent coroutine calls create multiple browser instances for
499
+ # the same HybridBrowserSession.
500
+ async with self._ensure_lock:
501
+ await self._ensure_browser_inner()
502
+
503
+ # Moved original logic to helper
504
+ async def _ensure_browser_inner(self) -> None:
505
+ r"""Internal browser initialization logic."""
506
+ from playwright.async_api import async_playwright
507
+
508
+ if self._page is not None:
509
+ return
510
+
511
+ self._playwright = await async_playwright().start()
512
+
513
+ # Prepare stealth options
514
+ launch_options: Dict[str, Any] = {"headless": self._headless}
515
+ context_options: Dict[str, Any] = {}
516
+ if self._stealth and self._stealth_config:
517
+ # Use preloaded stealth configuration
518
+ launch_options['args'] = self._stealth_config['launch_args']
519
+ context_options.update(self._stealth_config['context_options'])
520
+
521
+ if self._user_data_dir:
522
+ context = (
523
+ await self._playwright.chromium.launch_persistent_context(
524
+ user_data_dir=self._user_data_dir,
525
+ **launch_options,
526
+ **context_options,
527
+ )
528
+ )
529
+ self._context = context
530
+ # Get the first (default) page
531
+ pages = context.pages
532
+ if pages:
533
+ self._page = pages[0]
534
+ # Create ID for initial page
535
+ initial_tab_id = await TabIdGenerator.generate_tab_id()
536
+ await self._register_new_page(initial_tab_id, pages[0])
537
+ self._current_tab_id = initial_tab_id
538
+ # Handle additional pages if any
539
+ for page in pages[1:]:
540
+ tab_id = await TabIdGenerator.generate_tab_id()
541
+ await self._register_new_page(tab_id, page)
542
+ else:
543
+ self._page = await context.new_page()
544
+ initial_tab_id = await TabIdGenerator.generate_tab_id()
545
+ await self._register_new_page(initial_tab_id, self._page)
546
+ self._current_tab_id = initial_tab_id
547
+ else:
548
+ self._browser = await self._playwright.chromium.launch(
549
+ **launch_options
550
+ )
551
+ self._context = await self._browser.new_context(**context_options)
552
+ self._page = await self._context.new_page()
553
+
554
+ # Create ID for initial page
555
+ initial_tab_id = await TabIdGenerator.generate_tab_id()
556
+ await self._register_new_page(initial_tab_id, self._page)
557
+ self._current_tab_id = initial_tab_id
558
+
559
+ # Apply stealth modifications if enabled
560
+ if self._stealth and self._stealth_script:
561
+ try:
562
+ await self._page.add_init_script(self._stealth_script)
563
+ logger.debug("Applied stealth script to main page")
564
+ except Exception as e:
565
+ logger.warning(f"Failed to apply stealth script: {e}")
566
+
567
+ # Set up timeout for navigation
568
+ self._page.set_default_navigation_timeout(self._navigation_timeout)
569
+ self._page.set_default_timeout(self._navigation_timeout)
570
+
571
+ # Initialize utilities
572
+ self.snapshot = PageSnapshot(self._page)
573
+ self.executor = ActionExecutor(
574
+ self._page,
575
+ self,
576
+ default_timeout=self._default_timeout,
577
+ short_timeout=self._short_timeout,
578
+ )
579
+
580
+ logger.info("Browser session initialized successfully")
581
+
582
+ async def close(self) -> None:
583
+ r"""Close browser session and clean up resources."""
584
+ if self._page is None:
585
+ return
586
+
587
+ try:
588
+ logger.debug("Closing browser session...")
589
+ await self._close_session()
590
+
591
+ # Remove from singleton registry
592
+ try:
593
+ try:
594
+ loop = asyncio.get_running_loop()
595
+ loop_id = str(id(loop))
596
+ except RuntimeError:
597
+ # Use same logic as _get_or_create_instance
598
+ import threading
599
+
600
+ loop_id = f"sync_{threading.current_thread().ident}"
601
+
602
+ session_id = (
603
+ self._session_id
604
+ if self._session_id is not None
605
+ else "default"
606
+ )
607
+ session_key = (loop_id, session_id)
608
+
609
+ async with self._instances_lock:
610
+ if (
611
+ session_key in self._instances
612
+ and self._instances[session_key] is self
613
+ ):
614
+ del self._instances[session_key]
615
+ logger.debug(
616
+ f"Removed session {session_id} from registry"
617
+ )
618
+
619
+ except Exception as registry_error:
620
+ logger.warning(f"Error cleaning up registry: {registry_error}")
621
+
622
+ logger.debug("Browser session closed successfully")
623
+ except Exception as e:
624
+ logger.error(f"Error during browser session close: {e}")
625
+ finally:
626
+ self._page = None
627
+ self._pages = {}
628
+ self._current_tab_id = None
629
+ self.snapshot = None
630
+ self.executor = None
631
+
632
+ async def _close_session(self) -> None:
633
+ r"""Internal session close logic with thorough cleanup."""
634
+ try:
635
+ # Close all pages first
636
+ pages_to_close = list(self._pages.values())
637
+ for page in pages_to_close:
638
+ try:
639
+ if not page.is_closed():
640
+ await page.close()
641
+ logger.debug(
642
+ f"Closed page: "
643
+ f"{page.url if hasattr(page, 'url') else 'unknown'}" # noqa:E501
644
+ )
645
+ except Exception as e:
646
+ logger.warning(f"Error closing page: {e}")
647
+
648
+ # Clear the pages dictionary
649
+ self._pages.clear()
650
+
651
+ # Close context with explicit wait
652
+ if self._context:
653
+ try:
654
+ await self._context.close()
655
+ logger.debug("Browser context closed")
656
+ except Exception as e:
657
+ logger.warning(f"Error closing context: {e}")
658
+ finally:
659
+ self._context = None
660
+
661
+ # Close browser with explicit wait
662
+ if self._browser:
663
+ try:
664
+ await self._browser.close()
665
+ logger.debug("Browser instance closed")
666
+ except Exception as e:
667
+ logger.warning(f"Error closing browser: {e}")
668
+ finally:
669
+ self._browser = None
670
+
671
+ # Stop playwright with increased delay for cleanup
672
+ if self._playwright:
673
+ try:
674
+ await self._playwright.stop()
675
+ logger.debug("Playwright stopped")
676
+
677
+ # Give more time for complete subprocess cleanup
678
+ import asyncio
679
+
680
+ await asyncio.sleep(0.5)
681
+
682
+ except Exception as e:
683
+ logger.warning(f"Error stopping playwright: {e}")
684
+ finally:
685
+ self._playwright = None
686
+
687
+ except Exception as e:
688
+ logger.error(f"Error during session cleanup: {e}")
689
+ finally:
690
+ # Ensure all attributes are cleared regardless of errors
691
+ self._page = None
692
+ self._pages = {}
693
+ self._current_tab_id = None
694
+ self._context = None
695
+ self._browser = None
696
+ self._playwright = None
697
+
698
+ @classmethod
699
+ async def close_all_sessions(cls) -> None:
700
+ r"""Close all browser sessions and clean up the singleton registry."""
701
+ logger.debug("Closing all browser sessions...")
702
+ async with cls._instances_lock:
703
+ # Close all active sessions
704
+ instances_to_close = list(cls._instances.values())
705
+ cls._instances.clear()
706
+ logger.debug(f"Closing {len(instances_to_close)} sessions.")
707
+
708
+ # Close sessions outside the lock to avoid deadlock
709
+ for instance in instances_to_close:
710
+ try:
711
+ await instance._close_session()
712
+ logger.debug(f"Closed session: {instance._session_id}")
713
+ except Exception as e:
714
+ logger.error(
715
+ f"Error closing session {instance._session_id}: {e}"
716
+ )
717
+
718
+ logger.debug("All browser sessions closed and registry cleared")
719
+
720
+ @classmethod
721
+ async def close_all(cls) -> None:
722
+ """Alias for close_all_sessions for backward compatibility."""
723
+ await cls.close_all_sessions()
724
+
725
+ # ------------------------------------------------------------------
726
+ # Page interaction
727
+ # ------------------------------------------------------------------
728
+ async def visit(self, url: str) -> str:
729
+ r"""Navigate current tab to URL."""
730
+ await self.ensure_browser()
731
+ page = await self.get_page()
732
+
733
+ await page.goto(url, timeout=self._navigation_timeout)
734
+ await page.wait_for_load_state('domcontentloaded')
735
+
736
+ # Try to wait for network idle
737
+ try:
738
+ await page.wait_for_load_state(
739
+ 'networkidle', timeout=self._network_idle_timeout
740
+ )
741
+ except Exception:
742
+ logger.debug("Network idle timeout - continuing anyway")
743
+
744
+ return f"Navigated to {url}"
745
+
746
+ async def get_snapshot(
747
+ self,
748
+ *,
749
+ force_refresh: bool = False,
750
+ diff_only: bool = False,
751
+ viewport_limit: bool = False,
752
+ ) -> str:
753
+ r"""Get snapshot for current tab."""
754
+ if not self.snapshot:
755
+ return "<empty>"
756
+ return await self.snapshot.capture(
757
+ force_refresh=force_refresh,
758
+ diff_only=diff_only,
759
+ viewport_limit=viewport_limit,
760
+ )
761
+
762
+ async def exec_action(self, action: Dict[str, Any]) -> Dict[str, Any]:
763
+ r"""Execute action on current tab."""
764
+ if not self.executor:
765
+ return {
766
+ "success": False,
767
+ "message": "No executor available",
768
+ "details": {},
769
+ }
770
+ return await self.executor.execute(action)
771
+
772
+ async def get_page(self) -> "Page":
773
+ r"""Get current active page."""
774
+ await self.ensure_browser()
775
+ if self._page is None:
776
+ raise RuntimeError("No active page available")
777
+ return self._page
778
+
779
+ async def get_console_logs(self) -> Dict[str, Any]:
780
+ r"""Get current active logs."""
781
+ await self.ensure_browser()
782
+ if self._current_tab_id is None:
783
+ raise RuntimeError("No active tab available")
784
+ logs = self._console_logs.get(self._current_tab_id, None)
785
+ if logs is None:
786
+ raise RuntimeError("No active logs available for the page")
787
+ return logs