camel-ai 0.2.82__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 (481) hide show
  1. camel/__init__.py +3 -3
  2. camel/agents/__init__.py +2 -2
  3. camel/agents/_types.py +2 -2
  4. camel/agents/_utils.py +2 -2
  5. camel/agents/base.py +2 -2
  6. camel/agents/chat_agent.py +765 -541
  7. camel/agents/critic_agent.py +2 -2
  8. camel/agents/deductive_reasoner_agent.py +2 -2
  9. camel/agents/embodied_agent.py +2 -2
  10. camel/agents/knowledge_graph_agent.py +2 -2
  11. camel/agents/mcp_agent.py +2 -2
  12. camel/agents/multi_hop_generator_agent.py +2 -2
  13. camel/agents/programmed_agent_instruction.py +2 -2
  14. camel/agents/repo_agent.py +2 -2
  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 +2 -2
  21. camel/benchmarks/__init__.py +2 -2
  22. camel/benchmarks/apibank.py +2 -2
  23. camel/benchmarks/apibench.py +2 -2
  24. camel/benchmarks/base.py +2 -2
  25. camel/benchmarks/browsecomp.py +2 -2
  26. camel/benchmarks/gaia.py +2 -2
  27. camel/benchmarks/mock_website/mock_web.py +2 -2
  28. camel/benchmarks/mock_website/shopping_mall/app.py +2 -2
  29. camel/benchmarks/nexus.py +2 -2
  30. camel/benchmarks/ragbench.py +2 -2
  31. camel/bots/__init__.py +2 -2
  32. camel/bots/discord/__init__.py +2 -2
  33. camel/bots/discord/discord_app.py +2 -2
  34. camel/bots/discord/discord_installation.py +2 -2
  35. camel/bots/discord/discord_store.py +2 -2
  36. camel/bots/slack/__init__.py +2 -2
  37. camel/bots/slack/models.py +2 -2
  38. camel/bots/slack/slack_app.py +2 -2
  39. camel/bots/telegram_bot.py +2 -2
  40. camel/configs/__init__.py +8 -2
  41. camel/configs/aihubmix_config.py +2 -2
  42. camel/configs/aiml_config.py +2 -2
  43. camel/configs/amd_config.py +2 -2
  44. camel/configs/anthropic_config.py +2 -2
  45. camel/configs/base_config.py +2 -2
  46. camel/configs/bedrock_config.py +2 -2
  47. camel/configs/cerebras_config.py +2 -2
  48. camel/configs/cohere_config.py +2 -2
  49. camel/configs/cometapi_config.py +2 -2
  50. camel/configs/crynux_config.py +2 -2
  51. camel/configs/deepseek_config.py +2 -2
  52. camel/configs/function_gemma_config.py +59 -0
  53. camel/configs/gemini_config.py +2 -2
  54. camel/configs/groq_config.py +2 -2
  55. camel/configs/internlm_config.py +2 -2
  56. camel/configs/litellm_config.py +2 -2
  57. camel/configs/lmstudio_config.py +2 -2
  58. camel/configs/minimax_config.py +2 -2
  59. camel/configs/mistral_config.py +2 -2
  60. camel/configs/modelscope_config.py +2 -2
  61. camel/configs/moonshot_config.py +2 -2
  62. camel/configs/nebius_config.py +2 -2
  63. camel/configs/netmind_config.py +2 -2
  64. camel/configs/novita_config.py +2 -2
  65. camel/configs/nvidia_config.py +2 -2
  66. camel/configs/ollama_config.py +2 -2
  67. camel/configs/openai_config.py +2 -2
  68. camel/configs/openrouter_config.py +2 -2
  69. camel/configs/ppio_config.py +2 -2
  70. camel/configs/qianfan_config.py +2 -2
  71. camel/configs/qwen_config.py +2 -2
  72. camel/configs/reka_config.py +2 -2
  73. camel/configs/samba_config.py +2 -2
  74. camel/configs/sglang_config.py +2 -2
  75. camel/configs/siliconflow_config.py +2 -2
  76. camel/configs/togetherai_config.py +2 -2
  77. camel/configs/vllm_config.py +2 -2
  78. camel/configs/watsonx_config.py +2 -2
  79. camel/configs/yi_config.py +2 -2
  80. camel/configs/zhipuai_config.py +2 -2
  81. camel/data_collectors/__init__.py +2 -2
  82. camel/data_collectors/alpaca_collector.py +2 -2
  83. camel/data_collectors/base.py +2 -2
  84. camel/data_collectors/sharegpt_collector.py +2 -2
  85. camel/datagen/__init__.py +2 -2
  86. camel/datagen/cot_datagen.py +2 -2
  87. camel/datagen/evol_instruct/__init__.py +2 -2
  88. camel/datagen/evol_instruct/evol_instruct.py +2 -2
  89. camel/datagen/evol_instruct/scorer.py +2 -2
  90. camel/datagen/evol_instruct/templates.py +2 -2
  91. camel/datagen/self_improving_cot.py +2 -2
  92. camel/datagen/self_instruct/__init__.py +2 -2
  93. camel/datagen/self_instruct/filter/__init__.py +2 -2
  94. camel/datagen/self_instruct/filter/filter_function.py +2 -2
  95. camel/datagen/self_instruct/filter/filter_registry.py +2 -2
  96. camel/datagen/self_instruct/filter/instruction_filter.py +2 -2
  97. camel/datagen/self_instruct/self_instruct.py +2 -2
  98. camel/datagen/self_instruct/templates.py +2 -2
  99. camel/datagen/source2synth/__init__.py +2 -2
  100. camel/datagen/source2synth/data_processor.py +2 -2
  101. camel/datagen/source2synth/models.py +2 -2
  102. camel/datagen/source2synth/user_data_processor_config.py +2 -2
  103. camel/datahubs/__init__.py +2 -2
  104. camel/datahubs/base.py +2 -2
  105. camel/datahubs/huggingface.py +2 -2
  106. camel/datahubs/models.py +2 -2
  107. camel/datasets/__init__.py +2 -2
  108. camel/datasets/base_generator.py +2 -2
  109. camel/datasets/few_shot_generator.py +2 -2
  110. camel/datasets/models.py +2 -2
  111. camel/datasets/self_instruct_generator.py +2 -2
  112. camel/datasets/static_dataset.py +2 -2
  113. camel/embeddings/__init__.py +2 -2
  114. camel/embeddings/azure_embedding.py +2 -2
  115. camel/embeddings/base.py +2 -2
  116. camel/embeddings/gemini_embedding.py +2 -2
  117. camel/embeddings/jina_embedding.py +2 -2
  118. camel/embeddings/mistral_embedding.py +2 -2
  119. camel/embeddings/openai_compatible_embedding.py +2 -2
  120. camel/embeddings/openai_embedding.py +2 -2
  121. camel/embeddings/sentence_transformers_embeddings.py +2 -2
  122. camel/embeddings/together_embedding.py +2 -2
  123. camel/embeddings/vlm_embedding.py +2 -2
  124. camel/environments/__init__.py +2 -2
  125. camel/environments/models.py +2 -2
  126. camel/environments/multi_step.py +2 -2
  127. camel/environments/rlcards_env.py +2 -2
  128. camel/environments/single_step.py +2 -2
  129. camel/environments/tic_tac_toe.py +2 -2
  130. camel/extractors/__init__.py +2 -2
  131. camel/extractors/base.py +2 -2
  132. camel/extractors/python_strategies.py +2 -2
  133. camel/generators.py +2 -2
  134. camel/human.py +2 -2
  135. camel/interpreters/__init__.py +2 -2
  136. camel/interpreters/base.py +2 -2
  137. camel/interpreters/docker_interpreter.py +2 -2
  138. camel/interpreters/e2b_interpreter.py +2 -2
  139. camel/interpreters/internal_python_interpreter.py +2 -2
  140. camel/interpreters/interpreter_error.py +2 -2
  141. camel/interpreters/ipython_interpreter.py +2 -2
  142. camel/interpreters/microsandbox_interpreter.py +2 -2
  143. camel/interpreters/subprocess_interpreter.py +2 -2
  144. camel/loaders/__init__.py +2 -2
  145. camel/loaders/apify_reader.py +2 -2
  146. camel/loaders/base_io.py +2 -2
  147. camel/loaders/base_loader.py +2 -2
  148. camel/loaders/chunkr_reader.py +2 -2
  149. camel/loaders/crawl4ai_reader.py +2 -2
  150. camel/loaders/firecrawl_reader.py +2 -2
  151. camel/loaders/jina_url_reader.py +2 -2
  152. camel/loaders/markitdown.py +2 -2
  153. camel/loaders/mineru_extractor.py +2 -2
  154. camel/loaders/mistral_reader.py +2 -2
  155. camel/loaders/scrapegraph_reader.py +2 -2
  156. camel/loaders/unstructured_io.py +2 -2
  157. camel/logger.py +2 -2
  158. camel/memories/__init__.py +2 -2
  159. camel/memories/agent_memories.py +2 -2
  160. camel/memories/base.py +2 -2
  161. camel/memories/blocks/__init__.py +2 -2
  162. camel/memories/blocks/chat_history_block.py +2 -2
  163. camel/memories/blocks/vectordb_block.py +2 -2
  164. camel/memories/context_creators/__init__.py +2 -2
  165. camel/memories/context_creators/score_based.py +89 -2
  166. camel/memories/records.py +2 -2
  167. camel/messages/__init__.py +2 -2
  168. camel/messages/base.py +2 -2
  169. camel/messages/conversion/__init__.py +2 -2
  170. camel/messages/conversion/alpaca.py +2 -2
  171. camel/messages/conversion/conversation_models.py +2 -2
  172. camel/messages/conversion/sharegpt/__init__.py +2 -2
  173. camel/messages/conversion/sharegpt/function_call_formatter.py +2 -2
  174. camel/messages/conversion/sharegpt/hermes/__init__.py +2 -2
  175. camel/messages/conversion/sharegpt/hermes/hermes_function_formatter.py +2 -2
  176. camel/messages/func_message.py +2 -2
  177. camel/models/__init__.py +4 -2
  178. camel/models/_utils.py +2 -2
  179. camel/models/aihubmix_model.py +2 -2
  180. camel/models/aiml_model.py +2 -2
  181. camel/models/amd_model.py +2 -2
  182. camel/models/anthropic_model.py +2 -2
  183. camel/models/aws_bedrock_model.py +2 -2
  184. camel/models/azure_openai_model.py +4 -28
  185. camel/models/base_audio_model.py +2 -2
  186. camel/models/base_model.py +192 -14
  187. camel/models/cerebras_model.py +2 -2
  188. camel/models/cohere_model.py +4 -30
  189. camel/models/cometapi_model.py +2 -2
  190. camel/models/crynux_model.py +2 -2
  191. camel/models/deepseek_model.py +4 -28
  192. camel/models/fish_audio_model.py +2 -2
  193. camel/models/function_gemma_model.py +889 -0
  194. camel/models/gemini_model.py +4 -28
  195. camel/models/groq_model.py +2 -2
  196. camel/models/internlm_model.py +2 -2
  197. camel/models/litellm_model.py +3 -17
  198. camel/models/lmstudio_model.py +2 -2
  199. camel/models/minimax_model.py +2 -2
  200. camel/models/mistral_model.py +4 -30
  201. camel/models/model_factory.py +4 -2
  202. camel/models/model_manager.py +2 -2
  203. camel/models/modelscope_model.py +2 -2
  204. camel/models/moonshot_model.py +3 -15
  205. camel/models/nebius_model.py +2 -2
  206. camel/models/nemotron_model.py +2 -2
  207. camel/models/netmind_model.py +2 -2
  208. camel/models/novita_model.py +2 -2
  209. camel/models/nvidia_model.py +2 -2
  210. camel/models/ollama_model.py +2 -2
  211. camel/models/openai_audio_models.py +2 -2
  212. camel/models/openai_compatible_model.py +4 -28
  213. camel/models/openai_model.py +4 -43
  214. camel/models/openrouter_model.py +2 -2
  215. camel/models/ppio_model.py +2 -2
  216. camel/models/qianfan_model.py +2 -2
  217. camel/models/qwen_model.py +2 -2
  218. camel/models/reka_model.py +4 -30
  219. camel/models/reward/__init__.py +2 -2
  220. camel/models/reward/base_reward_model.py +2 -2
  221. camel/models/reward/evaluator.py +2 -2
  222. camel/models/reward/nemotron_model.py +2 -2
  223. camel/models/reward/skywork_model.py +2 -2
  224. camel/models/samba_model.py +4 -30
  225. camel/models/sglang_model.py +4 -30
  226. camel/models/siliconflow_model.py +2 -2
  227. camel/models/stub_model.py +2 -2
  228. camel/models/togetherai_model.py +2 -2
  229. camel/models/vllm_model.py +2 -2
  230. camel/models/volcano_model.py +147 -4
  231. camel/models/watsonx_model.py +4 -30
  232. camel/models/yi_model.py +2 -2
  233. camel/models/zhipuai_model.py +2 -2
  234. camel/parsers/__init__.py +2 -2
  235. camel/parsers/mcp_tool_call_parser.py +2 -2
  236. camel/personas/__init__.py +2 -2
  237. camel/personas/persona.py +2 -2
  238. camel/personas/persona_hub.py +2 -2
  239. camel/prompts/__init__.py +2 -2
  240. camel/prompts/ai_society.py +2 -2
  241. camel/prompts/base.py +2 -2
  242. camel/prompts/code.py +2 -2
  243. camel/prompts/evaluation.py +2 -2
  244. camel/prompts/generate_text_embedding_data.py +2 -2
  245. camel/prompts/image_craft.py +2 -2
  246. camel/prompts/misalignment.py +2 -2
  247. camel/prompts/multi_condition_image_craft.py +2 -2
  248. camel/prompts/object_recognition.py +2 -2
  249. camel/prompts/persona_hub.py +2 -2
  250. camel/prompts/prompt_templates.py +2 -2
  251. camel/prompts/role_description_prompt_template.py +2 -2
  252. camel/prompts/solution_extraction.py +2 -2
  253. camel/prompts/task_prompt_template.py +2 -2
  254. camel/prompts/translation.py +2 -2
  255. camel/prompts/video_description_prompt.py +2 -2
  256. camel/responses/__init__.py +2 -2
  257. camel/responses/agent_responses.py +2 -2
  258. camel/retrievers/__init__.py +2 -2
  259. camel/retrievers/auto_retriever.py +2 -2
  260. camel/retrievers/base.py +2 -2
  261. camel/retrievers/bm25_retriever.py +2 -2
  262. camel/retrievers/cohere_rerank_retriever.py +2 -2
  263. camel/retrievers/hybrid_retrival.py +2 -2
  264. camel/retrievers/vector_retriever.py +2 -2
  265. camel/runtimes/__init__.py +2 -2
  266. camel/runtimes/api.py +2 -2
  267. camel/runtimes/base.py +2 -2
  268. camel/runtimes/configs.py +2 -2
  269. camel/runtimes/daytona_runtime.py +2 -2
  270. camel/runtimes/docker_runtime.py +2 -2
  271. camel/runtimes/llm_guard_runtime.py +2 -2
  272. camel/runtimes/remote_http_runtime.py +2 -2
  273. camel/runtimes/ubuntu_docker_runtime.py +2 -2
  274. camel/runtimes/utils/__init__.py +2 -2
  275. camel/runtimes/utils/function_risk_toolkit.py +2 -2
  276. camel/runtimes/utils/ignore_risk_toolkit.py +2 -2
  277. camel/schemas/__init__.py +2 -2
  278. camel/schemas/base.py +2 -2
  279. camel/schemas/openai_converter.py +2 -2
  280. camel/schemas/outlines_converter.py +2 -2
  281. camel/services/agent_openapi_server.py +2 -2
  282. camel/societies/__init__.py +2 -2
  283. camel/societies/babyagi_playing.py +2 -2
  284. camel/societies/role_playing.py +2 -2
  285. camel/societies/workforce/__init__.py +2 -2
  286. camel/societies/workforce/base.py +2 -2
  287. camel/societies/workforce/events.py +4 -2
  288. camel/societies/workforce/prompts.py +9 -8
  289. camel/societies/workforce/role_playing_worker.py +2 -2
  290. camel/societies/workforce/single_agent_worker.py +2 -2
  291. camel/societies/workforce/structured_output_handler.py +2 -2
  292. camel/societies/workforce/task_channel.py +2 -2
  293. camel/societies/workforce/utils.py +2 -2
  294. camel/societies/workforce/worker.py +2 -2
  295. camel/societies/workforce/workflow_memory_manager.py +2 -2
  296. camel/societies/workforce/workforce.py +132 -71
  297. camel/societies/workforce/workforce_callback.py +2 -2
  298. camel/societies/workforce/workforce_logger.py +2 -2
  299. camel/societies/workforce/workforce_metrics.py +2 -2
  300. camel/storages/__init__.py +2 -2
  301. camel/storages/graph_storages/__init__.py +2 -2
  302. camel/storages/graph_storages/base.py +2 -2
  303. camel/storages/graph_storages/graph_element.py +2 -2
  304. camel/storages/graph_storages/nebula_graph.py +2 -2
  305. camel/storages/graph_storages/neo4j_graph.py +2 -2
  306. camel/storages/key_value_storages/__init__.py +2 -2
  307. camel/storages/key_value_storages/base.py +2 -2
  308. camel/storages/key_value_storages/in_memory.py +2 -2
  309. camel/storages/key_value_storages/json.py +2 -2
  310. camel/storages/key_value_storages/mem0_cloud.py +2 -2
  311. camel/storages/key_value_storages/redis.py +2 -2
  312. camel/storages/object_storages/__init__.py +2 -2
  313. camel/storages/object_storages/amazon_s3.py +2 -2
  314. camel/storages/object_storages/azure_blob.py +2 -2
  315. camel/storages/object_storages/base.py +2 -2
  316. camel/storages/object_storages/google_cloud.py +2 -2
  317. camel/storages/vectordb_storages/__init__.py +2 -2
  318. camel/storages/vectordb_storages/base.py +2 -2
  319. camel/storages/vectordb_storages/chroma.py +2 -2
  320. camel/storages/vectordb_storages/faiss.py +2 -2
  321. camel/storages/vectordb_storages/milvus.py +2 -2
  322. camel/storages/vectordb_storages/oceanbase.py +2 -2
  323. camel/storages/vectordb_storages/pgvector.py +2 -2
  324. camel/storages/vectordb_storages/qdrant.py +2 -2
  325. camel/storages/vectordb_storages/surreal.py +2 -2
  326. camel/storages/vectordb_storages/tidb.py +2 -2
  327. camel/storages/vectordb_storages/weaviate.py +2 -2
  328. camel/tasks/__init__.py +2 -2
  329. camel/tasks/task.py +2 -2
  330. camel/tasks/task_prompt.py +2 -2
  331. camel/terminators/__init__.py +2 -2
  332. camel/terminators/base.py +2 -2
  333. camel/terminators/response_terminator.py +2 -2
  334. camel/terminators/token_limit_terminator.py +2 -2
  335. camel/toolkits/__init__.py +6 -3
  336. camel/toolkits/aci_toolkit.py +2 -2
  337. camel/toolkits/arxiv_toolkit.py +2 -2
  338. camel/toolkits/ask_news_toolkit.py +2 -2
  339. camel/toolkits/async_browser_toolkit.py +2 -2
  340. camel/toolkits/audio_analysis_toolkit.py +2 -2
  341. camel/toolkits/base.py +47 -5
  342. camel/toolkits/bohrium_toolkit.py +2 -2
  343. camel/toolkits/browser_toolkit.py +2 -2
  344. camel/toolkits/browser_toolkit_commons.py +2 -2
  345. camel/toolkits/code_execution.py +2 -2
  346. camel/toolkits/context_summarizer_toolkit.py +2 -2
  347. camel/toolkits/craw4ai_toolkit.py +2 -2
  348. camel/toolkits/dappier_toolkit.py +2 -2
  349. camel/toolkits/data_commons_toolkit.py +2 -2
  350. camel/toolkits/dingtalk.py +2 -2
  351. camel/toolkits/earth_science_toolkit.py +2 -2
  352. camel/toolkits/edgeone_pages_mcp_toolkit.py +2 -2
  353. camel/toolkits/excel_toolkit.py +2 -2
  354. camel/toolkits/file_toolkit.py +2 -2
  355. camel/toolkits/function_tool.py +95 -25
  356. camel/toolkits/github_toolkit.py +2 -2
  357. camel/toolkits/gmail_toolkit.py +2 -2
  358. camel/toolkits/google_calendar_toolkit.py +2 -2
  359. camel/toolkits/google_drive_mcp_toolkit.py +2 -2
  360. camel/toolkits/google_maps_toolkit.py +2 -2
  361. camel/toolkits/google_scholar_toolkit.py +2 -2
  362. camel/toolkits/human_toolkit.py +2 -2
  363. camel/toolkits/hybrid_browser_toolkit/__init__.py +2 -2
  364. camel/toolkits/hybrid_browser_toolkit/config_loader.py +2 -2
  365. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +2 -2
  366. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +89 -104
  367. camel/toolkits/hybrid_browser_toolkit/installer.py +2 -2
  368. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +25 -14
  369. camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +6 -0
  370. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +2 -2
  371. camel/toolkits/hybrid_browser_toolkit_py/__init__.py +2 -2
  372. camel/toolkits/hybrid_browser_toolkit_py/actions.py +2 -2
  373. camel/toolkits/hybrid_browser_toolkit_py/agent.py +2 -2
  374. camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +2 -2
  375. camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +2 -2
  376. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +2 -2
  377. camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +2 -2
  378. camel/toolkits/image_analysis_toolkit.py +2 -2
  379. camel/toolkits/image_generation_toolkit.py +2 -2
  380. camel/toolkits/jina_reranker_toolkit.py +2 -2
  381. camel/toolkits/klavis_toolkit.py +2 -2
  382. camel/toolkits/linkedin_toolkit.py +2 -2
  383. camel/toolkits/markitdown_toolkit.py +2 -2
  384. camel/toolkits/math_toolkit.py +2 -2
  385. camel/toolkits/mcp_toolkit.py +2 -2
  386. camel/toolkits/memory_toolkit.py +2 -2
  387. camel/toolkits/meshy_toolkit.py +2 -2
  388. camel/toolkits/message_agent_toolkit.py +2 -2
  389. camel/toolkits/message_integration.py +6 -2
  390. camel/toolkits/microsoft_outlook_mail_toolkit.py +1885 -0
  391. camel/toolkits/mineru_toolkit.py +2 -2
  392. camel/toolkits/minimax_mcp_toolkit.py +2 -2
  393. camel/toolkits/networkx_toolkit.py +2 -2
  394. camel/toolkits/note_taking_toolkit.py +2 -2
  395. camel/toolkits/notion_mcp_toolkit.py +2 -2
  396. camel/toolkits/notion_toolkit.py +2 -2
  397. camel/toolkits/open_api_specs/biztoc/__init__.py +2 -2
  398. camel/toolkits/open_api_specs/coursera/__init__.py +2 -2
  399. camel/toolkits/open_api_specs/create_qr_code/__init__.py +2 -2
  400. camel/toolkits/open_api_specs/klarna/__init__.py +2 -2
  401. camel/toolkits/open_api_specs/nasa_apod/__init__.py +2 -2
  402. camel/toolkits/open_api_specs/outschool/__init__.py +2 -2
  403. camel/toolkits/open_api_specs/outschool/paths/__init__.py +2 -2
  404. camel/toolkits/open_api_specs/outschool/paths/get_classes.py +2 -2
  405. camel/toolkits/open_api_specs/outschool/paths/search_teachers.py +2 -2
  406. camel/toolkits/open_api_specs/security_config.py +2 -2
  407. camel/toolkits/open_api_specs/speak/__init__.py +2 -2
  408. camel/toolkits/open_api_specs/web_scraper/__init__.py +2 -2
  409. camel/toolkits/open_api_specs/web_scraper/paths/__init__.py +2 -2
  410. camel/toolkits/open_api_specs/web_scraper/paths/scraper.py +2 -2
  411. camel/toolkits/open_api_toolkit.py +2 -2
  412. camel/toolkits/openbb_toolkit.py +2 -2
  413. camel/toolkits/origene_mcp_toolkit.py +2 -2
  414. camel/toolkits/playwright_mcp_toolkit.py +2 -2
  415. camel/toolkits/pptx_toolkit.py +2 -2
  416. camel/toolkits/pubmed_toolkit.py +2 -2
  417. camel/toolkits/pulse_mcp_search_toolkit.py +2 -2
  418. camel/toolkits/pyautogui_toolkit.py +2 -2
  419. camel/toolkits/reddit_toolkit.py +2 -2
  420. camel/toolkits/resend_toolkit.py +2 -2
  421. camel/toolkits/retrieval_toolkit.py +2 -2
  422. camel/toolkits/screenshot_toolkit.py +2 -2
  423. camel/toolkits/search_toolkit.py +70 -13
  424. camel/toolkits/searxng_toolkit.py +2 -2
  425. camel/toolkits/semantic_scholar_toolkit.py +2 -2
  426. camel/toolkits/slack_toolkit.py +2 -2
  427. camel/toolkits/sql_toolkit.py +2 -2
  428. camel/toolkits/stripe_toolkit.py +2 -2
  429. camel/toolkits/sympy_toolkit.py +2 -2
  430. camel/toolkits/task_planning_toolkit.py +2 -2
  431. camel/toolkits/terminal_toolkit/__init__.py +2 -2
  432. camel/toolkits/terminal_toolkit/terminal_toolkit.py +323 -112
  433. camel/toolkits/terminal_toolkit/utils.py +179 -52
  434. camel/toolkits/thinking_toolkit.py +2 -2
  435. camel/toolkits/twitter_toolkit.py +2 -2
  436. camel/toolkits/vertex_ai_veo_toolkit.py +2 -2
  437. camel/toolkits/video_analysis_toolkit.py +2 -2
  438. camel/toolkits/video_download_toolkit.py +2 -2
  439. camel/toolkits/weather_toolkit.py +2 -2
  440. camel/toolkits/web_deploy_toolkit.py +2 -2
  441. camel/toolkits/wechat_official_toolkit.py +2 -2
  442. camel/toolkits/whatsapp_toolkit.py +2 -2
  443. camel/toolkits/wolfram_alpha_toolkit.py +2 -2
  444. camel/toolkits/zapier_toolkit.py +2 -2
  445. camel/types/__init__.py +2 -2
  446. camel/types/agents/__init__.py +2 -2
  447. camel/types/agents/tool_calling_record.py +2 -2
  448. camel/types/enums.py +5 -4
  449. camel/types/mcp_registries.py +2 -2
  450. camel/types/openai_types.py +2 -2
  451. camel/types/unified_model_type.py +10 -6
  452. camel/utils/__init__.py +5 -2
  453. camel/utils/agent_context.py +41 -0
  454. camel/utils/async_func.py +2 -2
  455. camel/utils/chunker/__init__.py +2 -2
  456. camel/utils/chunker/base.py +2 -2
  457. camel/utils/chunker/code_chunker.py +2 -2
  458. camel/utils/chunker/uio_chunker.py +2 -2
  459. camel/utils/commons.py +2 -2
  460. camel/utils/constants.py +2 -2
  461. camel/utils/context_utils.py +2 -2
  462. camel/utils/deduplication.py +2 -2
  463. camel/utils/filename.py +2 -2
  464. camel/utils/langfuse.py +18 -10
  465. camel/utils/mcp.py +2 -2
  466. camel/utils/mcp_client.py +2 -2
  467. camel/utils/message_summarizer.py +2 -2
  468. camel/utils/response_format.py +2 -2
  469. camel/utils/token_counting.py +2 -2
  470. camel/utils/tool_result.py +2 -2
  471. camel/verifiers/__init__.py +2 -2
  472. camel/verifiers/base.py +2 -2
  473. camel/verifiers/math_verifier.py +2 -2
  474. camel/verifiers/models.py +2 -2
  475. camel/verifiers/physics_verifier.py +2 -2
  476. camel/verifiers/python_verifier.py +2 -2
  477. {camel_ai-0.2.82.dist-info → camel_ai-0.2.83a6.dist-info}/METADATA +34 -29
  478. camel_ai-0.2.83a6.dist-info/RECORD +511 -0
  479. camel_ai-0.2.82.dist-info/RECORD +0 -507
  480. {camel_ai-0.2.82.dist-info → camel_ai-0.2.83a6.dist-info}/WHEEL +0 -0
  481. {camel_ai-0.2.82.dist-info → camel_ai-0.2.83a6.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
1
- # ========= Copyright 2023-2025 @ CAMEL-AI.org. All Rights Reserved. =========
1
+ # ========= Copyright 2023-2026 @ CAMEL-AI.org. All Rights Reserved. =========
2
2
  # Licensed under the Apache License, Version 2.0 (the "License");
3
3
  # you may not use this file except in compliance with the License.
4
4
  # You may obtain a copy of the License at
@@ -10,7 +10,7 @@
10
10
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
11
  # See the License for the specific language governing permissions and
12
12
  # limitations under the License.
13
- # ========= Copyright 2023-2025 @ CAMEL-AI.org. All Rights Reserved. =========
13
+ # ========= Copyright 2023-2026 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
  import os
15
15
  import platform
16
16
  import select
@@ -19,10 +19,11 @@ import subprocess
19
19
  import sys
20
20
  import threading
21
21
  import time
22
- from queue import Empty, Queue
22
+ from queue import Empty, Full, Queue
23
23
  from typing import Any, Dict, List, Optional
24
24
 
25
25
  from camel.logger import get_logger
26
+ from camel.toolkits import manual_timeout
26
27
  from camel.toolkits.base import BaseToolkit
27
28
  from camel.toolkits.function_tool import FunctionTool
28
29
  from camel.toolkits.terminal_toolkit.utils import (
@@ -118,6 +119,8 @@ class TerminalToolkit(BaseToolkit):
118
119
  # Thread-safe guard for concurrent access to
119
120
  # shell_sessions and session state
120
121
  self._session_lock = threading.RLock()
122
+ # Condition variable for efficient waiting on new output
123
+ self._output_condition = threading.Condition(self._session_lock)
121
124
 
122
125
  # Initialize docker_workdir with proper type
123
126
  self.docker_workdir: Optional[str] = None
@@ -190,10 +193,8 @@ class TerminalToolkit(BaseToolkit):
190
193
  "provided when using Docker backend."
191
194
  )
192
195
  try:
193
- # APIClient is used for operations that need a timeout,
194
- # like exec_start
195
196
  self.docker_api_client = docker.APIClient(
196
- base_url='unix://var/run/docker.sock', timeout=self.timeout
197
+ base_url='unix://var/run/docker.sock'
197
198
  )
198
199
  self.docker_client = docker.from_env()
199
200
  try:
@@ -207,7 +208,7 @@ class TerminalToolkit(BaseToolkit):
207
208
  )
