from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, status from adapter.di.container import AppContainer from adapter.http.fastapi.dependencies import ( get_container, get_create_sandbox, get_delete_sandbox, ) from adapter.http.fastapi.schemas import ( CreateSandboxRequest, DeleteSandboxResponse, ErrorResponse, HealthResponse, SandboxEndpointResponse, SandboxSessionResponse, ) from domain.error import SandboxConflictError, SandboxError, SandboxStartError from domain.sandbox import SandboxSession from usecase.sandbox import ( CreateSandbox, CreateSandboxCommand, DeleteSandbox, DeleteSandboxCommand, DeleteSandboxResult, ) router = APIRouter() @router.get( '/health', response_model=HealthResponse, status_code=status.HTTP_200_OK, ) def health(container: AppContainer = Depends(get_container)) -> HealthResponse: return HealthResponse( status='ok', app=container.config.app.name, env=container.config.app.env, ) @router.post( '/create', response_model=SandboxSessionResponse, responses={ status.HTTP_409_CONFLICT: {'model': ErrorResponse}, status.HTTP_503_SERVICE_UNAVAILABLE: {'model': ErrorResponse}, status.HTTP_500_INTERNAL_SERVER_ERROR: {'model': ErrorResponse}, }, status_code=status.HTTP_200_OK, ) def create_sandbox( request: CreateSandboxRequest, usecase: CreateSandbox = Depends(get_create_sandbox), ) -> SandboxSessionResponse: try: session = usecase.execute( CreateSandboxCommand( chat_id=request.chat_id, agent_id=request.agent_id, volume_host_path=request.volume_host_path, ) ) except SandboxConflictError as exc: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=str(exc), ) from exc except SandboxStartError as exc: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(exc), ) from exc except SandboxError as exc: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc), ) from exc return _to_sandbox_session_response(session) @router.delete( '/sandboxes/{chat_id}', response_model=DeleteSandboxResponse, responses={ status.HTTP_500_INTERNAL_SERVER_ERROR: {'model': ErrorResponse}, }, status_code=status.HTTP_200_OK, ) def delete_sandbox( chat_id: UUID, usecase: DeleteSandbox = Depends(get_delete_sandbox), ) -> DeleteSandboxResponse: try: result = usecase.execute(DeleteSandboxCommand(chat_id=chat_id)) except SandboxError as exc: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc), ) from exc return _to_delete_sandbox_response(result) def _to_sandbox_session_response(session: SandboxSession) -> SandboxSessionResponse: if session.endpoint is None: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail='sandbox_endpoint_unavailable', ) return SandboxSessionResponse( session_id=session.session_id, chat_id=session.chat_id, agent_id=session.agent_id, volume_host_path=session.volume_host_path, container_id=session.container_id, endpoint=SandboxEndpointResponse( ip=session.endpoint.ip, port=session.endpoint.port, ), status=session.status.value, expires_at=session.expires_at, ) def _to_delete_sandbox_response(result: DeleteSandboxResult) -> DeleteSandboxResponse: return DeleteSandboxResponse( chat_id=result.chat_id, result=result.result, session_id=result.session_id, container_id=result.container_id, )