"""Terminal session manager for WebSocket connections.""" import asyncio import uuid from typing import Any from fastapi import WebSocket from src.services.terminal_session import TerminalSession class TerminalManager: """Manages active terminal sessions.""" def __init__(self) -> None: self._sessions: dict[str, TerminalSession] = {} async def create_session( self, instance_id: uuid.UUID, container_id: str, websocket: WebSocket, ) -> TerminalSession: """Create a new terminal session.""" session_id = str(uuid.uuid4()) session = TerminalSession(session_id, instance_id, container_id) await session.start() self._sessions[session_id] = session # Start background tasks for I/O streaming asyncio.create_task(self._read_loop(session, websocket)) asyncio.create_task(self._write_loop(session, websocket)) return session async def _read_loop(self, session: TerminalSession, websocket: WebSocket) -> None: """Read output from the container and send to WebSocket.""" try: while session.is_alive() and not session._closed: data = await session.read_output() if data: await websocket.send_bytes(data) else: await asyncio.sleep(0.01) except Exception: pass finally: await self._cleanup_session(session) async def _write_loop(self, session: TerminalSession, websocket: WebSocket) -> None: """Read input from WebSocket and send to container.""" try: while session.is_alive() and not session._closed: message = await websocket.receive() if message["type"] == "websocket.receive": if "bytes" in message: await session.write_input(message["bytes"]) elif "text" in message: text = message["text"] if text.startswith("{"): # Control message (JSON) import json try: ctrl = json.loads(text) if ctrl.get("type") == "resize": await session.resize( ctrl.get("cols", 80), ctrl.get("rows", 24), ) except json.JSONDecodeError: pass else: await session.write_input(text.encode("utf-8")) elif message["type"] == "websocket.disconnect": break except Exception: pass finally: await self._cleanup_session(session) async def _cleanup_session(self, session: TerminalSession) -> None: """Clean up a session.""" if session.session_id in self._sessions: del self._sessions[session.session_id] await session.close() async def close_all(self) -> None: """Close all active sessions.""" sessions = list(self._sessions.values()) self._sessions.clear() for session in sessions: await session.close() # Global terminal manager instance terminal_manager = TerminalManager()