208
209
  except NotFound:
209
210
  raise RuntimeError(
210
- f"Container '{docker_container_name}' not found. "
211
+ f"Container '{docker_container_name}' not found."
211
212
  )
212
213
 
213
214
  # Ensure the working directory exists inside the container
@@ -224,10 +225,6 @@ class TerminalToolkit(BaseToolkit):
224
225
  f"[Docker] Failed to ensure workdir "
225
226
  f"'{self.docker_workdir}': {e}"
226
227
  )
227
- except NotFound:
228
- raise RuntimeError(
229
- f"Docker container '{docker_container_name}' not found."
230
- )
231
228
  except APIError as e:
232
229
  raise RuntimeError(f"Failed to connect to Docker daemon: {e}")
233
230
 
@@ -398,34 +395,13 @@ class TerminalToolkit(BaseToolkit):
398
395
  "using system Python"
399
396
  )
400
397
 
401
- def _adapt_command_for_environment(self, command: str) -> str:
402
- r"""Adapt command to use virtual environment if available."""
403
- # Only adapt for local backend
404
- if self.use_docker_backend:
405
- return command
406
-
407
- # Check if we have any virtual environment (cloned or initial)
408
- env_path = None
398
+ def _get_venv_path(self) -> Optional[str]:
399
+ r"""Get the virtual environment path if available."""
409
400
  if self.cloned_env_path and os.path.exists(self.cloned_env_path):
