sycommon-python-lib 0.2.1a36__tar.gz → 0.2.1a37__tar.gz
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.
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/PKG-INFO +1 -1
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/pyproject.toml +1 -1
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/agent/sandbox/file_ops.py +156 -13
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/agent/sandbox/http_sandbox_backend.py +288 -53
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon_python_lib.egg-info/PKG-INFO +1 -1
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/README.md +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/setup.cfg +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/command/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/command/cli.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/command/core/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/command/core/console.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/command/core/models.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/command/core/project.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/command/core/utils.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/command/templates/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/command/templates/agent/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/command/templates/base/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/command/templates/web/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/agent/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/agent/agent_manager.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/agent/chat_events.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/agent/deep_agent.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/agent/multi_agent_team.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/agent/sandbox/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/agent/sandbox/sandbox_pool.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/agent/sandbox/sandbox_recovery.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/agent/sandbox/session.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/auth/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/auth/ldap_service.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/config/Config.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/config/DatabaseConfig.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/config/ElasticsearchConfig.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/config/EmbeddingConfig.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/config/LLMConfig.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/config/LangfuseConfig.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/config/MQConfig.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/config/RedisConfig.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/config/RerankerConfig.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/config/SentryConfig.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/config/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/database/async_base_db_service.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/database/async_database_service.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/database/base_db_service.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/database/database_service.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/database/elasticsearch_service.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/database/redis_service.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/database/token_usage_db_service.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/health/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/health/health_check.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/health/metrics.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/health/ping.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/heartbeat_process/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/heartbeat_process/heartbeat_config.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/heartbeat_process/heartbeat_process_manager.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/heartbeat_process/heartbeat_process_worker.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/llm/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/llm/embedding.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/llm/get_llm.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/llm/llm_logger.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/llm/llm_tokens.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/llm/llm_with_token_tracking.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/llm/native_with_fallback_runnable.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/llm/output_fixing_runnable.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/llm/struct_token.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/llm/sy_langfuse.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/llm/token_usage_es_service.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/llm/token_usage_mysql_service.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/llm/usage_token.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/logging/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/logging/async_sql_logger.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/logging/kafka_log.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/logging/logger_levels.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/logging/logger_wrapper.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/logging/process_logger.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/logging/sql_logger.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/middleware/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/middleware/background_execution.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/middleware/context.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/middleware/cors.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/middleware/docs.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/middleware/exception.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/middleware/middleware.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/middleware/monitor_memory.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/middleware/mq.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/middleware/sandbox.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/middleware/timeout.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/middleware/traceid.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/models/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/models/base_http.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/models/log.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/models/mqlistener_config.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/models/mqmsg_model.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/models/mqsend_config.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/models/sandbox.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/models/sso_user.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/models/token_usage.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/models/token_usage_mysql.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/notice/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/notice/uvicorn_monitor.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/rabbitmq/process_pool_consumer.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/rabbitmq/rabbitmq_client.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/rabbitmq/rabbitmq_pool.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/rabbitmq/rabbitmq_service.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/rabbitmq/rabbitmq_service_client_manager.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/rabbitmq/rabbitmq_service_connection_monitor.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/rabbitmq/rabbitmq_service_consumer_manager.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/rabbitmq/rabbitmq_service_core.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/rabbitmq/rabbitmq_service_producer_manager.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/sentry/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/sentry/sy_sentry.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/services.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/sse/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/sse/event.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/sse/sse.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/synacos/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/synacos/example.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/synacos/example2.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/synacos/feign.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/synacos/feign_client.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/synacos/nacos_client_base.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/synacos/nacos_config_manager.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/synacos/nacos_heartbeat_manager.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/synacos/nacos_service.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/synacos/nacos_service_discovery.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/synacos/nacos_service_registration.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/synacos/param.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/tests/deep_agent_server.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/tests/test_deep_agent.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/tests/test_email.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/tests/test_mq.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/tools/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/tools/async_utils.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/tools/docs.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/tools/env.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/tools/merge_headers.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/tools/snowflake.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/tools/syemail.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/tools/timing.py +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon_python_lib.egg-info/SOURCES.txt +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon_python_lib.egg-info/dependency_links.txt +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon_python_lib.egg-info/entry_points.txt +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon_python_lib.egg-info/requires.txt +0 -0
- {sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon_python_lib.egg-info/top_level.txt +0 -0
{sycommon_python_lib-0.2.1a36 → sycommon_python_lib-0.2.1a37}/src/sycommon/agent/sandbox/file_ops.py
RENAMED
|
@@ -33,7 +33,7 @@ class FileOperationsMixin:
|
|
|
33
33
|
"""文件操作 Mixin
|
|
34
34
|
|
|
35
35
|
适配 deepagents 0.5.0a2 协议
|
|
36
|
-
|
|
36
|
+
同步方法为主实现,异步方法使用 aiohttp 实现
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
39
|
_timeout: int # 类型提示,子类需要提供此属性
|
|
@@ -46,6 +46,10 @@ class FileOperationsMixin:
|
|
|
46
46
|
"""发送同步 POST 请求"""
|
|
47
47
|
...
|
|
48
48
|
|
|
49
|
+
async def _post_async_with_failover(self: "HTTPSandboxBackend", endpoint: str, data: dict, timeout: int = None) -> dict:
|
|
50
|
+
"""发送异步 POST 请求(带故障转移)"""
|
|
51
|
+
...
|
|
52
|
+
|
|
49
53
|
def _ensure_synced_sync(self: "HTTPSandboxBackend"):
|
|
50
54
|
"""确保目录已同步(同步版本)"""
|
|
51
55
|
if not self._auto_sync:
|
|
@@ -72,6 +76,32 @@ class FileOperationsMixin:
|
|
|
72
76
|
self._workspace_verified = True
|
|
73
77
|
SYLogger.info(f"[Sandbox] 同步完成")
|
|
74
78
|
|
|
79
|
+
async def _ensure_synced_async(self: "HTTPSandboxBackend"):
|
|
80
|
+
"""确保目录已同步(异步版本)"""
|
|
81
|
+
if not self._auto_sync:
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
if self._synced and self._workspace_verified:
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
SYLogger.info(f"[Sandbox] 开始检查工作空间同步状态(异步)...")
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
workspace_exists = await self._check_workspace_exists_async()
|
|
91
|
+
except Exception as e:
|
|
92
|
+
SYLogger.warning(f"[Sandbox] 检查工作空间异常: {e},跳过检查")
|
|
93
|
+
workspace_exists = True
|
|
94
|
+
|
|
95
|
+
if not workspace_exists:
|
|
96
|
+
SYLogger.info(f"[Sandbox] 工作空间不存在或已被清空,重新同步...")
|
|
97
|
+
self._synced = False
|
|
98
|
+
|
|
99
|
+
if not self._synced:
|
|
100
|
+
SYLogger.info(f"[Sandbox] 开始同步目录(异步)...")
|
|
101
|
+
await self._sync_async()
|
|
102
|
+
self._workspace_verified = True
|
|
103
|
+
SYLogger.info(f"[Sandbox] 同步完成")
|
|
104
|
+
|
|
75
105
|
def _check_workspace_exists_sync(self: "HTTPSandboxBackend") -> bool:
|
|
76
106
|
"""检查沙箱工作空间是否存在(同步版本)"""
|
|
77
107
|
try:
|
|
@@ -84,10 +114,26 @@ class FileOperationsMixin:
|
|
|
84
114
|
SYLogger.warning(f"[Sandbox] 检查工作空间失败: {e}")
|
|
85
115
|
return False
|
|
86
116
|
|
|
117
|
+
async def _check_workspace_exists_async(self: "HTTPSandboxBackend") -> bool:
|
|
118
|
+
"""检查沙箱工作空间是否存在(异步版本)"""
|
|
119
|
+
try:
|
|
120
|
+
await self._post_async_with_failover(f"{SANDBOX_API_PREFIX}/ls", {
|
|
121
|
+
"path": "/",
|
|
122
|
+
"user_id": self.user_id
|
|
123
|
+
})
|
|
124
|
+
return True
|
|
125
|
+
except Exception as e:
|
|
126
|
+
SYLogger.warning(f"[Sandbox] 检查工作空间失败: {e}")
|
|
127
|
+
return False
|
|
128
|
+
|
|
87
129
|
def _sync_sync(self: "HTTPSandboxBackend"):
|
|
88
130
|
"""同步目录(同步版本,由子类实现)"""
|
|
89
131
|
...
|
|
90
132
|
|
|
133
|
+
async def _sync_async(self: "HTTPSandboxBackend") -> dict:
|
|
134
|
+
"""同步目录(异步版本,由子类实现)"""
|
|
135
|
+
...
|
|
136
|
+
|
|
91
137
|
# ============== 目录操作 ==============
|
|
92
138
|
|
|
93
139
|
def ls(self: "HTTPSandboxBackend", path: str) -> LsResult:
|
|
@@ -107,8 +153,20 @@ class FileOperationsMixin:
|
|
|
107
153
|
return LsResult(error=str(e))
|
|
108
154
|
|
|
109
155
|
async def als(self: "HTTPSandboxBackend", path: str) -> LsResult:
|
|
110
|
-
"""
|
|
111
|
-
|
|
156
|
+
"""异步列出目录内容(真正的异步实现)"""
|
|
157
|
+
try:
|
|
158
|
+
await self._ensure_synced_async()
|
|
159
|
+
SYLogger.info(f"[Sandbox] 异步列出目录: {path}")
|
|
160
|
+
result = await self._post_async_with_failover(f"{SANDBOX_API_PREFIX}/ls", {
|
|
161
|
+
"path": path,
|
|
162
|
+
"user_id": self.user_id
|
|
163
|
+
})
|
|
164
|
+
entries = [FileInfo(**item) for item in result]
|
|
165
|
+
SYLogger.info(f"[Sandbox] 目录内容: {len(entries)} 项")
|
|
166
|
+
return LsResult(entries=entries)
|
|
167
|
+
except Exception as e:
|
|
168
|
+
SYLogger.error(f"[Sandbox] 异步列出目录失败: {e}")
|
|
169
|
+
return LsResult(error=str(e))
|
|
112
170
|
|
|
113
171
|
# ============== 文件读写 ==============
|
|
114
172
|
|
|
@@ -153,8 +211,33 @@ class FileOperationsMixin:
|
|
|
153
211
|
offset: int = 0,
|
|
154
212
|
limit: int = 2000,
|
|
155
213
|
) -> ReadResult:
|
|
156
|
-
"""
|
|
157
|
-
|
|
214
|
+
"""异步读取文件内容(真正的异步实现)"""
|
|
215
|
+
try:
|
|
216
|
+
await self._ensure_synced_async()
|
|
217
|
+
SYLogger.info(f"[Sandbox] 异步读取文件: {file_path} (offset={offset}, limit={limit})")
|
|
218
|
+
result = await self._post_async_with_failover(f"{SANDBOX_API_PREFIX}/read", {
|
|
219
|
+
"file_path": file_path,
|
|
220
|
+
"user_id": self.user_id,
|
|
221
|
+
"offset": offset,
|
|
222
|
+
"limit": limit
|
|
223
|
+
})
|
|
224
|
+
if result.get("error"):
|
|
225
|
+
SYLogger.error(f"[Sandbox] 异步读取文件失败: {result['error']}")
|
|
226
|
+
return ReadResult(error=result["error"])
|
|
227
|
+
content = result.get("content", "")
|
|
228
|
+
SYLogger.info(f"[Sandbox] 异步读取完成: {len(content)} 字符")
|
|
229
|
+
from datetime import datetime
|
|
230
|
+
now = datetime.now().isoformat()
|
|
231
|
+
file_data = FileData(
|
|
232
|
+
content=content,
|
|
233
|
+
encoding="utf-8",
|
|
234
|
+
created_at=result.get("created_at", now),
|
|
235
|
+
modified_at=result.get("modified_at", now),
|
|
236
|
+
)
|
|
237
|
+
return ReadResult(file_data=file_data)
|
|
238
|
+
except Exception as e:
|
|
239
|
+
SYLogger.error(f"[Sandbox] 异步读取文件异常: {e}")
|
|
240
|
+
return ReadResult(error=str(e))
|
|
158
241
|
|
|
159
242
|
def write(
|
|
160
243
|
self: "HTTPSandboxBackend",
|
|
@@ -188,8 +271,27 @@ class FileOperationsMixin:
|
|
|
188
271
|
file_path: str,
|
|
189
272
|
content: str,
|
|
190
273
|
) -> WriteResult:
|
|
191
|
-
"""
|
|
192
|
-
|
|
274
|
+
"""异步写入文件(真正的异步实现)"""
|
|
275
|
+
try:
|
|
276
|
+
await self._ensure_synced_async()
|
|
277
|
+
SYLogger.info(f"[Sandbox] 异步写入文件: {file_path} ({len(content)} 字符)")
|
|
278
|
+
result = await self._post_async_with_failover(f"{SANDBOX_API_PREFIX}/write", {
|
|
279
|
+
"file_path": file_path,
|
|
280
|
+
"content": base64.b64encode(content.encode()).decode(),
|
|
281
|
+
"user_id": self.user_id
|
|
282
|
+
})
|
|
283
|
+
write_result = WriteResult(
|
|
284
|
+
error=result.get("error"),
|
|
285
|
+
path=result.get("path")
|
|
286
|
+
)
|
|
287
|
+
if write_result.error:
|
|
288
|
+
SYLogger.error(f"[Sandbox] 异步写入失败: {write_result.error}")
|
|
289
|
+
else:
|
|
290
|
+
SYLogger.info(f"[Sandbox] 异步写入成功: {write_result.path}")
|
|
291
|
+
return write_result
|
|
292
|
+
except Exception as e:
|
|
293
|
+
SYLogger.error(f"[Sandbox] 异步写入文件异常: {e}")
|
|
294
|
+
return WriteResult(error=str(e))
|
|
193
295
|
|
|
194
296
|
def edit(
|
|
195
297
|
self: "HTTPSandboxBackend",
|
|
@@ -224,8 +326,24 @@ class FileOperationsMixin:
|
|
|
224
326
|
new_string: str,
|
|
225
327
|
replace_all: bool = False,
|
|
226
328
|
) -> EditResult:
|
|
227
|
-
"""
|
|
228
|
-
|
|
329
|
+
"""异步编辑文件(真正的异步实现)"""
|
|
330
|
+
try:
|
|
331
|
+
await self._ensure_synced_async()
|
|
332
|
+
result = await self._post_async_with_failover(f"{SANDBOX_API_PREFIX}/edit", {
|
|
333
|
+
"file_path": file_path,
|
|
334
|
+
"old_string": base64.b64encode(old_string.encode()).decode(),
|
|
335
|
+
"new_string": base64.b64encode(new_string.encode()).decode(),
|
|
336
|
+
"user_id": self.user_id,
|
|
337
|
+
"replace_all": replace_all
|
|
338
|
+
})
|
|
339
|
+
return EditResult(
|
|
340
|
+
error=result.get("error"),
|
|
341
|
+
path=result.get("path"),
|
|
342
|
+
occurrences=result.get("occurrences")
|
|
343
|
+
)
|
|
344
|
+
except Exception as e:
|
|
345
|
+
SYLogger.error(f"[Sandbox] 异步编辑文件异常: {e}")
|
|
346
|
+
return EditResult(error=str(e))
|
|
229
347
|
|
|
230
348
|
# ============== 搜索 ==============
|
|
231
349
|
|
|
@@ -245,8 +363,19 @@ class FileOperationsMixin:
|
|
|
245
363
|
return GlobResult(error=str(e))
|
|
246
364
|
|
|
247
365
|
async def aglob(self: "HTTPSandboxBackend", pattern: str, path: str = "/") -> GlobResult:
|
|
248
|
-
"""异步 glob
|
|
249
|
-
|
|
366
|
+
"""异步 glob 搜索文件(真正的异步实现)"""
|
|
367
|
+
try:
|
|
368
|
+
await self._ensure_synced_async()
|
|
369
|
+
result = await self._post_async_with_failover(f"{SANDBOX_API_PREFIX}/glob", {
|
|
370
|
+
"pattern": pattern,
|
|
371
|
+
"path": path,
|
|
372
|
+
"user_id": self.user_id
|
|
373
|
+
})
|
|
374
|
+
matches = [FileInfo(**item) for item in result]
|
|
375
|
+
return GlobResult(matches=matches)
|
|
376
|
+
except Exception as e:
|
|
377
|
+
SYLogger.error(f"[Sandbox] 异步 glob 搜索失败: {e}")
|
|
378
|
+
return GlobResult(error=str(e))
|
|
250
379
|
|
|
251
380
|
def grep(
|
|
252
381
|
self: "HTTPSandboxBackend",
|
|
@@ -277,8 +406,22 @@ class FileOperationsMixin:
|
|
|
277
406
|
path: str = None,
|
|
278
407
|
glob: str = None,
|
|
279
408
|
) -> GrepResult:
|
|
280
|
-
"""
|
|
281
|
-
|
|
409
|
+
"""异步搜索文件内容(真正的异步实现)"""
|
|
410
|
+
try:
|
|
411
|
+
await self._ensure_synced_async()
|
|
412
|
+
result = await self._post_async_with_failover(f"{SANDBOX_API_PREFIX}/grep", {
|
|
413
|
+
"pattern": pattern,
|
|
414
|
+
"user_id": self.user_id,
|
|
415
|
+
"path": path,
|
|
416
|
+
"glob": glob
|
|
417
|
+
})
|
|
418
|
+
if isinstance(result, list):
|
|
419
|
+
matches = [GrepMatch(**item) for item in result]
|
|
420
|
+
return GrepResult(matches=matches)
|
|
421
|
+
return GrepResult(error="Unexpected result format")
|
|
422
|
+
except Exception as e:
|
|
423
|
+
SYLogger.error(f"[Sandbox] 异步 grep 搜索失败: {e}")
|
|
424
|
+
return GrepResult(error=str(e))
|
|
282
425
|
|
|
283
426
|
# ============== 兼容旧版 API(已废弃)==============
|
|
284
427
|
|
|
@@ -9,10 +9,13 @@ HTTP 客户端沙箱后端
|
|
|
9
9
|
import asyncio
|
|
10
10
|
import base64
|
|
11
11
|
import os
|
|
12
|
+
import ssl
|
|
12
13
|
import requests
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import Optional, List
|
|
15
16
|
|
|
17
|
+
import aiohttp
|
|
18
|
+
|
|
16
19
|
from sycommon.logging.kafka_log import SYLogger
|
|
17
20
|
from sycommon.agent.sandbox.file_ops import FileOperationsMixin, SANDBOX_API_PREFIX
|
|
18
21
|
|
|
@@ -23,6 +26,13 @@ from deepagents.backends.protocol import (
|
|
|
23
26
|
FileUploadResponse,
|
|
24
27
|
)
|
|
25
28
|
|
|
29
|
+
# SSL 上下文
|
|
30
|
+
try:
|
|
31
|
+
import certifi
|
|
32
|
+
_SSL_CONTEXT = ssl.create_default_context(cafile=certifi.where())
|
|
33
|
+
except ImportError:
|
|
34
|
+
_SSL_CONTEXT = ssl.create_default_context()
|
|
35
|
+
|
|
26
36
|
|
|
27
37
|
class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
|
|
28
38
|
"""
|
|
@@ -305,6 +315,58 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
|
|
|
305
315
|
"""发送同步 POST 请求(带故障转移)"""
|
|
306
316
|
return self._post_sync_with_failover(endpoint, data, timeout)
|
|
307
317
|
|
|
318
|
+
# ============== 内部方法 - 异步版本 ==============
|
|
319
|
+
|
|
320
|
+
async def _post_async(self, endpoint: str, data: dict, timeout: int = None) -> dict:
|
|
321
|
+
"""真正的异步 POST 请求(使用 aiohttp)"""
|
|
322
|
+
actual_timeout = timeout or self._timeout
|
|
323
|
+
if "/execute" in endpoint:
|
|
324
|
+
http_timeout = actual_timeout + 60
|
|
325
|
+
else:
|
|
326
|
+
http_timeout = actual_timeout + 30
|
|
327
|
+
|
|
328
|
+
trace_id = SYLogger.get_trace_id()
|
|
329
|
+
headers = {"Content-Type": "application/json"}
|
|
330
|
+
if trace_id:
|
|
331
|
+
headers["x-traceid-header"] = str(trace_id)
|
|
332
|
+
|
|
333
|
+
url = f"{self._base_url}{endpoint}"
|
|
334
|
+
SYLogger.info(f"[Sandbox] Async POST 请求开始: {url}, timeout={http_timeout}s")
|
|
335
|
+
|
|
336
|
+
connector = aiohttp.TCPConnector(ssl=_SSL_CONTEXT)
|
|
337
|
+
timeout_obj = aiohttp.ClientTimeout(total=http_timeout)
|
|
338
|
+
|
|
339
|
+
async with aiohttp.ClientSession(connector=connector, timeout=timeout_obj) as session:
|
|
340
|
+
async with session.post(url, json=data, headers=headers) as resp:
|
|
341
|
+
SYLogger.info(f"[Sandbox] Async POST 响应状态码: {resp.status}")
|
|
342
|
+
if resp.status >= 400:
|
|
343
|
+
text = await resp.text()
|
|
344
|
+
SYLogger.error(f"[Sandbox] HTTP 错误 {resp.status}: {text}")
|
|
345
|
+
resp.raise_for_status()
|
|
346
|
+
result = await resp.json()
|
|
347
|
+
SYLogger.info(f"[Sandbox] Async POST 成功: {url}")
|
|
348
|
+
return result
|
|
349
|
+
|
|
350
|
+
async def _post_async_with_failover(self, endpoint: str, data: dict, timeout: int = None) -> dict:
|
|
351
|
+
"""带故障转移的异步 POST 请求"""
|
|
352
|
+
max_retries = 3
|
|
353
|
+
|
|
354
|
+
for attempt in range(max_retries):
|
|
355
|
+
try:
|
|
356
|
+
return await self._post_async(endpoint, data, timeout)
|
|
357
|
+
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
|
358
|
+
SYLogger.warning(f"[Sandbox] Async 请求失败 (尝试 {attempt + 1}/{max_retries}): {self._base_url} - {e}")
|
|
359
|
+
|
|
360
|
+
if attempt < max_retries - 1:
|
|
361
|
+
# 尝试故障转移
|
|
362
|
+
switched = await asyncio.to_thread(self._refresh_from_nacos_and_switch_sync)
|
|
363
|
+
if not switched:
|
|
364
|
+
await asyncio.sleep(1)
|
|
365
|
+
else:
|
|
366
|
+
raise RuntimeError(f"沙箱服务不可用: {e}")
|
|
367
|
+
|
|
368
|
+
raise RuntimeError("沙箱服务不可用")
|
|
369
|
+
|
|
308
370
|
# ============== 属性 ==============
|
|
309
371
|
|
|
310
372
|
@property
|
|
@@ -351,8 +413,172 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
|
|
|
351
413
|
)
|
|
352
414
|
|
|
353
415
|
async def aexecute(self, command: str, *, timeout: int = None) -> ExecuteResponse:
|
|
354
|
-
"""异步执行 shell
|
|
355
|
-
|
|
416
|
+
"""异步执行 shell 命令(真正的异步实现)"""
|
|
417
|
+
SYLogger.info(f"[Sandbox] aexecute 开始: command={command}")
|
|
418
|
+
await self._ensure_synced_async()
|
|
419
|
+
SYLogger.info(f"[Sandbox] _ensure_synced_async 完成,开始执行命令: {command}")
|
|
420
|
+
try:
|
|
421
|
+
result = await self._post_async_with_failover(f"{SANDBOX_API_PREFIX}/execute", {
|
|
422
|
+
"command": command,
|
|
423
|
+
"user_id": self._user_id,
|
|
424
|
+
"timeout": timeout or self._timeout
|
|
425
|
+
})
|
|
426
|
+
response = ExecuteResponse(
|
|
427
|
+
output=result["output"],
|
|
428
|
+
exit_code=result["exit_code"],
|
|
429
|
+
truncated=result.get("truncated", False)
|
|
430
|
+
)
|
|
431
|
+
SYLogger.info(f"[Sandbox] 异步执行完成: exit_code={response.exit_code}")
|
|
432
|
+
return response
|
|
433
|
+
except Exception as e:
|
|
434
|
+
SYLogger.error(f"[Sandbox] 异步执行异常: {e}")
|
|
435
|
+
return ExecuteResponse(
|
|
436
|
+
output=f"Error: {type(e).__name__}: {e}",
|
|
437
|
+
exit_code=1,
|
|
438
|
+
truncated=False
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
async def _ensure_synced_async(self):
|
|
442
|
+
"""确保目录已同步(异步版本)"""
|
|
443
|
+
if not self._auto_sync:
|
|
444
|
+
return
|
|
445
|
+
|
|
446
|
+
if self._synced and self._workspace_verified:
|
|
447
|
+
return
|
|
448
|
+
|
|
449
|
+
SYLogger.info(f"[Sandbox] 开始检查工作空间同步状态(异步)...")
|
|
450
|
+
|
|
451
|
+
try:
|
|
452
|
+
workspace_exists = await self._check_workspace_exists_async()
|
|
453
|
+
except Exception as e:
|
|
454
|
+
SYLogger.warning(f"[Sandbox] 检查工作空间异常: {e},跳过检查")
|
|
455
|
+
workspace_exists = True
|
|
456
|
+
|
|
457
|
+
if not workspace_exists:
|
|
458
|
+
SYLogger.info(f"[Sandbox] 工作空间不存在或已被清空,重新同步...")
|
|
459
|
+
self._synced = False
|
|
460
|
+
|
|
461
|
+
if not self._synced:
|
|
462
|
+
SYLogger.info(f"[Sandbox] 开始同步目录(异步)...")
|
|
463
|
+
await self._sync_async()
|
|
464
|
+
self._workspace_verified = True
|
|
465
|
+
SYLogger.info(f"[Sandbox] 同步完成")
|
|
466
|
+
|
|
467
|
+
async def _check_workspace_exists_async(self) -> bool:
|
|
468
|
+
"""检查沙箱工作空间是否存在(异步版本)"""
|
|
469
|
+
try:
|
|
470
|
+
await self._post_async(f"{SANDBOX_API_PREFIX}/ls", {
|
|
471
|
+
"path": "/",
|
|
472
|
+
"user_id": self._user_id
|
|
473
|
+
})
|
|
474
|
+
return True
|
|
475
|
+
except Exception as e:
|
|
476
|
+
SYLogger.warning(f"[Sandbox] 检查工作空间失败: {e}")
|
|
477
|
+
return False
|
|
478
|
+
|
|
479
|
+
async def _sync_async(self) -> dict:
|
|
480
|
+
"""同步目录(异步版本)"""
|
|
481
|
+
if not self._sync_dirs:
|
|
482
|
+
SYLogger.info("[Sandbox] 没有配置同步目录")
|
|
483
|
+
return {}
|
|
484
|
+
|
|
485
|
+
SYLogger.info(f"[Sandbox] _sync_async 开始, 共 {len(self._sync_dirs)} 个目录")
|
|
486
|
+
results = {}
|
|
487
|
+
for idx, (local_path, remote_path) in enumerate(self._sync_dirs):
|
|
488
|
+
SYLogger.info(f"[Sandbox] 同步目录 [{idx+1}/{len(self._sync_dirs)}]: {local_path} -> {remote_path}")
|
|
489
|
+
local_dir = Path(local_path)
|
|
490
|
+
|
|
491
|
+
if not local_dir.exists():
|
|
492
|
+
SYLogger.error(f"[Sandbox] 本地目录不存在: {local_path}")
|
|
493
|
+
results[local_path] = {"success": 0, "failed": 0, "errors": [{"error": "目录不存在"}]}
|
|
494
|
+
continue
|
|
495
|
+
|
|
496
|
+
if local_dir.is_dir():
|
|
497
|
+
SYLogger.info(f"[Sandbox] 开始上传目录: {local_path}")
|
|
498
|
+
result = await self._upload_directory_async(str(local_dir), remote_path)
|
|
499
|
+
results[local_path] = result
|
|
500
|
+
SYLogger.info(f"[Sandbox] 目录上传完成: {local_path}, 成功={result['success']}, 失败={result['failed']}")
|
|
501
|
+
else:
|
|
502
|
+
# 单文件
|
|
503
|
+
SYLogger.info(f"[Sandbox] 上传单文件: {local_path}")
|
|
504
|
+
content = await asyncio.to_thread(lambda: open(local_dir, "rb").read())
|
|
505
|
+
upload_results = await self.aupload_files([(remote_path, content)])
|
|
506
|
+
if upload_results[0].error:
|
|
507
|
+
results[local_path] = {"success": 0, "failed": 1, "errors": [{"path": remote_path, "error": upload_results[0].error}]}
|
|
508
|
+
else:
|
|
509
|
+
results[local_path] = {"success": 1, "failed": 0, "errors": []}
|
|
510
|
+
|
|
511
|
+
self._synced = True
|
|
512
|
+
SYLogger.info("[Sandbox] _sync_async 完成")
|
|
513
|
+
return results
|
|
514
|
+
|
|
515
|
+
async def _upload_directory_async(self, local_dir: str, remote_path: str = "/") -> dict:
|
|
516
|
+
"""上传整个目录到沙箱(异步版本)"""
|
|
517
|
+
local_path = Path(local_dir)
|
|
518
|
+
if not local_path.exists():
|
|
519
|
+
raise FileNotFoundError(f"本地目录不存在: {local_dir}")
|
|
520
|
+
|
|
521
|
+
if not local_path.is_dir():
|
|
522
|
+
raise NotADirectoryError(f"路径不是目录: {local_dir}")
|
|
523
|
+
|
|
524
|
+
SYLogger.info(f"[Sandbox] _upload_directory_async 开始: {local_dir} -> {remote_path}")
|
|
525
|
+
|
|
526
|
+
exclude_patterns = [".git", "__pycache__", ".pyc", ".DS_Store", "node_modules"]
|
|
527
|
+
|
|
528
|
+
def should_exclude(name: str) -> bool:
|
|
529
|
+
for pattern in exclude_patterns:
|
|
530
|
+
if pattern.startswith("*"):
|
|
531
|
+
if name.endswith(pattern[1:]):
|
|
532
|
+
return True
|
|
533
|
+
elif name == pattern:
|
|
534
|
+
return True
|
|
535
|
+
return False
|
|
536
|
+
|
|
537
|
+
files_to_upload = []
|
|
538
|
+
|
|
539
|
+
for root, dirs, files in os.walk(local_path):
|
|
540
|
+
dirs[:] = [d for d in dirs if not should_exclude(d)]
|
|
541
|
+
|
|
542
|
+
for file in files:
|
|
543
|
+
if should_exclude(file):
|
|
544
|
+
continue
|
|
545
|
+
|
|
546
|
+
local_file = Path(root) / file
|
|
547
|
+
relative_path = local_file.relative_to(local_path)
|
|
548
|
+
sandbox_path = str(Path(remote_path) / relative_path)
|
|
549
|
+
files_to_upload.append((sandbox_path, local_file))
|
|
550
|
+
|
|
551
|
+
SYLogger.info(f"[Sandbox] 准备上传 {len(files_to_upload)} 个文件到沙箱")
|
|
552
|
+
|
|
553
|
+
success_count = 0
|
|
554
|
+
failed_count = 0
|
|
555
|
+
errors = []
|
|
556
|
+
|
|
557
|
+
for idx, (sandbox_path, local_file) in enumerate(files_to_upload):
|
|
558
|
+
try:
|
|
559
|
+
if idx % 10 == 0:
|
|
560
|
+
SYLogger.info(f"[Sandbox] 上传进度: {idx}/{len(files_to_upload)}")
|
|
561
|
+
|
|
562
|
+
content = await asyncio.to_thread(lambda f=local_file: open(f, "rb").read())
|
|
563
|
+
|
|
564
|
+
result = await self._post_async(f"{SANDBOX_API_PREFIX}/upload", {
|
|
565
|
+
"path": sandbox_path,
|
|
566
|
+
"content": base64.b64encode(content).decode(),
|
|
567
|
+
"user_id": self._user_id
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
if result.get("error"):
|
|
571
|
+
failed_count += 1
|
|
572
|
+
errors.append({"path": sandbox_path, "error": result["error"]})
|
|
573
|
+
else:
|
|
574
|
+
success_count += 1
|
|
575
|
+
|
|
576
|
+
except Exception as e:
|
|
577
|
+
failed_count += 1
|
|
578
|
+
errors.append({"path": sandbox_path, "error": str(e)})
|
|
579
|
+
|
|
580
|
+
SYLogger.info(f"[Sandbox] _upload_directory_async 完成: 成功={success_count}, 失败={failed_count}")
|
|
581
|
+
return {"success": success_count, "failed": failed_count, "errors": errors}
|
|
356
582
|
|
|
357
583
|
# ============== 后台进程执行 ==============
|
|
358
584
|
|
|
@@ -396,68 +622,46 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
|
|
|
396
622
|
return {"process_id": None, "status": "error", "error": str(e)}
|
|
397
623
|
|
|
398
624
|
async def aexecute_background(self, command: str, *, timeout: int = 600) -> dict:
|
|
399
|
-
"""异步后台执行 shell
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
625
|
+
"""异步后台执行 shell 命令(真正的异步实现)"""
|
|
626
|
+
await self._ensure_synced_async()
|
|
627
|
+
SYLogger.info(f"[Sandbox] 异步后台执行: {command}")
|
|
628
|
+
try:
|
|
629
|
+
result = await self._post_async_with_failover(f"{SANDBOX_API_PREFIX}/execute_background", {
|
|
630
|
+
"command": command,
|
|
631
|
+
"user_id": self._user_id,
|
|
632
|
+
"timeout": timeout
|
|
633
|
+
})
|
|
634
|
+
SYLogger.info(f"[Sandbox] 异步后台进程已启动: process_id={result['process_id']}")
|
|
635
|
+
return result
|
|
636
|
+
except Exception as e:
|
|
637
|
+
SYLogger.error(f"[Sandbox] 异步后台执行失败: {e}")
|
|
638
|
+
return {"process_id": None, "status": "error", "error": str(e)}
|
|
407
639
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
"process_id": str,
|
|
411
|
-
"status": "running" | "completed" | "timeout" | "error" | "not_found",
|
|
412
|
-
"exit_code": int | None,
|
|
413
|
-
"output": str | None, # 仅在进程结束时返回
|
|
414
|
-
"truncated": bool,
|
|
415
|
-
"command": str | None,
|
|
416
|
-
"started_at": str | None
|
|
417
|
-
}
|
|
418
|
-
"""
|
|
640
|
+
async def acheck_process(self, process_id: str) -> dict:
|
|
641
|
+
"""异步检查后台进程状态(真正的异步实现)"""
|
|
419
642
|
try:
|
|
420
|
-
result = self.
|
|
643
|
+
result = await self._post_async_with_failover(f"{SANDBOX_API_PREFIX}/process_status", {
|
|
421
644
|
"process_id": process_id,
|
|
422
645
|
"user_id": self._user_id
|
|
423
646
|
})
|
|
424
647
|
return result
|
|
425
648
|
except Exception as e:
|
|
426
|
-
SYLogger.error(f"[Sandbox]
|
|
649
|
+
SYLogger.error(f"[Sandbox] 异步检查进程失败: {e}")
|
|
427
650
|
return {"process_id": process_id, "status": "error", "error": str(e)}
|
|
428
651
|
|
|
429
|
-
async def
|
|
430
|
-
"""
|
|
431
|
-
return await asyncio.to_thread(self.check_process, process_id)
|
|
432
|
-
|
|
433
|
-
def kill_process(self, process_id: str) -> dict:
|
|
434
|
-
"""终止后台进程
|
|
435
|
-
|
|
436
|
-
Args:
|
|
437
|
-
process_id: execute_background 返回的进程 ID
|
|
438
|
-
|
|
439
|
-
Returns:
|
|
440
|
-
dict: {
|
|
441
|
-
"process_id": str,
|
|
442
|
-
"status": "killed" | "not_found" | "already_completed",
|
|
443
|
-
"output": str | None # 进程截止到被杀死时的输出
|
|
444
|
-
}
|
|
445
|
-
"""
|
|
652
|
+
async def akill_process(self, process_id: str) -> dict:
|
|
653
|
+
"""异步终止后台进程(真正的异步实现)"""
|
|
446
654
|
try:
|
|
447
|
-
result = self.
|
|
655
|
+
result = await self._post_async_with_failover(f"{SANDBOX_API_PREFIX}/kill_process", {
|
|
448
656
|
"process_id": process_id,
|
|
449
657
|
"user_id": self._user_id
|
|
450
658
|
})
|
|
451
|
-
SYLogger.info(f"[Sandbox]
|
|
659
|
+
SYLogger.info(f"[Sandbox] 异步进程终止: {process_id}, status={result['status']}")
|
|
452
660
|
return result
|
|
453
661
|
except Exception as e:
|
|
454
|
-
SYLogger.error(f"[Sandbox]
|
|
662
|
+
SYLogger.error(f"[Sandbox] 异步终止进程失败: {e}")
|
|
455
663
|
return {"process_id": process_id, "status": "error", "error": str(e)}
|
|
456
664
|
|
|
457
|
-
async def akill_process(self, process_id: str) -> dict:
|
|
458
|
-
"""异步终止后台进程"""
|
|
459
|
-
return await asyncio.to_thread(self.kill_process, process_id)
|
|
460
|
-
|
|
461
665
|
# ============== 批量操作 ==============
|
|
462
666
|
|
|
463
667
|
def upload_files(self, files: List[tuple[str, bytes]]) -> List[FileUploadResponse]:
|
|
@@ -479,8 +683,22 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
|
|
|
479
683
|
return results
|
|
480
684
|
|
|
481
685
|
async def aupload_files(self, files: List[tuple[str, bytes]]) -> List[FileUploadResponse]:
|
|
482
|
-
"""
|
|
483
|
-
|
|
686
|
+
"""异步上传多个文件(真正的异步实现)"""
|
|
687
|
+
results = []
|
|
688
|
+
for path, content in files:
|
|
689
|
+
try:
|
|
690
|
+
result = await self._post_async_with_failover(f"{SANDBOX_API_PREFIX}/upload", {
|
|
691
|
+
"path": path,
|
|
692
|
+
"content": base64.b64encode(content).decode(),
|
|
693
|
+
"user_id": self._user_id
|
|
694
|
+
})
|
|
695
|
+
results.append(FileUploadResponse(
|
|
696
|
+
path=result["path"],
|
|
697
|
+
error=result.get("error")
|
|
698
|
+
))
|
|
699
|
+
except Exception as e:
|
|
700
|
+
results.append(FileUploadResponse(path=path, error=str(e)))
|
|
701
|
+
return results
|
|
484
702
|
|
|
485
703
|
def download_files(self, paths: List[str]) -> List[FileDownloadResponse]:
|
|
486
704
|
"""下载多个文件"""
|
|
@@ -504,8 +722,25 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
|
|
|
504
722
|
return results
|
|
505
723
|
|
|
506
724
|
async def adownload_files(self, paths: List[str]) -> List[FileDownloadResponse]:
|
|
507
|
-
"""
|
|
508
|
-
|
|
725
|
+
"""异步下载多个文件(真正的异步实现)"""
|
|
726
|
+
results = []
|
|
727
|
+
for path in paths:
|
|
728
|
+
try:
|
|
729
|
+
result = await self._post_async_with_failover(
|
|
730
|
+
f"{SANDBOX_API_PREFIX}/download",
|
|
731
|
+
{"path": path, "user_id": self._user_id}
|
|
732
|
+
)
|
|
733
|
+
if result.get("error"):
|
|
734
|
+
results.append(FileDownloadResponse(
|
|
735
|
+
path=path,
|
|
736
|
+
error=result["error"]
|
|
737
|
+
))
|
|
738
|
+
else:
|
|
739
|
+
content = base64.b64decode(result["content"])
|
|
740
|
+
results.append(FileDownloadResponse(path=path, content=content))
|
|
741
|
+
except Exception as e:
|
|
742
|
+
results.append(FileDownloadResponse(path=path, error=str(e)))
|
|
743
|
+
return results
|
|
509
744
|
|
|
510
745
|
# ============== 目录同步 ==============
|
|
511
746
|
|
|
@@ -555,8 +790,8 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
|
|
|
555
790
|
return self._sync_sync()
|
|
556
791
|
|
|
557
792
|
async def async_sync(self) -> dict:
|
|
558
|
-
"""
|
|
559
|
-
return await
|
|
793
|
+
"""异步手动同步所有配置的目录到沙箱(真正的异步实现)"""
|
|
794
|
+
return await self._sync_async()
|
|
560
795
|
|
|
561
796
|
def sync_dirs(self, dirs: List[tuple[str, str]]) -> dict:
|
|
562
797
|
"""同步指定的目录列表到沙箱
|