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.
- camel/__init__.py +3 -3
- camel/agents/__init__.py +2 -2
- camel/agents/_types.py +2 -2
- camel/agents/_utils.py +2 -2
- camel/agents/base.py +2 -2
- camel/agents/chat_agent.py +765 -541
- camel/agents/critic_agent.py +2 -2
- camel/agents/deductive_reasoner_agent.py +2 -2
- camel/agents/embodied_agent.py +2 -2
- camel/agents/knowledge_graph_agent.py +2 -2
- camel/agents/mcp_agent.py +2 -2
- camel/agents/multi_hop_generator_agent.py +2 -2
- camel/agents/programmed_agent_instruction.py +2 -2
- camel/agents/repo_agent.py +2 -2
- camel/agents/role_assignment_agent.py +2 -2
- camel/agents/search_agent.py +2 -2
- camel/agents/task_agent.py +2 -2
- camel/agents/tool_agents/__init__.py +2 -2
- camel/agents/tool_agents/base.py +2 -2
- camel/agents/tool_agents/hugging_face_tool_agent.py +2 -2
- camel/benchmarks/__init__.py +2 -2
- camel/benchmarks/apibank.py +2 -2
- camel/benchmarks/apibench.py +2 -2
- camel/benchmarks/base.py +2 -2
- camel/benchmarks/browsecomp.py +2 -2
- camel/benchmarks/gaia.py +2 -2
- camel/benchmarks/mock_website/mock_web.py +2 -2
- camel/benchmarks/mock_website/shopping_mall/app.py +2 -2
- camel/benchmarks/nexus.py +2 -2
- camel/benchmarks/ragbench.py +2 -2
- camel/bots/__init__.py +2 -2
- camel/bots/discord/__init__.py +2 -2
- camel/bots/discord/discord_app.py +2 -2
- camel/bots/discord/discord_installation.py +2 -2
- camel/bots/discord/discord_store.py +2 -2
- camel/bots/slack/__init__.py +2 -2
- camel/bots/slack/models.py +2 -2
- camel/bots/slack/slack_app.py +2 -2
- camel/bots/telegram_bot.py +2 -2
- camel/configs/__init__.py +8 -2
- camel/configs/aihubmix_config.py +2 -2
- camel/configs/aiml_config.py +2 -2
- camel/configs/amd_config.py +2 -2
- camel/configs/anthropic_config.py +2 -2
- camel/configs/base_config.py +2 -2
- camel/configs/bedrock_config.py +2 -2
- camel/configs/cerebras_config.py +2 -2
- camel/configs/cohere_config.py +2 -2
- camel/configs/cometapi_config.py +2 -2
- camel/configs/crynux_config.py +2 -2
- camel/configs/deepseek_config.py +2 -2
- camel/configs/function_gemma_config.py +59 -0
- camel/configs/gemini_config.py +2 -2
- camel/configs/groq_config.py +2 -2
- camel/configs/internlm_config.py +2 -2
- camel/configs/litellm_config.py +2 -2
- camel/configs/lmstudio_config.py +2 -2
- camel/configs/minimax_config.py +2 -2
- camel/configs/mistral_config.py +2 -2
- camel/configs/modelscope_config.py +2 -2
- camel/configs/moonshot_config.py +2 -2
- camel/configs/nebius_config.py +2 -2
- camel/configs/netmind_config.py +2 -2
- camel/configs/novita_config.py +2 -2
- camel/configs/nvidia_config.py +2 -2
- camel/configs/ollama_config.py +2 -2
- camel/configs/openai_config.py +2 -2
- camel/configs/openrouter_config.py +2 -2
- camel/configs/ppio_config.py +2 -2
- camel/configs/qianfan_config.py +2 -2
- camel/configs/qwen_config.py +2 -2
- camel/configs/reka_config.py +2 -2
- camel/configs/samba_config.py +2 -2
- camel/configs/sglang_config.py +2 -2
- camel/configs/siliconflow_config.py +2 -2
- camel/configs/togetherai_config.py +2 -2
- camel/configs/vllm_config.py +2 -2
- camel/configs/watsonx_config.py +2 -2
- camel/configs/yi_config.py +2 -2
- camel/configs/zhipuai_config.py +2 -2
- camel/data_collectors/__init__.py +2 -2
- camel/data_collectors/alpaca_collector.py +2 -2
- camel/data_collectors/base.py +2 -2
- camel/data_collectors/sharegpt_collector.py +2 -2
- camel/datagen/__init__.py +2 -2
- camel/datagen/cot_datagen.py +2 -2
- camel/datagen/evol_instruct/__init__.py +2 -2
- camel/datagen/evol_instruct/evol_instruct.py +2 -2
- camel/datagen/evol_instruct/scorer.py +2 -2
- camel/datagen/evol_instruct/templates.py +2 -2
- camel/datagen/self_improving_cot.py +2 -2
- camel/datagen/self_instruct/__init__.py +2 -2
- camel/datagen/self_instruct/filter/__init__.py +2 -2
- camel/datagen/self_instruct/filter/filter_function.py +2 -2
- camel/datagen/self_instruct/filter/filter_registry.py +2 -2
- camel/datagen/self_instruct/filter/instruction_filter.py +2 -2
- camel/datagen/self_instruct/self_instruct.py +2 -2
- camel/datagen/self_instruct/templates.py +2 -2
- camel/datagen/source2synth/__init__.py +2 -2
- camel/datagen/source2synth/data_processor.py +2 -2
- camel/datagen/source2synth/models.py +2 -2
- camel/datagen/source2synth/user_data_processor_config.py +2 -2
- camel/datahubs/__init__.py +2 -2
- camel/datahubs/base.py +2 -2
- camel/datahubs/huggingface.py +2 -2
- camel/datahubs/models.py +2 -2
- camel/datasets/__init__.py +2 -2
- camel/datasets/base_generator.py +2 -2
- camel/datasets/few_shot_generator.py +2 -2
- camel/datasets/models.py +2 -2
- camel/datasets/self_instruct_generator.py +2 -2
- camel/datasets/static_dataset.py +2 -2
- camel/embeddings/__init__.py +2 -2
- camel/embeddings/azure_embedding.py +2 -2
- camel/embeddings/base.py +2 -2
- camel/embeddings/gemini_embedding.py +2 -2
- camel/embeddings/jina_embedding.py +2 -2
- camel/embeddings/mistral_embedding.py +2 -2
- camel/embeddings/openai_compatible_embedding.py +2 -2
- camel/embeddings/openai_embedding.py +2 -2
- camel/embeddings/sentence_transformers_embeddings.py +2 -2
- camel/embeddings/together_embedding.py +2 -2
- camel/embeddings/vlm_embedding.py +2 -2
- camel/environments/__init__.py +2 -2
- camel/environments/models.py +2 -2
- camel/environments/multi_step.py +2 -2
- camel/environments/rlcards_env.py +2 -2
- camel/environments/single_step.py +2 -2
- camel/environments/tic_tac_toe.py +2 -2
- camel/extractors/__init__.py +2 -2
- camel/extractors/base.py +2 -2
- camel/extractors/python_strategies.py +2 -2
- camel/generators.py +2 -2
- camel/human.py +2 -2
- camel/interpreters/__init__.py +2 -2
- camel/interpreters/base.py +2 -2
- camel/interpreters/docker_interpreter.py +2 -2
- camel/interpreters/e2b_interpreter.py +2 -2
- camel/interpreters/internal_python_interpreter.py +2 -2
- camel/interpreters/interpreter_error.py +2 -2
- camel/interpreters/ipython_interpreter.py +2 -2
- camel/interpreters/microsandbox_interpreter.py +2 -2
- camel/interpreters/subprocess_interpreter.py +2 -2
- camel/loaders/__init__.py +2 -2
- camel/loaders/apify_reader.py +2 -2
- camel/loaders/base_io.py +2 -2
- camel/loaders/base_loader.py +2 -2
- camel/loaders/chunkr_reader.py +2 -2
- camel/loaders/crawl4ai_reader.py +2 -2
- camel/loaders/firecrawl_reader.py +2 -2
- camel/loaders/jina_url_reader.py +2 -2
- camel/loaders/markitdown.py +2 -2
- camel/loaders/mineru_extractor.py +2 -2
- camel/loaders/mistral_reader.py +2 -2
- camel/loaders/scrapegraph_reader.py +2 -2
- camel/loaders/unstructured_io.py +2 -2
- camel/logger.py +2 -2
- camel/memories/__init__.py +2 -2
- camel/memories/agent_memories.py +2 -2
- camel/memories/base.py +2 -2
- camel/memories/blocks/__init__.py +2 -2
- camel/memories/blocks/chat_history_block.py +2 -2
- camel/memories/blocks/vectordb_block.py +2 -2
- camel/memories/context_creators/__init__.py +2 -2
- camel/memories/context_creators/score_based.py +89 -2
- camel/memories/records.py +2 -2
- camel/messages/__init__.py +2 -2
- camel/messages/base.py +2 -2
- camel/messages/conversion/__init__.py +2 -2
- camel/messages/conversion/alpaca.py +2 -2
- camel/messages/conversion/conversation_models.py +2 -2
- camel/messages/conversion/sharegpt/__init__.py +2 -2
- camel/messages/conversion/sharegpt/function_call_formatter.py +2 -2
- camel/messages/conversion/sharegpt/hermes/__init__.py +2 -2
- camel/messages/conversion/sharegpt/hermes/hermes_function_formatter.py +2 -2
- camel/messages/func_message.py +2 -2
- camel/models/__init__.py +4 -2
- camel/models/_utils.py +2 -2
- camel/models/aihubmix_model.py +2 -2
- camel/models/aiml_model.py +2 -2
- camel/models/amd_model.py +2 -2
- camel/models/anthropic_model.py +2 -2
- camel/models/aws_bedrock_model.py +2 -2
- camel/models/azure_openai_model.py +4 -28
- camel/models/base_audio_model.py +2 -2
- camel/models/base_model.py +192 -14
- camel/models/cerebras_model.py +2 -2
- camel/models/cohere_model.py +4 -30
- camel/models/cometapi_model.py +2 -2
- camel/models/crynux_model.py +2 -2
- camel/models/deepseek_model.py +4 -28
- camel/models/fish_audio_model.py +2 -2
- camel/models/function_gemma_model.py +889 -0
- camel/models/gemini_model.py +4 -28
- camel/models/groq_model.py +2 -2
- camel/models/internlm_model.py +2 -2
- camel/models/litellm_model.py +3 -17
- camel/models/lmstudio_model.py +2 -2
- camel/models/minimax_model.py +2 -2
- camel/models/mistral_model.py +4 -30
- camel/models/model_factory.py +4 -2
- camel/models/model_manager.py +2 -2
- camel/models/modelscope_model.py +2 -2
- camel/models/moonshot_model.py +3 -15
- camel/models/nebius_model.py +2 -2
- camel/models/nemotron_model.py +2 -2
- camel/models/netmind_model.py +2 -2
- camel/models/novita_model.py +2 -2
- camel/models/nvidia_model.py +2 -2
- camel/models/ollama_model.py +2 -2
- camel/models/openai_audio_models.py +2 -2
- camel/models/openai_compatible_model.py +4 -28
- camel/models/openai_model.py +4 -43
- camel/models/openrouter_model.py +2 -2
- camel/models/ppio_model.py +2 -2
- camel/models/qianfan_model.py +2 -2
- camel/models/qwen_model.py +2 -2
- camel/models/reka_model.py +4 -30
- camel/models/reward/__init__.py +2 -2
- camel/models/reward/base_reward_model.py +2 -2
- camel/models/reward/evaluator.py +2 -2
- camel/models/reward/nemotron_model.py +2 -2
- camel/models/reward/skywork_model.py +2 -2
- camel/models/samba_model.py +4 -30
- camel/models/sglang_model.py +4 -30
- camel/models/siliconflow_model.py +2 -2
- camel/models/stub_model.py +2 -2
- camel/models/togetherai_model.py +2 -2
- camel/models/vllm_model.py +2 -2
- camel/models/volcano_model.py +147 -4
- camel/models/watsonx_model.py +4 -30
- camel/models/yi_model.py +2 -2
- camel/models/zhipuai_model.py +2 -2
- camel/parsers/__init__.py +2 -2
- camel/parsers/mcp_tool_call_parser.py +2 -2
- camel/personas/__init__.py +2 -2
- camel/personas/persona.py +2 -2
- camel/personas/persona_hub.py +2 -2
- camel/prompts/__init__.py +2 -2
- camel/prompts/ai_society.py +2 -2
- camel/prompts/base.py +2 -2
- camel/prompts/code.py +2 -2
- camel/prompts/evaluation.py +2 -2
- camel/prompts/generate_text_embedding_data.py +2 -2
- camel/prompts/image_craft.py +2 -2
- camel/prompts/misalignment.py +2 -2
- camel/prompts/multi_condition_image_craft.py +2 -2
- camel/prompts/object_recognition.py +2 -2
- camel/prompts/persona_hub.py +2 -2
- camel/prompts/prompt_templates.py +2 -2
- camel/prompts/role_description_prompt_template.py +2 -2
- camel/prompts/solution_extraction.py +2 -2
- camel/prompts/task_prompt_template.py +2 -2
- camel/prompts/translation.py +2 -2
- camel/prompts/video_description_prompt.py +2 -2
- camel/responses/__init__.py +2 -2
- camel/responses/agent_responses.py +2 -2
- camel/retrievers/__init__.py +2 -2
- camel/retrievers/auto_retriever.py +2 -2
- camel/retrievers/base.py +2 -2
- camel/retrievers/bm25_retriever.py +2 -2
- camel/retrievers/cohere_rerank_retriever.py +2 -2
- camel/retrievers/hybrid_retrival.py +2 -2
- camel/retrievers/vector_retriever.py +2 -2
- camel/runtimes/__init__.py +2 -2
- camel/runtimes/api.py +2 -2
- camel/runtimes/base.py +2 -2
- camel/runtimes/configs.py +2 -2
- camel/runtimes/daytona_runtime.py +2 -2
- camel/runtimes/docker_runtime.py +2 -2
- camel/runtimes/llm_guard_runtime.py +2 -2
- camel/runtimes/remote_http_runtime.py +2 -2
- camel/runtimes/ubuntu_docker_runtime.py +2 -2
- camel/runtimes/utils/__init__.py +2 -2
- camel/runtimes/utils/function_risk_toolkit.py +2 -2
- camel/runtimes/utils/ignore_risk_toolkit.py +2 -2
- camel/schemas/__init__.py +2 -2
- camel/schemas/base.py +2 -2
- camel/schemas/openai_converter.py +2 -2
- camel/schemas/outlines_converter.py +2 -2
- camel/services/agent_openapi_server.py +2 -2
- camel/societies/__init__.py +2 -2
- camel/societies/babyagi_playing.py +2 -2
- camel/societies/role_playing.py +2 -2
- camel/societies/workforce/__init__.py +2 -2
- camel/societies/workforce/base.py +2 -2
- camel/societies/workforce/events.py +4 -2
- camel/societies/workforce/prompts.py +9 -8
- camel/societies/workforce/role_playing_worker.py +2 -2
- camel/societies/workforce/single_agent_worker.py +2 -2
- camel/societies/workforce/structured_output_handler.py +2 -2
- camel/societies/workforce/task_channel.py +2 -2
- camel/societies/workforce/utils.py +2 -2
- camel/societies/workforce/worker.py +2 -2
- camel/societies/workforce/workflow_memory_manager.py +2 -2
- camel/societies/workforce/workforce.py +132 -71
- camel/societies/workforce/workforce_callback.py +2 -2
- camel/societies/workforce/workforce_logger.py +2 -2
- camel/societies/workforce/workforce_metrics.py +2 -2
- camel/storages/__init__.py +2 -2
- camel/storages/graph_storages/__init__.py +2 -2
- camel/storages/graph_storages/base.py +2 -2
- camel/storages/graph_storages/graph_element.py +2 -2
- camel/storages/graph_storages/nebula_graph.py +2 -2
- camel/storages/graph_storages/neo4j_graph.py +2 -2
- camel/storages/key_value_storages/__init__.py +2 -2
- camel/storages/key_value_storages/base.py +2 -2
- camel/storages/key_value_storages/in_memory.py +2 -2
- camel/storages/key_value_storages/json.py +2 -2
- camel/storages/key_value_storages/mem0_cloud.py +2 -2
- camel/storages/key_value_storages/redis.py +2 -2
- camel/storages/object_storages/__init__.py +2 -2
- camel/storages/object_storages/amazon_s3.py +2 -2
- camel/storages/object_storages/azure_blob.py +2 -2
- camel/storages/object_storages/base.py +2 -2
- camel/storages/object_storages/google_cloud.py +2 -2
- camel/storages/vectordb_storages/__init__.py +2 -2
- camel/storages/vectordb_storages/base.py +2 -2
- camel/storages/vectordb_storages/chroma.py +2 -2
- camel/storages/vectordb_storages/faiss.py +2 -2
- camel/storages/vectordb_storages/milvus.py +2 -2
- camel/storages/vectordb_storages/oceanbase.py +2 -2
- camel/storages/vectordb_storages/pgvector.py +2 -2
- camel/storages/vectordb_storages/qdrant.py +2 -2
- camel/storages/vectordb_storages/surreal.py +2 -2
- camel/storages/vectordb_storages/tidb.py +2 -2
- camel/storages/vectordb_storages/weaviate.py +2 -2
- camel/tasks/__init__.py +2 -2
- camel/tasks/task.py +2 -2
- camel/tasks/task_prompt.py +2 -2
- camel/terminators/__init__.py +2 -2
- camel/terminators/base.py +2 -2
- camel/terminators/response_terminator.py +2 -2
- camel/terminators/token_limit_terminator.py +2 -2
- camel/toolkits/__init__.py +6 -3
- camel/toolkits/aci_toolkit.py +2 -2
- camel/toolkits/arxiv_toolkit.py +2 -2
- camel/toolkits/ask_news_toolkit.py +2 -2
- camel/toolkits/async_browser_toolkit.py +2 -2
- camel/toolkits/audio_analysis_toolkit.py +2 -2
- camel/toolkits/base.py +47 -5
- camel/toolkits/bohrium_toolkit.py +2 -2
- camel/toolkits/browser_toolkit.py +2 -2
- camel/toolkits/browser_toolkit_commons.py +2 -2
- camel/toolkits/code_execution.py +2 -2
- camel/toolkits/context_summarizer_toolkit.py +2 -2
- camel/toolkits/craw4ai_toolkit.py +2 -2
- camel/toolkits/dappier_toolkit.py +2 -2
- camel/toolkits/data_commons_toolkit.py +2 -2
- camel/toolkits/dingtalk.py +2 -2
- camel/toolkits/earth_science_toolkit.py +2 -2
- camel/toolkits/edgeone_pages_mcp_toolkit.py +2 -2
- camel/toolkits/excel_toolkit.py +2 -2
- camel/toolkits/file_toolkit.py +2 -2
- camel/toolkits/function_tool.py +95 -25
- camel/toolkits/github_toolkit.py +2 -2
- camel/toolkits/gmail_toolkit.py +2 -2
- camel/toolkits/google_calendar_toolkit.py +2 -2
- camel/toolkits/google_drive_mcp_toolkit.py +2 -2
- camel/toolkits/google_maps_toolkit.py +2 -2
- camel/toolkits/google_scholar_toolkit.py +2 -2
- camel/toolkits/human_toolkit.py +2 -2
- camel/toolkits/hybrid_browser_toolkit/__init__.py +2 -2
- camel/toolkits/hybrid_browser_toolkit/config_loader.py +2 -2
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +2 -2
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +89 -104
- camel/toolkits/hybrid_browser_toolkit/installer.py +2 -2
- camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +25 -14
- camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +6 -0
- camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +2 -2
- camel/toolkits/hybrid_browser_toolkit_py/__init__.py +2 -2
- camel/toolkits/hybrid_browser_toolkit_py/actions.py +2 -2
- camel/toolkits/hybrid_browser_toolkit_py/agent.py +2 -2
- camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +2 -2
- camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +2 -2
- camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +2 -2
- camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +2 -2
- camel/toolkits/image_analysis_toolkit.py +2 -2
- camel/toolkits/image_generation_toolkit.py +2 -2
- camel/toolkits/jina_reranker_toolkit.py +2 -2
- camel/toolkits/klavis_toolkit.py +2 -2
- camel/toolkits/linkedin_toolkit.py +2 -2
- camel/toolkits/markitdown_toolkit.py +2 -2
- camel/toolkits/math_toolkit.py +2 -2
- camel/toolkits/mcp_toolkit.py +2 -2
- camel/toolkits/memory_toolkit.py +2 -2
- camel/toolkits/meshy_toolkit.py +2 -2
- camel/toolkits/message_agent_toolkit.py +2 -2
- camel/toolkits/message_integration.py +6 -2
- camel/toolkits/microsoft_outlook_mail_toolkit.py +1885 -0
- camel/toolkits/mineru_toolkit.py +2 -2
- camel/toolkits/minimax_mcp_toolkit.py +2 -2
- camel/toolkits/networkx_toolkit.py +2 -2
- camel/toolkits/note_taking_toolkit.py +2 -2
- camel/toolkits/notion_mcp_toolkit.py +2 -2
- camel/toolkits/notion_toolkit.py +2 -2
- camel/toolkits/open_api_specs/biztoc/__init__.py +2 -2
- camel/toolkits/open_api_specs/coursera/__init__.py +2 -2
- camel/toolkits/open_api_specs/create_qr_code/__init__.py +2 -2
- camel/toolkits/open_api_specs/klarna/__init__.py +2 -2
- camel/toolkits/open_api_specs/nasa_apod/__init__.py +2 -2
- camel/toolkits/open_api_specs/outschool/__init__.py +2 -2
- camel/toolkits/open_api_specs/outschool/paths/__init__.py +2 -2
- camel/toolkits/open_api_specs/outschool/paths/get_classes.py +2 -2
- camel/toolkits/open_api_specs/outschool/paths/search_teachers.py +2 -2
- camel/toolkits/open_api_specs/security_config.py +2 -2
- camel/toolkits/open_api_specs/speak/__init__.py +2 -2
- camel/toolkits/open_api_specs/web_scraper/__init__.py +2 -2
- camel/toolkits/open_api_specs/web_scraper/paths/__init__.py +2 -2
- camel/toolkits/open_api_specs/web_scraper/paths/scraper.py +2 -2
- camel/toolkits/open_api_toolkit.py +2 -2
- camel/toolkits/openbb_toolkit.py +2 -2
- camel/toolkits/origene_mcp_toolkit.py +2 -2
- camel/toolkits/playwright_mcp_toolkit.py +2 -2
- camel/toolkits/pptx_toolkit.py +2 -2
- camel/toolkits/pubmed_toolkit.py +2 -2
- camel/toolkits/pulse_mcp_search_toolkit.py +2 -2
- camel/toolkits/pyautogui_toolkit.py +2 -2
- camel/toolkits/reddit_toolkit.py +2 -2
- camel/toolkits/resend_toolkit.py +2 -2
- camel/toolkits/retrieval_toolkit.py +2 -2
- camel/toolkits/screenshot_toolkit.py +2 -2
- camel/toolkits/search_toolkit.py +70 -13
- camel/toolkits/searxng_toolkit.py +2 -2
- camel/toolkits/semantic_scholar_toolkit.py +2 -2
- camel/toolkits/slack_toolkit.py +2 -2
- camel/toolkits/sql_toolkit.py +2 -2
- camel/toolkits/stripe_toolkit.py +2 -2
- camel/toolkits/sympy_toolkit.py +2 -2
- camel/toolkits/task_planning_toolkit.py +2 -2
- camel/toolkits/terminal_toolkit/__init__.py +2 -2
- camel/toolkits/terminal_toolkit/terminal_toolkit.py +323 -112
- camel/toolkits/terminal_toolkit/utils.py +179 -52
- camel/toolkits/thinking_toolkit.py +2 -2
- camel/toolkits/twitter_toolkit.py +2 -2
- camel/toolkits/vertex_ai_veo_toolkit.py +2 -2
- camel/toolkits/video_analysis_toolkit.py +2 -2
- camel/toolkits/video_download_toolkit.py +2 -2
- camel/toolkits/weather_toolkit.py +2 -2
- camel/toolkits/web_deploy_toolkit.py +2 -2
- camel/toolkits/wechat_official_toolkit.py +2 -2
- camel/toolkits/whatsapp_toolkit.py +2 -2
- camel/toolkits/wolfram_alpha_toolkit.py +2 -2
- camel/toolkits/zapier_toolkit.py +2 -2
- camel/types/__init__.py +2 -2
- camel/types/agents/__init__.py +2 -2
- camel/types/agents/tool_calling_record.py +2 -2
- camel/types/enums.py +5 -4
- camel/types/mcp_registries.py +2 -2
- camel/types/openai_types.py +2 -2
- camel/types/unified_model_type.py +10 -6
- camel/utils/__init__.py +5 -2
- camel/utils/agent_context.py +41 -0
- camel/utils/async_func.py +2 -2
- camel/utils/chunker/__init__.py +2 -2
- camel/utils/chunker/base.py +2 -2
- camel/utils/chunker/code_chunker.py +2 -2
- camel/utils/chunker/uio_chunker.py +2 -2
- camel/utils/commons.py +2 -2
- camel/utils/constants.py +2 -2
- camel/utils/context_utils.py +2 -2
- camel/utils/deduplication.py +2 -2
- camel/utils/filename.py +2 -2
- camel/utils/langfuse.py +18 -10
- camel/utils/mcp.py +2 -2
- camel/utils/mcp_client.py +2 -2
- camel/utils/message_summarizer.py +2 -2
- camel/utils/response_format.py +2 -2
- camel/utils/token_counting.py +2 -2
- camel/utils/tool_result.py +2 -2
- camel/verifiers/__init__.py +2 -2
- camel/verifiers/base.py +2 -2
- camel/verifiers/math_verifier.py +2 -2
- camel/verifiers/models.py +2 -2
- camel/verifiers/physics_verifier.py +2 -2
- camel/verifiers/python_verifier.py +2 -2
- {camel_ai-0.2.82.dist-info → camel_ai-0.2.83a6.dist-info}/METADATA +34 -29
- camel_ai-0.2.83a6.dist-info/RECORD +511 -0
- camel_ai-0.2.82.dist-info/RECORD +0 -507
- {camel_ai-0.2.82.dist-info → camel_ai-0.2.83a6.dist-info}/WHEEL +0 -0
- {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-
|
|
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-
|
|
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'
|
|
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
|
|
402
|
-
r"""
|
|
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
|
-
|
|
401
|
+
return self.cloned_env_path
|
|
411
402
|
elif self.initial_env_path and os.path.exists(self.initial_env_path):
|
|
412
|
-
|
|
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.
|
|
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
|
-
|
|
549
|
-
start_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
|
|
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
|
-
|
|
568
|
+
last_output_time = time.time() # Reset idle timer
|
|
571
569
|
else:
|
|
572
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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,
|
|
627
|
-
|
|
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
|
-
|
|
641
|
-
|
|
676
|
+
env_vars = os.environ.copy()
|
|
677
|
+
env_vars["PYTHONUNBUFFERED"] = "1"
|
|
678
|
+
proc = subprocess.Popen(
|
|
642
679
|
command,
|
|
643
|
-
|
|
644
|
-
|
|
680
|
+
stdout=subprocess.PIPE,
|
|
681
|
+
stderr=subprocess.STDOUT,
|
|
682
|
+
stdin=subprocess.PIPE,
|
|
645
683
|
shell=True,
|
|
646
|
-
|
|
684
|
+
text=True,
|
|
647
685
|
cwd=self.working_dir,
|
|
648
686
|
encoding="utf-8",
|
|
687
|
+
env=env_vars,
|
|
649
688
|
)
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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
|
-
|
|
664
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
945
|
-
session
|
|
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
|
-
|
|
973
|
-
|
|
974
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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
|
-
|
|
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.
|