410
- env_path = self.cloned_env_path
401
+ return self.cloned_env_path
411
402
  elif self.initial_env_path and os.path.exists(self.initial_env_path):
412
- env_path = self.initial_env_path
413
-
414
- if not env_path:
415
- return command
416
-
417
- # Check if command starts with python or pip
418
- command_lower = command.strip().lower()
419
- if command_lower.startswith('python'):
420
- # Replace 'python' with the virtual environment python
421
- return command.replace('python', f'"{self.python_executable}"', 1)
422
- elif command_lower.startswith('pip'):
423
- # Replace 'pip' with python -m pip from virtual environment
424
- return command.replace(
425
- 'pip', f'"{self.python_executable}" -m pip', 1
426
- )
427
-
428
- return command
403
+ return self.initial_env_path
404
+ return None
429
405
 
430
406
  def _write_to_log(self, log_file: str, content: str) -> None:
431
407
  r"""Write content to log file with optional ANSI stripping.
@@ -455,6 +431,24 @@ class TerminalToolkit(BaseToolkit):
455
431
  session = self.shell_sessions[session_id]
456
432
 
457
433
  def reader():
434
+ def safe_put(data: str) -> None:
435
+ """Put data to queue, dropping if full to prevent blocking."""
436
+ try:
437
+ session["output_stream"].put_nowait(data)
438
+ except Full:
439
+ # Queue is full, log warning and continue
440
+ # Data is still written to log file, so not lost
441
+ logger.warning(
442
+ f"[SESSION {session_id}] Output queue full, "
443
+ f"dropping data (still logged to file)"
444
+ )
445
+ return
446
+ # Notify waiters that new output is available
447
+ # Done outside try-except to avoid catching unrelated
448
+ # exceptions
449
+ with self._output_condition:
450
+ self._output_condition.notify_all()
451
+
458
452
  try:
459
453
  if session["backend"] == "local":
460
454
  # For local processes, read line by line from stdout
@@ -462,8 +456,8 @@ class TerminalToolkit(BaseToolkit):
462
456
  for line in iter(
463
457
  session["process"].stdout.readline, ''
464
458
  ):
465
- session["output_stream"].put(line)
466
459
  self._write_to_log(session["log_file"], line)
460
+ safe_put(line)
467
461
  finally:
468
462
  session["process"].stdout.close()
469
463
  elif session["backend"] == "docker":
@@ -485,10 +479,10 @@ class TerminalToolkit(BaseToolkit):
485
479
  decoded_data = data.decode(
486
480
  'utf-8', errors='ignore'
487
481
  )
488
- session["output_stream"].put(decoded_data)
489
482
  self._write_to_log(
490
483
  session["log_file"], decoded_data
491
484
  )
485
+ safe_put(decoded_data)
492
486
  # Check if the process is still running
493
487
  if not self.docker_api_client.exec_inspect(
494
488
  session["exec_id"]
@@ -508,9 +502,11 @@ class TerminalToolkit(BaseToolkit):
508
502
  )
509
503
  finally:
510
504
  try:
511
- with self._session_lock:
505
+ with self._output_condition:
512
506
  if session_id in self.shell_sessions:
513
507
  self.shell_sessions[session_id]["running"] = False
508
+ # Notify waiters that session has terminated
509
+ self._output_condition.notify_all()
514
510
  except Exception:
515
511
  pass
516
512
 
@@ -521,7 +517,6 @@ class TerminalToolkit(BaseToolkit):
521
517
  self,
522
518
  id: str,
523
519
  idle_duration: float = 0.5,
524
- check_interval: float = 0.1,
525
520
  max_wait: float = 5.0,
526
521
  ) -> str:
527
522
  r"""Collects output from a session until it's idle or a max wait time
