sycommon-python-lib 0.2.1a27__tar.gz → 0.2.1a29__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.1a27 → sycommon_python_lib-0.2.1a29}/PKG-INFO +1 -1
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/pyproject.toml +1 -1
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/sandbox/http_sandbox_backend.py +130 -9
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/middleware/sandbox.py +269 -146
- sycommon_python_lib-0.2.1a29/src/sycommon/models/sandbox.py +201 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon_python_lib.egg-info/PKG-INFO +1 -1
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon_python_lib.egg-info/SOURCES.txt +1 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/README.md +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/setup.cfg +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/command/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/command/cli.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/command/core/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/command/core/console.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/command/core/models.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/command/core/project.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/command/core/utils.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/command/templates/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/command/templates/agent/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/command/templates/base/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/command/templates/web/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/01_basic_agent.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/02_tool_agent.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/03_structured_output.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/04_memory_agent.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/05_streaming.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/06_multi_agent.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/07_skills_agent.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/08_middleware.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/09_interrupt.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/10_custom_llm.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/11_complex_workflow.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/12_batch_processing.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/middleware/01_basic_monitoring.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/middleware/02_permission_control.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/middleware/03_tool_skill_filter.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/middleware/04_caching_retry.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/middleware/05_sanitization.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/middleware/06_tracking.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/middleware/07_advanced.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/middleware/08_progressive_skills.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/middleware/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/middleware/override_examples.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/examples/virtual_employee_demo.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/exports.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/get_agent.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/sandbox/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/sandbox/file_ops.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/sandbox/sandbox_pool.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/sandbox/sandbox_recovery.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/sandbox/session.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/skills/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/skills/examples/faq_handler/scripts/search.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/skills/exports.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/agent/virtual_employee.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/config/Config.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/config/DatabaseConfig.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/config/ElasticsearchConfig.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/config/EmbeddingConfig.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/config/LLMConfig.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/config/LangfuseConfig.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/config/MQConfig.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/config/RedisConfig.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/config/RerankerConfig.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/config/SentryConfig.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/config/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/database/async_base_db_service.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/database/async_database_service.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/database/base_db_service.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/database/database_service.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/database/elasticsearch_service.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/database/redis_service.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/database/token_usage_db_service.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/health/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/health/health_check.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/health/metrics.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/health/ping.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/heartbeat_process/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/heartbeat_process/heartbeat_config.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/heartbeat_process/heartbeat_process_manager.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/heartbeat_process/heartbeat_process_worker.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/llm/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/llm/embedding.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/llm/get_llm.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/llm/llm_logger.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/llm/llm_tokens.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/llm/llm_with_token_tracking.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/llm/native_with_fallback_runnable.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/llm/output_fixing_runnable.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/llm/struct_token.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/llm/sy_langfuse.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/llm/token_usage_es_service.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/llm/token_usage_mysql_service.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/llm/usage_token.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/logging/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/logging/async_sql_logger.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/logging/kafka_log.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/logging/logger_levels.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/logging/logger_wrapper.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/logging/process_logger.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/logging/sql_logger.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/middleware/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/middleware/context.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/middleware/cors.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/middleware/docs.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/middleware/exception.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/middleware/middleware.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/middleware/monitor_memory.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/middleware/mq.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/middleware/timeout.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/middleware/traceid.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/models/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/models/base_http.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/models/log.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/models/mqlistener_config.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/models/mqmsg_model.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/models/mqsend_config.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/models/sso_user.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/models/token_usage.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/models/token_usage_mysql.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/notice/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/notice/uvicorn_monitor.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/rabbitmq/process_pool_consumer.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/rabbitmq/rabbitmq_client.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/rabbitmq/rabbitmq_pool.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/rabbitmq/rabbitmq_service.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/rabbitmq/rabbitmq_service_client_manager.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/rabbitmq/rabbitmq_service_connection_monitor.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/rabbitmq/rabbitmq_service_consumer_manager.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/rabbitmq/rabbitmq_service_core.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/rabbitmq/rabbitmq_service_producer_manager.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/sentry/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/sentry/sy_sentry.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/services.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/sse/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/sse/event.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/sse/sse.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/synacos/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/synacos/example.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/synacos/example2.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/synacos/feign.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/synacos/feign_client.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/synacos/nacos_client_base.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/synacos/nacos_config_manager.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/synacos/nacos_heartbeat_manager.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/synacos/nacos_service.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/synacos/nacos_service_discovery.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/synacos/nacos_service_registration.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/synacos/param.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/tests/deep_agent_server.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/tests/test_deep_agent.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/tests/test_email.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/tests/test_mq.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/tools/__init__.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/tools/async_utils.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/tools/docs.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/tools/env.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/tools/merge_headers.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/tools/snowflake.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/tools/syemail.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/tools/timing.py +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon_python_lib.egg-info/dependency_links.txt +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon_python_lib.egg-info/entry_points.txt +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon_python_lib.egg-info/requires.txt +0 -0
- {sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon_python_lib.egg-info/top_level.txt +0 -0
|
@@ -342,6 +342,110 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
|
|
|
342
342
|
"""异步执行 shell 命令"""
|
|
343
343
|
return await asyncio.to_thread(self.execute, command, timeout=timeout)
|
|
344
344
|
|
|
345
|
+
# ============== 后台进程执行 ==============
|
|
346
|
+
|
|
347
|
+
def execute_background(self, command: str, *, timeout: int = 600) -> dict:
|
|
348
|
+
"""后台执行 shell 命令(不阻塞,立即返回 process_id)
|
|
349
|
+
|
|
350
|
+
适用于长时间运行的服务(如 uvicorn、FastAPI 等)
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
command: 要执行的命令
|
|
354
|
+
timeout: 命令超时时间(秒),默认 600 秒
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
dict: {"process_id": str, "status": "started"}
|
|
358
|
+
|
|
359
|
+
Example:
|
|
360
|
+
# 启动后台服务
|
|
361
|
+
result = backend.execute_background("uvicorn main:app --host 0.0.0.0 --port 8000")
|
|
362
|
+
process_id = result["process_id"]
|
|
363
|
+
|
|
364
|
+
# 稍后检查状态
|
|
365
|
+
status = backend.check_process(process_id)
|
|
366
|
+
if status["status"] == "running":
|
|
367
|
+
print("服务正在运行...")
|
|
368
|
+
|
|
369
|
+
# 需要时终止进程
|
|
370
|
+
backend.kill_process(process_id)
|
|
371
|
+
"""
|
|
372
|
+
self._ensure_synced_sync()
|
|
373
|
+
SYLogger.info(f"[Sandbox] 后台执行: {command}")
|
|
374
|
+
try:
|
|
375
|
+
result = self._post_sync(f"{SANDBOX_API_PREFIX}/execute_background", {
|
|
376
|
+
"command": command,
|
|
377
|
+
"user_id": self._user_id,
|
|
378
|
+
"timeout": timeout
|
|
379
|
+
})
|
|
380
|
+
SYLogger.info(f"[Sandbox] 后台进程已启动: process_id={result['process_id']}")
|
|
381
|
+
return result
|
|
382
|
+
except Exception as e:
|
|
383
|
+
SYLogger.error(f"[Sandbox] 后台执行失败: {e}")
|
|
384
|
+
return {"process_id": None, "status": "error", "error": str(e)}
|
|
385
|
+
|
|
386
|
+
async def aexecute_background(self, command: str, *, timeout: int = 600) -> dict:
|
|
387
|
+
"""异步后台执行 shell 命令"""
|
|
388
|
+
return await asyncio.to_thread(self.execute_background, command, timeout=timeout)
|
|
389
|
+
|
|
390
|
+
def check_process(self, process_id: str) -> dict:
|
|
391
|
+
"""检查后台进程状态
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
process_id: execute_background 返回的进程 ID
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
dict: {
|
|
398
|
+
"process_id": str,
|
|
399
|
+
"status": "running" | "completed" | "timeout" | "error" | "not_found",
|
|
400
|
+
"exit_code": int | None,
|
|
401
|
+
"output": str | None, # 仅在进程结束时返回
|
|
402
|
+
"truncated": bool,
|
|
403
|
+
"command": str | None,
|
|
404
|
+
"started_at": str | None
|
|
405
|
+
}
|
|
406
|
+
"""
|
|
407
|
+
try:
|
|
408
|
+
result = self._post_sync(f"{SANDBOX_API_PREFIX}/process_status", {
|
|
409
|
+
"process_id": process_id,
|
|
410
|
+
"user_id": self._user_id
|
|
411
|
+
})
|
|
412
|
+
return result
|
|
413
|
+
except Exception as e:
|
|
414
|
+
SYLogger.error(f"[Sandbox] 检查进程失败: {e}")
|
|
415
|
+
return {"process_id": process_id, "status": "error", "error": str(e)}
|
|
416
|
+
|
|
417
|
+
async def acheck_process(self, process_id: str) -> dict:
|
|
418
|
+
"""异步检查后台进程状态"""
|
|
419
|
+
return await asyncio.to_thread(self.check_process, process_id)
|
|
420
|
+
|
|
421
|
+
def kill_process(self, process_id: str) -> dict:
|
|
422
|
+
"""终止后台进程
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
process_id: execute_background 返回的进程 ID
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
dict: {
|
|
429
|
+
"process_id": str,
|
|
430
|
+
"status": "killed" | "not_found" | "already_completed",
|
|
431
|
+
"output": str | None # 进程截止到被杀死时的输出
|
|
432
|
+
}
|
|
433
|
+
"""
|
|
434
|
+
try:
|
|
435
|
+
result = self._post_sync(f"{SANDBOX_API_PREFIX}/kill_process", {
|
|
436
|
+
"process_id": process_id,
|
|
437
|
+
"user_id": self._user_id
|
|
438
|
+
})
|
|
439
|
+
SYLogger.info(f"[Sandbox] 进程终止: {process_id}, status={result['status']}")
|
|
440
|
+
return result
|
|
441
|
+
except Exception as e:
|
|
442
|
+
SYLogger.error(f"[Sandbox] 终止进程失败: {e}")
|
|
443
|
+
return {"process_id": process_id, "status": "error", "error": str(e)}
|
|
444
|
+
|
|
445
|
+
async def akill_process(self, process_id: str) -> dict:
|
|
446
|
+
"""异步终止后台进程"""
|
|
447
|
+
return await asyncio.to_thread(self.kill_process, process_id)
|
|
448
|
+
|
|
345
449
|
# ============== 批量操作 ==============
|
|
346
450
|
|
|
347
451
|
def upload_files(self, files: List[tuple[str, bytes]]) -> List[FileUploadResponse]:
|
|
@@ -596,21 +700,38 @@ class HTTPSandboxBackend(FileOperationsMixin, SandboxBackendProtocol):
|
|
|
596
700
|
"""异步重置沙箱环境"""
|
|
597
701
|
return await asyncio.to_thread(self.reset)
|
|
598
702
|
|
|
599
|
-
def health_check(self) -> dict:
|
|
600
|
-
"""
|
|
703
|
+
def health_check(self, timeout: int = 10) -> dict:
|
|
704
|
+
"""健康检查(快速检查,默认10秒超时)"""
|
|
601
705
|
trace_id = SYLogger.get_trace_id()
|
|
602
706
|
headers = {}
|
|
603
707
|
if trace_id:
|
|
604
708
|
headers["x-traceid-header"] = str(trace_id)
|
|
605
709
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
710
|
+
url = f"{self._base_url}{SANDBOX_API_PREFIX}/health"
|
|
711
|
+
SYLogger.info(f"[Sandbox] 健康检查: {url}, timeout={timeout}s")
|
|
712
|
+
try:
|
|
713
|
+
resp = requests.get(
|
|
714
|
+
url,
|
|
715
|
+
headers=headers if headers else None,
|
|
716
|
+
timeout=timeout
|
|
717
|
+
)
|
|
718
|
+
resp.raise_for_status()
|
|
719
|
+
result = resp.json()
|
|
720
|
+
SYLogger.info(f"[Sandbox] 健康检查成功: {result}")
|
|
721
|
+
return result
|
|
722
|
+
except Exception as e:
|
|
723
|
+
SYLogger.error(f"[Sandbox] 健康检查失败: {e}")
|
|
724
|
+
raise
|
|
613
725
|
|
|
614
726
|
async def ahealth_check(self) -> dict:
|
|
615
727
|
"""异步健康检查"""
|
|
616
728
|
return await asyncio.to_thread(self.health_check)
|
|
729
|
+
|
|
730
|
+
def check_available(self, timeout: int = 5) -> bool:
|
|
731
|
+
"""快速检查沙箱是否可用(5秒超时)"""
|
|
732
|
+
try:
|
|
733
|
+
self.health_check(timeout=timeout)
|
|
734
|
+
return True
|
|
735
|
+
except Exception as e:
|
|
736
|
+
SYLogger.warning(f"[Sandbox] 沙箱不可用: {e}")
|
|
737
|
+
return False
|
{sycommon_python_lib-0.2.1a27 → sycommon_python_lib-0.2.1a29}/src/sycommon/middleware/sandbox.py
RENAMED
|
@@ -26,12 +26,34 @@ import base64
|
|
|
26
26
|
import shutil
|
|
27
27
|
import tempfile
|
|
28
28
|
import platform
|
|
29
|
+
import uuid
|
|
30
|
+
import threading
|
|
29
31
|
from datetime import datetime
|
|
30
32
|
from fastapi import FastAPI, APIRouter, Request, Header
|
|
31
|
-
from typing import
|
|
33
|
+
from typing import Dict
|
|
32
34
|
|
|
33
|
-
from pydantic import BaseModel
|
|
34
35
|
from sycommon.logging.kafka_log import SYLogger
|
|
36
|
+
from sycommon.models.sandbox import (
|
|
37
|
+
ExecuteRequest, ExecuteResponse,
|
|
38
|
+
UploadRequest, UploadResponse,
|
|
39
|
+
DownloadRequest, DownloadResponse,
|
|
40
|
+
FileInfo, LsRequest, LsResponse,
|
|
41
|
+
GlobRequest, GrepRequest, GrepMatch,
|
|
42
|
+
ReadRequest, ReadResponse,
|
|
43
|
+
WriteRequest, WriteResponse,
|
|
44
|
+
EditRequest, EditResponse,
|
|
45
|
+
HealthResponse, ResetRequest, ResetResponse,
|
|
46
|
+
ExecuteBackgroundRequest, ExecuteBackgroundResponse,
|
|
47
|
+
ProcessStatusRequest, ProcessStatusResponse,
|
|
48
|
+
KillProcessRequest, KillProcessResponse,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ============== 后台进程管理 ==============
|
|
53
|
+
|
|
54
|
+
# 进程存储: process_id -> {process: Popen, command: str, user_id: str, started_at: datetime, workspace: str}
|
|
55
|
+
_background_processes: Dict[str, dict] = {}
|
|
56
|
+
_process_lock = threading.Lock()
|
|
35
57
|
|
|
36
58
|
|
|
37
59
|
# ============== 常量配置 ==============
|
|
@@ -100,150 +122,6 @@ def resolve_sandbox_path(path: str, workspace: str) -> str:
|
|
|
100
122
|
return resolved
|
|
101
123
|
|
|
102
124
|
|
|
103
|
-
# ============== 请求/响应模型 ==============
|
|
104
|
-
|
|
105
|
-
class ExecuteRequest(BaseModel):
|
|
106
|
-
"""执行命令请求"""
|
|
107
|
-
command: str
|
|
108
|
-
user_id: str # 用户ID,用于隔离工作目录
|
|
109
|
-
timeout: Optional[int] = 120 # 默认超时 120 秒
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
class ExecuteResponse(BaseModel):
|
|
113
|
-
"""执行命令响应"""
|
|
114
|
-
output: str
|
|
115
|
-
exit_code: int
|
|
116
|
-
truncated: bool = False
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
class UploadRequest(BaseModel):
|
|
120
|
-
"""上传文件请求"""
|
|
121
|
-
path: str
|
|
122
|
-
content: str # base64 encoded
|
|
123
|
-
user_id: str
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
class UploadResponse(BaseModel):
|
|
127
|
-
"""上传文件响应"""
|
|
128
|
-
path: str
|
|
129
|
-
error: Optional[str] = None
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
class DownloadRequest(BaseModel):
|
|
133
|
-
"""下载文件请求"""
|
|
134
|
-
path: str
|
|
135
|
-
user_id: str
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
class DownloadResponse(BaseModel):
|
|
139
|
-
"""下载文件响应"""
|
|
140
|
-
path: str
|
|
141
|
-
content: Optional[str] = None # base64 encoded
|
|
142
|
-
error: Optional[str] = None
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
class FileInfo(BaseModel):
|
|
146
|
-
"""文件信息"""
|
|
147
|
-
path: str
|
|
148
|
-
is_dir: Optional[bool] = None
|
|
149
|
-
size: Optional[int] = None
|
|
150
|
-
modified_at: Optional[str] = None
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
class LsRequest(BaseModel):
|
|
154
|
-
"""列出目录请求"""
|
|
155
|
-
path: str
|
|
156
|
-
user_id: str
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
class LsResponse(BaseModel):
|
|
160
|
-
"""列出目录响应"""
|
|
161
|
-
files: list[FileInfo] = []
|
|
162
|
-
error: Optional[str] = None
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
class GlobRequest(BaseModel):
|
|
166
|
-
"""glob 搜索请求"""
|
|
167
|
-
pattern: str
|
|
168
|
-
path: str = "/"
|
|
169
|
-
user_id: str
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
class GrepRequest(BaseModel):
|
|
173
|
-
"""grep 搜索请求"""
|
|
174
|
-
pattern: str
|
|
175
|
-
user_id: str
|
|
176
|
-
path: Optional[str] = None
|
|
177
|
-
glob: Optional[str] = None
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
class GrepMatch(BaseModel):
|
|
181
|
-
"""grep 匹配结果"""
|
|
182
|
-
path: str
|
|
183
|
-
line: int
|
|
184
|
-
text: str
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
class ReadRequest(BaseModel):
|
|
188
|
-
"""读取文件请求"""
|
|
189
|
-
file_path: str
|
|
190
|
-
user_id: str
|
|
191
|
-
offset: int = 0
|
|
192
|
-
limit: int = 2000
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
class ReadResponse(BaseModel):
|
|
196
|
-
"""读取文件响应"""
|
|
197
|
-
content: Optional[str] = None
|
|
198
|
-
error: Optional[str] = None
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
class WriteRequest(BaseModel):
|
|
202
|
-
"""写入文件请求"""
|
|
203
|
-
file_path: str
|
|
204
|
-
content: str # base64 encoded
|
|
205
|
-
user_id: str
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
class WriteResponse(BaseModel):
|
|
209
|
-
"""写入文件响应"""
|
|
210
|
-
error: Optional[str] = None
|
|
211
|
-
path: Optional[str] = None
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
class EditRequest(BaseModel):
|
|
215
|
-
"""编辑文件请求"""
|
|
216
|
-
file_path: str
|
|
217
|
-
old_string: str # base64 encoded
|
|
218
|
-
new_string: str # base64 encoded
|
|
219
|
-
user_id: str
|
|
220
|
-
replace_all: bool = False
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
class EditResponse(BaseModel):
|
|
224
|
-
"""编辑文件响应"""
|
|
225
|
-
error: Optional[str] = None
|
|
226
|
-
path: Optional[str] = None
|
|
227
|
-
occurrences: Optional[int] = None
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
class HealthResponse(BaseModel):
|
|
231
|
-
"""健康检查响应"""
|
|
232
|
-
status: str
|
|
233
|
-
workspace: str
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
class ResetRequest(BaseModel):
|
|
237
|
-
"""重置请求"""
|
|
238
|
-
user_id: str
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
class ResetResponse(BaseModel):
|
|
242
|
-
"""重置响应"""
|
|
243
|
-
status: str
|
|
244
|
-
path: str
|
|
245
|
-
|
|
246
|
-
|
|
247
125
|
def setup_sandbox_handler(app: FastAPI, config: dict = None):
|
|
248
126
|
"""
|
|
249
127
|
沙箱服务初始化
|
|
@@ -448,6 +326,251 @@ COMMAND_EOF
|
|
|
448
326
|
truncated=False
|
|
449
327
|
)
|
|
450
328
|
|
|
329
|
+
@sandbox_router.post("/execute_background", response_model=ExecuteBackgroundResponse)
|
|
330
|
+
async def execute_background(req: ExecuteBackgroundRequest):
|
|
331
|
+
"""后台执行 shell 命令(不阻塞,立即返回 process_id)"""
|
|
332
|
+
workspace = get_user_workspace(req.user_id)
|
|
333
|
+
|
|
334
|
+
if not req.command:
|
|
335
|
+
raise ValueError("Command must be a non-empty string.")
|
|
336
|
+
|
|
337
|
+
process_id = str(uuid.uuid4())[:8]
|
|
338
|
+
SYLogger.info(f"[Sandbox Server] 后台执行命令: {req.command} (user={req.user_id}, process_id={process_id})")
|
|
339
|
+
|
|
340
|
+
# 创建常用目录
|
|
341
|
+
for subdir in ["tmp", "output", "skills", "memory", "data"]:
|
|
342
|
+
os.makedirs(os.path.join(workspace, subdir), exist_ok=True)
|
|
343
|
+
|
|
344
|
+
# 使用与 execute 相同的 shell 初始化脚本
|
|
345
|
+
init_script = f'''
|
|
346
|
+
# 沙箱环境初始化
|
|
347
|
+
export SANDBOX_ROOT="{workspace}"
|
|
348
|
+
export _SANDBOX_WORKSPACE="{workspace}"
|
|
349
|
+
|
|
350
|
+
sandbox_path() {{
|
|
351
|
+
local path="$1"
|
|
352
|
+
if [[ "$path" == "$_SANDBOX_WORKSPACE"* ]]; then
|
|
353
|
+
echo "$path"
|
|
354
|
+
return
|
|
355
|
+
fi
|
|
356
|
+
if [[ "$path" == /* ]]; then
|
|
357
|
+
path="${{path#/}}"
|
|
358
|
+
echo "$_SANDBOX_WORKSPACE/$path"
|
|
359
|
+
else
|
|
360
|
+
echo "$path"
|
|
361
|
+
fi
|
|
362
|
+
}}
|
|
363
|
+
|
|
364
|
+
cd() {{
|
|
365
|
+
local target="$1"
|
|
366
|
+
if [[ -z "$target" ]]; then
|
|
367
|
+
command cd "$_SANDBOX_WORKSPACE"
|
|
368
|
+
elif [[ "$target" == "$_SANDBOX_WORKSPACE"* ]]; then
|
|
369
|
+
command cd "$target"
|
|
370
|
+
elif [[ "$target" == /* ]]; then
|
|
371
|
+
local mapped="$_SANDBOX_WORKSPACE/${{target#/}}"
|
|
372
|
+
command cd "$mapped"
|
|
373
|
+
else
|
|
374
|
+
command cd "$target"
|
|
375
|
+
fi
|
|
376
|
+
}}
|
|
377
|
+
|
|
378
|
+
mkdir() {{
|
|
379
|
+
local args=("$@")
|
|
380
|
+
local new_args=()
|
|
381
|
+
for arg in "${{args[@]}}"; do
|
|
382
|
+
if [[ "$arg" == "$_SANDBOX_WORKSPACE"* ]]; then
|
|
383
|
+
new_args+=("$arg")
|
|
384
|
+
elif [[ "$arg" == /* ]]; then
|
|
385
|
+
new_args+=("$_SANDBOX_WORKSPACE/${{arg#/}}")
|
|
386
|
+
else
|
|
387
|
+
new_args+=("$arg")
|
|
388
|
+
fi
|
|
389
|
+
done
|
|
390
|
+
command mkdir "${{new_args[@]}}"
|
|
391
|
+
}}
|
|
392
|
+
|
|
393
|
+
cd "$_SANDBOX_WORKSPACE"
|
|
394
|
+
'''
|
|
395
|
+
|
|
396
|
+
full_command = f'''bash <<'COMMAND_EOF'
|
|
397
|
+
{init_script}
|
|
398
|
+
{req.command}
|
|
399
|
+
COMMAND_EOF
|
|
400
|
+
'''
|
|
401
|
+
|
|
402
|
+
env = os.environ.copy()
|
|
403
|
+
env["TMPDIR"] = os.path.join(workspace, "tmp")
|
|
404
|
+
env["TEMP"] = os.path.join(workspace, "tmp")
|
|
405
|
+
env["TMP"] = os.path.join(workspace, "tmp")
|
|
406
|
+
env["SANDBOX_ROOT"] = workspace
|
|
407
|
+
|
|
408
|
+
try:
|
|
409
|
+
process = subprocess.Popen(
|
|
410
|
+
full_command,
|
|
411
|
+
shell=True,
|
|
412
|
+
stdout=subprocess.PIPE,
|
|
413
|
+
stderr=subprocess.PIPE,
|
|
414
|
+
text=True,
|
|
415
|
+
cwd=workspace,
|
|
416
|
+
env=env
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
with _process_lock:
|
|
420
|
+
_background_processes[process_id] = {
|
|
421
|
+
"process": process,
|
|
422
|
+
"command": req.command,
|
|
423
|
+
"user_id": req.user_id,
|
|
424
|
+
"started_at": datetime.now(),
|
|
425
|
+
"workspace": workspace,
|
|
426
|
+
"timeout": req.timeout,
|
|
427
|
+
"output_buffer": "",
|
|
428
|
+
"completed": False,
|
|
429
|
+
"killed": False
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
# 启动后台线程监控进程
|
|
433
|
+
def monitor_process():
|
|
434
|
+
try:
|
|
435
|
+
stdout, stderr = process.communicate(timeout=req.timeout)
|
|
436
|
+
except subprocess.TimeoutExpired:
|
|
437
|
+
process.kill()
|
|
438
|
+
stdout, stderr = process.communicate()
|
|
439
|
+
with _process_lock:
|
|
440
|
+
if process_id in _background_processes:
|
|
441
|
+
_background_processes[process_id]["killed"] = True
|
|
442
|
+
_background_processes[process_id]["output_buffer"] = f"Error: Command timed out after {req.timeout} seconds.\nPartial output:\n{stdout}"
|
|
443
|
+
except Exception as e:
|
|
444
|
+
with _process_lock:
|
|
445
|
+
if process_id in _background_processes:
|
|
446
|
+
_background_processes[process_id]["output_buffer"] = f"Error: {type(e).__name__}: {e}"
|
|
447
|
+
return
|
|
448
|
+
|
|
449
|
+
output_parts = []
|
|
450
|
+
if stdout:
|
|
451
|
+
output_parts.append(stdout)
|
|
452
|
+
if stderr:
|
|
453
|
+
stderr_lines = stderr.strip().split("\n")
|
|
454
|
+
output_parts.extend(f"[stderr] {line}" for line in stderr_lines)
|
|
455
|
+
|
|
456
|
+
output = "\n".join(output_parts) if output_parts else "<no output>"
|
|
457
|
+
|
|
458
|
+
if len(output) > MAX_OUTPUT_BYTES:
|
|
459
|
+
output = output[:MAX_OUTPUT_BYTES] + f"\n\n... Output truncated at {MAX_OUTPUT_BYTES} bytes."
|
|
460
|
+
|
|
461
|
+
exit_code = process.returncode
|
|
462
|
+
if exit_code != 0:
|
|
463
|
+
output = f"{output.rstrip()}\n\nExit code: {exit_code}"
|
|
464
|
+
|
|
465
|
+
with _process_lock:
|
|
466
|
+
if process_id in _background_processes:
|
|
467
|
+
_background_processes[process_id]["output_buffer"] = output
|
|
468
|
+
_background_processes[process_id]["exit_code"] = exit_code
|
|
469
|
+
_background_processes[process_id]["completed"] = True
|
|
470
|
+
|
|
471
|
+
monitor_thread = threading.Thread(target=monitor_process, daemon=True)
|
|
472
|
+
monitor_thread.start()
|
|
473
|
+
|
|
474
|
+
return ExecuteBackgroundResponse(process_id=process_id, status="started")
|
|
475
|
+
|
|
476
|
+
except Exception as e:
|
|
477
|
+
SYLogger.error(f"[Sandbox Server] 后台执行异常: {e}")
|
|
478
|
+
raise RuntimeError(f"Failed to start background process: {e}")
|
|
479
|
+
|
|
480
|
+
@sandbox_router.post("/process_status", response_model=ProcessStatusResponse)
|
|
481
|
+
async def get_process_status(req: ProcessStatusRequest):
|
|
482
|
+
"""查询后台进程状态"""
|
|
483
|
+
with _process_lock:
|
|
484
|
+
proc_info = _background_processes.get(req.process_id)
|
|
485
|
+
|
|
486
|
+
if not proc_info:
|
|
487
|
+
return ProcessStatusResponse(
|
|
488
|
+
process_id=req.process_id,
|
|
489
|
+
status="not_found"
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
# 检查用户权限
|
|
493
|
+
if proc_info["user_id"] != req.user_id:
|
|
494
|
+
return ProcessStatusResponse(
|
|
495
|
+
process_id=req.process_id,
|
|
496
|
+
status="not_found"
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
process = proc_info["process"]
|
|
500
|
+
|
|
501
|
+
# 判断进程状态
|
|
502
|
+
if proc_info["killed"]:
|
|
503
|
+
status = "timeout"
|
|
504
|
+
elif proc_info["completed"]:
|
|
505
|
+
status = "completed"
|
|
506
|
+
elif process.poll() is not None:
|
|
507
|
+
# 进程已结束但还没被监控线程处理(极少情况)
|
|
508
|
+
status = "completed"
|
|
509
|
+
else:
|
|
510
|
+
status = "running"
|
|
511
|
+
|
|
512
|
+
return ProcessStatusResponse(
|
|
513
|
+
process_id=req.process_id,
|
|
514
|
+
status=status,
|
|
515
|
+
exit_code=proc_info.get("exit_code"),
|
|
516
|
+
output=proc_info.get("output_buffer") if proc_info["completed"] or proc_info["killed"] else None,
|
|
517
|
+
truncated=len(proc_info.get("output_buffer", "")) >= MAX_OUTPUT_BYTES if proc_info.get("output_buffer") else False,
|
|
518
|
+
command=proc_info["command"],
|
|
519
|
+
started_at=proc_info["started_at"].isoformat()
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
@sandbox_router.post("/kill_process", response_model=KillProcessResponse)
|
|
523
|
+
async def kill_process(req: KillProcessRequest):
|
|
524
|
+
"""终止后台进程"""
|
|
525
|
+
with _process_lock:
|
|
526
|
+
proc_info = _background_processes.get(req.process_id)
|
|
527
|
+
|
|
528
|
+
if not proc_info:
|
|
529
|
+
return KillProcessResponse(
|
|
530
|
+
process_id=req.process_id,
|
|
531
|
+
status="not_found"
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
# 检查用户权限
|
|
535
|
+
if proc_info["user_id"] != req.user_id:
|
|
536
|
+
return KillProcessResponse(
|
|
537
|
+
process_id=req.process_id,
|
|
538
|
+
status="not_found"
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
if proc_info["completed"] or proc_info["killed"]:
|
|
542
|
+
return KillProcessResponse(
|
|
543
|
+
process_id=req.process_id,
|
|
544
|
+
status="already_completed",
|
|
545
|
+
output=proc_info.get("output_buffer")
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
process = proc_info["process"]
|
|
549
|
+
try:
|
|
550
|
+
process.kill()
|
|
551
|
+
# 等待进程结束
|
|
552
|
+
process.wait(timeout=5)
|
|
553
|
+
except Exception as e:
|
|
554
|
+
SYLogger.warning(f"[Sandbox Server] 终止进程时出错: {e}")
|
|
555
|
+
|
|
556
|
+
# 获取当前输出(如果有)
|
|
557
|
+
current_output = proc_info.get("output_buffer", "")
|
|
558
|
+
proc_info["killed"] = True
|
|
559
|
+
proc_info["completed"] = True
|
|
560
|
+
proc_info["exit_code"] = process.returncode if process.returncode is not None else -9
|
|
561
|
+
|
|
562
|
+
if not current_output:
|
|
563
|
+
current_output = f"Process killed by user request. Exit code: {proc_info['exit_code']}"
|
|
564
|
+
proc_info["output_buffer"] = current_output
|
|
565
|
+
|
|
566
|
+
SYLogger.info(f"[Sandbox Server] 进程已终止: {req.process_id}")
|
|
567
|
+
|
|
568
|
+
return KillProcessResponse(
|
|
569
|
+
process_id=req.process_id,
|
|
570
|
+
status="killed",
|
|
571
|
+
output=current_output
|
|
572
|
+
)
|
|
573
|
+
|
|
451
574
|
@sandbox_router.post("/ls", response_model=list[FileInfo])
|
|
452
575
|
async def ls_info(req: LsRequest):
|
|
453
576
|
"""列出目录内容"""
|