from fastapi import APIRouter, HTTPException, BackgroundTasks from typing import List, Optional from pydantic import BaseModel router = APIRouter() from src.domain.models import ( StandardResponse, SessionResponse, SessionDetailResponse, ChatRequest, ShellRequest, FileRequest, ShellResponse, FileResponse ) # --- Endpoints --- from src.application.session_manager import session_manager @router.put("/sessions", response_model=StandardResponse) async def create_session(): session = await session_manager.create_new_session() return {"code": 0, "msg": "success", "data": {"session_id": session.session_id}} @router.get("/sessions/{session_id}", response_model=StandardResponse) async def get_session(session_id: str): session = await session_manager.get_session_details(session_id) if not session: return {"code": 404, "msg": "Session not found", "data": None} return {"code": 0, "msg": "success", "data": {"session_id": session.session_id, "title": session.title, "events": []}} @router.get("/sessions", response_model=StandardResponse) async def list_sessions(): sessions = await session_manager.list_all_sessions() session_list = [ { "session_id": s.session_id, "title": s.title, "latest_message": s.messages[-1].content if s.messages else "", "latest_message_at": s.messages[-1].timestamp if s.messages else s.updated_at, "status": s.status, "unread_message_count": 0 } for s in sessions ] return {"code": 0, "msg": "success", "data": {"sessions": session_list}} @router.delete("/sessions/{session_id}", response_model=StandardResponse) async def delete_session(session_id: str): await session_manager.delete_session(session_id) return {"code": 0, "msg": "success", "data": None} @router.post("/sessions/{session_id}/stop", response_model=StandardResponse) async def stop_session(session_id: str): await session_manager.stop_session(session_id) return {"code": 0, "msg": "success", "data": None} from fastapi.responses import StreamingResponse from src.application.chat_service import ChatService from src.infrastructure.llm.gemini_client import GeminiAgent import os import json # Initialize services (Dependency Injection would be better in production) api_key = os.getenv("GEMINI_API_KEY") gemini_agent = GeminiAgent(api_key=api_key) chat_service = ChatService(session_manager, gemini_agent) @router.post("/sessions/{session_id}/chat") async def chat_session(session_id: str, request: ChatRequest): async def event_generator(): async for event in chat_service.chat(session_id, request.message): # Format as SSE yield f"event: {event['event']}\ndata: {json.dumps(event['data'])}\n\n" return StreamingResponse(event_generator(), media_type="text/event-stream") from src.infrastructure.sandbox.docker_manager import docker_sandbox @router.post("/sessions/{session_id}/shell", response_model=StandardResponse) async def view_shell(session_id: str, request: ShellRequest): if not docker_sandbox: return {"code": 500, "msg": "Sandbox service unavailable", "data": None} # In a real scenario, we might want to execute a command or get logs # For this API endpoint "view shell session content", it implies getting history # But the request body has "session_id" (shell session id). # For simplicity, we'll just return a mock or the last command output if we tracked it. # Let's assume we execute a 'whoami' just to prove it works, or return empty if no command provided. # Since the API spec says "View shell session output", let's assume we are retrieving logs # But our simple sandbox doesn't track shell sessions persistently yet. # We will return a placeholder or execute a dummy command to verify connectivity. output = await docker_sandbox.execute_shell(session_id, "echo 'Shell session active'") return { "code": 0, "msg": "success", "data": { "output": output, "session_id": request.session_id, "console": [{"ps1": "$", "command": "echo 'Shell session active'", "output": output}] } } @router.post("/sessions/{session_id}/file", response_model=StandardResponse) async def view_file(session_id: str, request: FileRequest): if not docker_sandbox: return {"code": 500, "msg": "Sandbox service unavailable", "data": None} content = await docker_sandbox.read_file(session_id, request.file) return {"code": 0, "msg": "success", "data": {"content": content, "file": request.file}} from fastapi import WebSocket, WebSocketDisconnect @router.websocket("/sessions/{session_id}/vnc") async def vnc_websocket(websocket: WebSocket, session_id: str): await websocket.accept(subprotocol="binary") try: # In a real implementation, we would connect to the VNC port of the container # and proxy the traffic. # For now, we just keep the connection open and echo back any data or send a placeholder. while True: data = await websocket.receive_bytes() # Echo or process await websocket.send_bytes(data) except WebSocketDisconnect: print(f"VNC Client disconnected for session {session_id}") except Exception as e: print(f"VNC Error: {e}") await websocket.close()