@@ -531,8 +526,6 @@ class TerminalToolkit(BaseToolkit):
531
526
  id (str): The session ID.
532
527
  idle_duration (float): How long the stream must be empty to be
533
528
  considered idle.(default: 0.5)
534
- check_interval (float): The time to sleep between checks.
535
- (default: 0.1)
536
529
  max_wait (float): The maximum total time to wait for the process
537
530
  to go idle. (default: 5.0)
538
531
 
@@ -544,11 +537,15 @@ class TerminalToolkit(BaseToolkit):
544
537
  if id not in self.shell_sessions:
545
538
  return f"Error: No session found with ID '{id}'."
546
539
 
547
- output_parts = []
548
- idle_time = 0.0
549
- start_time = time.time()
540
+ output_parts: List[str] = []
541
+ last_output_time = time.time()
542
+ start_time = last_output_time
543
+
544
+ while True:
545
+ elapsed = time.time() - start_time
546
+ if elapsed >= max_wait:
547
+ break
550
548
 
551
- while time.time() - start_time < max_wait:
552
549
  new_output = self.shell_view(id)
553
550
 
554
551
  # Check for terminal state messages from shell_view
@@ -565,20 +562,34 @@ class TerminalToolkit(BaseToolkit):
565
562
  if new_output.startswith("Error: No session found"):
566
563
  return new_output
567
564
 
568
- if new_output:
565
+ # Check if this is actual output or just the idle message
566
+ if new_output and not new_output.startswith("[No new output]"):
569
567
  output_parts.append(new_output)
570
- idle_time = 0.0 # Reset idle timer
568
+ last_output_time = time.time() # Reset idle timer
571
569
  else:
572
- idle_time += check_interval
570
+ # No new output, check if we've been idle long enough
571
+ idle_time = time.time() - last_output_time
573
572
  if idle_time >= idle_duration:
574
573
  # Process is idle, success
575
574
  return "".join(output_parts)
576
- time.sleep(check_interval)
575
+
576
+ # Calculate remaining time for idle and max_wait
577
+ time_until_idle = idle_duration - (time.time() - last_output_time)
578
+ time_until_max = max_wait - (time.time() - start_time)
579
+ # Wait for the shorter of: idle timeout, max timeout, or a
580
+ # reasonable check interval
581
+ wait_time = max(0.0, min(time_until_idle, time_until_max))
582
+
583
+ if wait_time > 0:
584
+ # Use condition variable to wait efficiently
585
+ # Wake up when new output arrives or timeout expires
586
+ with self._output_condition:
587
+ self._output_condition.wait(timeout=wait_time)
577
588
 
578
589
  # If we exit the loop, it means max_wait was reached.
579
590
  # Check one last time for any final output.
580
591
  final_output = self.shell_view(id)
581
- if final_output:
592
+ if final_output and not final_output.startswith("[No new output]"):
582
593
  output_parts.append(final_output)
583
594
 
584
595
  warning_message = (
@@ -588,7 +599,13 @@ class TerminalToolkit(BaseToolkit):
588
599
  )
589
600
  return "".join(output_parts) + warning_message
590
601
 
591
- def shell_exec(self, id: str, command: str, block: bool = True) -> str:
602
+ def shell_exec(
603
+ self,
604
+ id: str,
605
+ command: str,
606
+ block: bool = True,
607
+ timeout: float = 20.0,
608
+ ) -> str:
592
609
  r"""Executes a shell command in blocking or non-blocking mode.
593
610
 
594
611
  Args:
@@ -598,9 +615,17 @@ class TerminalToolkit(BaseToolkit):
598
615
  block (bool, optional): Determines the execution mode. Defaults to
599
616
  True. If `True` (blocking mode), the function waits for the
600
617
  command to complete and returns the full output. Use this for
601
- most commands . If `False` (non-blocking mode), the function
618
+ most commands. If `False` (non-blocking mode), the function
602
619
  starts the command in the background. Use this only for
603
620
  interactive sessions or long-running tasks, or servers.
621
+ timeout (float, optional): The maximum time in seconds to
622
+ wait for the command to complete in blocking mode. If the
623
+ command does not complete within the timeout, it will be
624
+ converted to a tracked background session (process keeps
625
+ running without restart). You can then use `shell_view(id)`
626
+ to check output, or `shell_kill_process(id)` to terminate it.
627
+ This parameter is ignored in non-blocking mode.
628
+ (default: :obj:`20`)
604
629
 
605
630
  Returns:
606
631
  str: The output of the command execution, which varies by mode.
@@ -620,11 +645,21 @@ class TerminalToolkit(BaseToolkit):
620
645
 
621
646
  if self.use_docker_backend:
622
647
  # For Docker, we always run commands in a shell
623
- # to support complex commands
624
- command = f'bash -c "{command}"'
648
+ # to support complex commands.
649
+ # Use shlex.quote to properly escape the command string.
650
+ command = f'bash -c {shlex.quote(command)}'
625
651
  else:
626
- # For local execution, check if we need to use cloned environment
627
- command = self._adapt_command_for_environment(command)
652
+ # For local execution, activate virtual environment if available
653
+ env_path = self._get_venv_path()
654
+ if env_path:
655
+ if self.os_type == 'Windows':
656
+ activate = os.path.join(
657
+ env_path, "Scripts", "activate.bat"
658
+ )
659
+ command = f'call "{activate}" && {command}'
660
+ else:
661
+ activate = os.path.join(env_path, "bin", "activate")
662
+ command = f'. "{activate}" && {command}'
628
663
 
629
664
  session_id = id
630
665
 
@@ -635,58 +670,157 @@ class TerminalToolkit(BaseToolkit):
635
670
  f"{time.ctime()} ---\n> {command}\n"
636
671
  )
637
672
  output = ""
673
+
638
674
  try:
639
675
  if not self.use_docker_backend:
640
- # LOCAL BLOCKING
641
- result = subprocess.run(
676
+ env_vars = os.environ.copy()
677
+ env_vars["PYTHONUNBUFFERED"] = "1"
678
+ proc = subprocess.Popen(
642
679
  command,
643
- capture_output=True,
644
- text=True,
680
+ stdout=subprocess.PIPE,
681
+ stderr=subprocess.STDOUT,
682
+ stdin=subprocess.PIPE,
645
683
  shell=True,
646
- timeout=self.timeout,
684
+ text=True,
647
685
  cwd=self.working_dir,
648
686
  encoding="utf-8",
687
+ env=env_vars,
649
688
  )
650
- stdout = result.stdout or ""
651
- stderr = result.stderr or ""
652
- output = stdout + (
653
- f"\nSTDERR:\n{stderr}" if stderr else ""
654
- )
689
+ try:
690
+ stdout, _ = proc.communicate(timeout=timeout)
691
+ output = stdout or ""
692
+ except subprocess.TimeoutExpired as e:
693
+ if e.stdout:
694
+ partial_output = (
695
+ e.stdout.decode("utf-8", errors="ignore")
696
+ if isinstance(e.stdout, bytes)
697
+ else e.stdout
698
+ )
699
+ else:
700
+ partial_output = ""
701
+
702
+ session_log_file = os.path.join(
703
+ self.log_dir, f"session_{session_id}.log"
704
+ )
705
+ self._write_to_log(
706
+ session_log_file,
707
+ f"--- Blocking command timed out, converted to "
708
+ f"session at {time.ctime()} ---\n> {command}\n",
709
+ )
710
+
711
+ # Pre-populate output queue with partial output
712
+ output_queue: Queue = Queue(maxsize=10000)
713
+ if partial_output:
714
+ output_queue.put(partial_output)
715
+
716
+ with self._session_lock:
717
+ self.shell_sessions[session_id] = {
718
+ "id": session_id,
719
+ "process": proc,
720
+ "output_stream": output_queue,
721
+ "command_history": [command],
722
+ "running": True,
723
+ "log_file": session_log_file,
724
+ "backend": "local",
725
+ "timeout_converted": True,
726
+ }
727
+
728
+ # Start reader thread to capture ongoing output
729
+ self._start_output_reader_thread(session_id)
730
+
731
+ self._write_to_log(
732
+ self.blocking_log_file, log_entry + "\n"
733
+ )
734
+ return (
735
+ f"Command did not complete within {timeout} "
736
+ f"seconds. Process continues in background as "
737
+ f"session '{session_id}'.\n\n"
738
+ f"You can use:\n"
739
+ f" - shell_view('{session_id}') - get output\n"
740
+ f" - shell_kill_process('{session_id}') - "
741
+ f"terminate"
742
+ )
655
743
  else:
656
- # DOCKER BLOCKING
744
+ # DOCKER BLOCKING with timeout
657
745
  assert (
658
746
  self.docker_workdir is not None
659
747
  ) # Docker backend always has workdir
660
748
  exec_instance = self.docker_api_client.exec_create(
661
749
  self.container.id, command, workdir=self.docker_workdir
662
750
  )
663
- exec_output = self.docker_api_client.exec_start(
664
- exec_instance['Id']
751
+ exec_id = exec_instance['Id']
752
+
753
+ # Use thread to implement timeout for docker exec
754
+ result_container: Dict[str, Any] = {}
755
+
756
+ def run_exec():
757
+ try:
758
+ result_container['output'] = (
759
+ self.docker_api_client.exec_start(exec_id)
760
+ )
761
+ except Exception as e:
762
+ result_container['error'] = e
763
+
764
+ exec_thread = threading.Thread(target=run_exec)
765
+ exec_thread.start()
766
+ exec_thread.join(timeout=timeout)
767
+
768
+ if exec_thread.is_alive():
769
+ # Timeout occurred - convert to tracked session
770
+ # so agent can monitor or kill the process.
771
+ # The exec_thread continues running in background and
772
+ # will eventually write output to result_container.
773
+ session_log_file = os.path.join(
774
+ self.log_dir, f"session_{session_id}.log"
775
+ )
776
+ self._write_to_log(
777
+ session_log_file,
778
+ f"--- Blocking command timed out, converted to "
779
+ f"session at {time.ctime()} ---\n> {command}\n",
780
+ )
781
+
782
+ with self._session_lock:
783
+ self.shell_sessions[session_id] = {
784
+ "id": session_id,
785
+ "process": None, # No socket for blocking exec
786
+ "exec_id": exec_id,
787
+ "exec_thread": exec_thread, # Thread reference
788
+ "result_container": result_container, # Shared
789
+ "output_stream": Queue(maxsize=10000),
790
+ "command_history": [command],
791
+ "running": True,
792
+ "log_file": session_log_file,
793
+ "backend": "docker",
794
+ "timeout_converted": True, # Mark as converted
795
+ }
796
+
797
+ self._write_to_log(
798
+ self.blocking_log_file, log_entry + "\n"
799
+ )
800
+ return (
801
+ f"Command did not complete within {timeout} "
802
+ f"seconds. Process continues in background as "
803
+ f"session '{session_id}'.\n\n"
804
+ f"You can use:\n"
805
+ f" - shell_view('{session_id}') - get output\n"
806
+ f" - shell_kill_process('{session_id}') - "
807
+ f"terminate"
808
+ )
809
+
810
+ if 'error' in result_container:
811
+ raise result_container['error']
812
+
813
+ output = result_container['output'].decode(
814
+ 'utf-8', errors='ignore'
665
815
  )
666
- output = exec_output.decode('utf-8', errors='ignore')
667
816
 
668
817
  log_entry += f"--- Output ---\n{output}\n"
669
818
  if output.strip():
670
819
  return _to_plain(output)
671
820
  else:
672
821
  return "Command executed successfully (no output)."
673
- except subprocess.TimeoutExpired:
674
- error_msg = (
675
- f"Error: Command timed out after {self.timeout} seconds."
676
- )
677
- log_entry += f"--- Error ---\n{error_msg}\n"
678
- return error_msg
679
822
  except Exception as e:
680
- if (
681
- isinstance(e, (subprocess.TimeoutExpired, TimeoutError))
682
- or "timed out" in str(e).lower()
683
- ):
684
- error_msg = (
685
- f"Error: Command timed out after "
686
- f"{self.timeout} seconds."
687
- )
688
- else:
689
- error_msg = f"Error executing command: {e}"
823
+ error_msg = f"Error executing command: {e}"
690
824
  log_entry += f"--- Error ---\n{error_msg}\n"
691
825
  return error_msg
692
826
  finally:
@@ -697,12 +831,6 @@ class TerminalToolkit(BaseToolkit):
697
831
  self.log_dir, f"session_{session_id}.log"
698
832
  )
699
833
 
700
- self._write_to_log(
701
- session_log_file,
702
- f"--- Starting non-blocking session at {time.ctime()} ---\n"
703
- f"> {command}\n",
704
- )
705
-
706
834
  # PYTHONUNBUFFERED=1 for real-time output
707
835
  # Without this, Python subprocesses buffer output (4KB buffer)
708
836
  # and shell_view() won't see output until buffer fills or process
@@ -711,11 +839,24 @@ class TerminalToolkit(BaseToolkit):
711
839
  env_vars["PYTHONUNBUFFERED"] = "1"
712
840
  docker_env = {"PYTHONUNBUFFERED": "1"}
713
841
 
842
+ # Check and create session atomically to prevent race condition
714
843
  with self._session_lock:
844
+ if session_id in self.shell_sessions:
845
+ existing_session = self.shell_sessions[session_id]
846
+ if existing_session.get("running", False):
847
+ return (
848
+ f"Error: Session '{session_id}' already exists "
849
+ f"and is running. Use a different ID or kill "
850
+ f"the existing session first."
851
+ )
852
+
853
+ # Create session entry while holding the lock
715
854
  self.shell_sessions[session_id] = {
716
855
  "id": session_id,
717
856
  "process": None,
718
- "output_stream": Queue(),
857
+ # Limit queue size to prevent memory exhaustion
858
+ # (~100MB with 10k items of ~10KB each)
859
+ "output_stream": Queue(maxsize=10000),
719
860
  "command_history": [command],
720
861
  "running": True,
721
862
  "log_file": session_log_file,
@@ -724,6 +865,12 @@ class TerminalToolkit(BaseToolkit):
724
865
  else "local",
725
866
  }
726
867
 
868
+ self._write_to_log(
869
+ session_log_file,
870
+ f"--- Starting non-blocking session at {time.ctime()} ---\n"
871
+ f"> {command}\n",
872
+ )
873
+
727
874
  process = None
728
875
  exec_socket = None
729
876
  try:
@@ -897,6 +1044,49 @@ class TerminalToolkit(BaseToolkit):
897
1044
  if output:
898
1045
  return "".join(output)
899
1046
  else:
1047
+ # For timeout-converted Docker sessions, check thread and output
1048
+ if (
1049
+ session.get("timeout_converted")
1050
+ and session.get("backend") == "docker"
1051
+ ):
1052
+ exec_thread = session.get("exec_thread")
1053
+ result_container = session.get("result_container", {})
1054
+
1055
+ # Check if the background thread has completed
1056
+ if exec_thread and not exec_thread.is_alive():
1057
+ # Thread finished - get output from result_container
1058
+ with self._session_lock:
1059
+ if id in self.shell_sessions:
1060
+ self.shell_sessions[id]["running"] = False
1061
+
1062
+ if 'output' in result_container:
1063
+ completed_output = result_container['output'].decode(
1064
+ 'utf-8', errors='ignore'
1065
+ )
1066
+ # Write to log file
1067
+ self._write_to_log(
1068
+ session["log_file"], completed_output
1069
+ )
1070
+ return (
1071
+ f"{_to_plain(completed_output)}\n\n"
1072
+ f"--- SESSION COMPLETED ---"
1073
+ )
1074
+ elif 'error' in result_container:
1075
+ return (
1076
+ f"--- SESSION FAILED ---\n"
1077
+ f"Error: {result_container['error']}"
1078
+ )
1079
+ else:
1080
+ return "--- SESSION COMPLETED (no output) ---"
1081
+ else:
1082
+ # Thread still running
1083
+ return (
1084
+ "[Process still running]\n"
1085
+ "Command is executing in background. "
1086
+ "Check again later for output.\n"
1087
+ "Use shell_kill_process() to terminate if needed."
1088
+ )
1089
+
900
1090
  # No new output - guide the agent
901
1091
  return (
902
1092
  "[No new output]\n"
@@ -907,6 +1097,7 @@ class TerminalToolkit(BaseToolkit):
907
1097
  "too frequently)"
908
1098
  )
909
1099
 
1100
+ @manual_timeout
910
1101
  def shell_kill_process(self, id: str) -> str:
911
1102
  r"""This function forcibly terminates a running non-blocking process.
912
1103
 
@@ -941,8 +1132,30 @@ class TerminalToolkit(BaseToolkit):
941
1132
  except Exception:
942
1133
  pass
943
1134
  else: # docker
944
- # Docker exec processes stop when the socket is closed.
945
- session["process"].close()
1135
+ # Check if this is a timeout-converted session (no socket)
1136
+ if session.get("timeout_converted") and session.get("exec_id"):
1137
+ # Kill the process using Docker exec PID
1138
+ exec_id = session["exec_id"]
1139
+ try:
1140
+ exec_info = self.docker_api_client.exec_inspect(
1141
+ exec_id
1142
+ )
1143
+ pid = exec_info.get('Pid')
1144
+ if pid and exec_info.get('Running', False):
1145
+ # Kill the process inside the container
1146
+ kill_cmd = f'kill -9 {pid}'
1147
+ kill_exec = self.docker_api_client.exec_create(
1148
+ self.container.id, kill_cmd
1149
+ )
1150
+ self.docker_api_client.exec_start(kill_exec['Id'])
1151
+ except Exception as kill_err:
1152
+ logger.warning(
1153
+ f"[SESSION {id}] Failed to kill Docker exec "
1154
+ f"process: {kill_err}"
1155
+ )
1156
+ elif session["process"] is not None:
1157
+ # Normal non-blocking session with socket
1158
+ session["process"].close()
946
1159
  with self._session_lock:
947
1160
  if id in self.shell_sessions:
948
1161
  self.shell_sessions[id]["running"] = False
@@ -969,9 +1182,11 @@ class TerminalToolkit(BaseToolkit):
969
1182
  str: The output from the shell session after the user's command has
970
1183
  been executed, or help information for general queries.
971
1184
  """
972
- logger.info("\n" + "=" * 60)
973
- logger.info("LLM Agent needs your help!")
974
- logger.info(f"PROMPT: {prompt}")
1185
+ # Use print for user-facing messages since this is an interactive
1186
+ # function that requires terminal input via input()
1187
+ print("\n" + "=" * 60)
1188
+ print("LLM Agent needs your help!")
1189
+ print(f"PROMPT: {prompt}")
975
1190
 
976
1191
  # Case 1: Session doesn't exist - offer to create one
977
1192
  if id not in self.shell_sessions:
@@ -980,9 +1195,7 @@ class TerminalToolkit(BaseToolkit):
980
1195
  if not user_input:
981
1196
  return "No user response."
982
1197
  else:
983
- logger.info(
984
- f"Creating session '{id}' and executing command..."
985
- )
1198
+ print(f"Creating session '{id}' and executing command...")
986
1199
  result = self.shell_exec(id, user_input, block=True)
987
1200
  return (
988
1201
  f"Session '{id}' created and "
@@ -999,11 +1212,11 @@ class TerminalToolkit(BaseToolkit):
999
1212
  last_output.strip() if last_output.strip() else "(no output)"
1000
1213
  )
1001
1214
 
1002
- logger.info(f"SESSION: '{id}' (active)")
1003
- logger.info("=" * 60)
1004
- logger.info("--- LAST OUTPUT ---")
1005
- logger.info(last_output_display)
1006
- logger.info("-------------------")
1215
+ print(f"SESSION: '{id}' (active)")
1216
+ print("=" * 60)
1217
+ print("--- LAST OUTPUT ---")
1218
+ print(last_output_display)
1219
+ print("-------------------")
1007
1220
 
1008
1221
  try:
1009
1222
  user_input = input("Your input: ").strip()
@@ -1024,6 +1237,7 @@ class TerminalToolkit(BaseToolkit):
1024
1237
  self.cleanup()
1025
1238
  return False
1026
1239
 
1240
+ @manual_timeout
1027
1241
  def cleanup(self):
1028
1242
  r"""Clean up all active sessions."""
1029
1243
  with self._session_lock:
@@ -1042,8 +1256,7 @@ class TerminalToolkit(BaseToolkit):
1042
1256
  f"during cleanup: {e}"
1043
1257
  )
1044
1258
 
1045
- cleanup._manual_timeout = True # type: ignore[attr-defined]
1046
-
1259
+ @manual_timeout
1047
1260
  def __del__(self):
1048
1261
  r"""Fallback cleanup in destructor."""
1049
1262
  try:
@@ -1051,8 +1264,6 @@ class TerminalToolkit(BaseToolkit):
1051
1264
  except Exception:
1052
1265
  pass
1053
1266
 
1054
- __del__._manual_timeout = True # type: ignore[attr-defined]
1055
-
1056
1267
  def get_tools(self) -> List[FunctionTool]:
1057
1268
  r"""Returns a list of FunctionTool objects representing the functions
1058
1269
  in the toolkit.