""" FastAPI application for the AETHER-TaskFlow Environment. Endpoints: GET / - Interactive Mission Control Dashboard POST /reset - Reset the environment, return initial observation POST /step - Execute an action, return next observation GET /state - Return current internal state GET /schema - Return action/observation/state JSON schemas GET /health - Health check WS /ws - WebSocket for persistent sessions GET /docs - Swagger UI """ from __future__ import annotations import os import sys from pathlib import Path from typing import Any, Dict from fastapi import Body, HTTPException, status from fastapi.responses import HTMLResponse from openenv.core.env_server.http_server import create_app from openenv.core.env_server.types import ResetRequest, ResetResponse, SchemaResponse, StepResponse from openenv.core.env_server.web_interface import WebInterfaceManager from pydantic import ValidationError _REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(_REPO_ROOT)) try: from models import AetherTaskFlowAction, AetherTaskFlowObservation, AetherTaskFlowState from env.aether_env import AetherTaskFlowEnvironment except ModuleNotFoundError: sys.path.insert(0, str(_REPO_ROOT)) from models import AetherTaskFlowAction, AetherTaskFlowObservation, AetherTaskFlowState from env.aether_env import AetherTaskFlowEnvironment _DIFFICULTY = os.getenv("AETHER_DIFFICULTY", "easy") def _make_env() -> AetherTaskFlowEnvironment: difficulty = os.getenv("AETHER_DIFFICULTY", _DIFFICULTY) return AetherTaskFlowEnvironment(difficulty=difficulty) def _create_persistent_manager() -> WebInterfaceManager: temp_env = _make_env() metadata: dict = {} try: if hasattr(temp_env, "get_metadata"): metadata = temp_env.get_metadata() except Exception: pass finally: if hasattr(temp_env, "close"): try: temp_env.close() except Exception: pass return WebInterfaceManager(_make_env, AetherTaskFlowAction, AetherTaskFlowObservation, metadata=metadata) app = create_app( _make_env, AetherTaskFlowAction, AetherTaskFlowObservation, env_name="aether_taskflow", max_concurrent_envs=4, ) _persistent_manager = _create_persistent_manager() def _remove_route(path: str, method: str) -> None: app.router.routes = [ route for route in app.router.routes if not (getattr(route, "path", None) == path and method in (getattr(route, "methods", set()) or set())) ] for _path, _method in (("/reset", "POST"), ("/step", "POST"), ("/state", "GET"), ("/schema", "GET")): _remove_route(_path, _method) def _action_to_payload(action: Any) -> Dict[str, Any]: if hasattr(action, "model_dump"): return action.model_dump(exclude={"metadata"}) if isinstance(action, dict): return action raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Invalid action") def _extract_action_payload(payload: Dict[str, Any]) -> Dict[str, Any]: env = _persistent_manager.env if "message" in payload and isinstance(payload["message"], str): if hasattr(env, "message_to_action"): return _action_to_payload(env.message_to_action(payload["message"])) return {"message": payload["message"]} action_payload = payload.get("action", payload) if isinstance(action_payload, str): if hasattr(env, "message_to_action"): return _action_to_payload(env.message_to_action(action_payload)) return {"message": action_payload} if isinstance(action_payload, dict): if "message" in action_payload and isinstance(action_payload["message"], str): if hasattr(env, "message_to_action"): return _action_to_payload(env.message_to_action(action_payload["message"])) return {"message": action_payload["message"]} return action_payload raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Invalid payload") @app.get("/", response_class=HTMLResponse, tags=["UI"], summary="Mission Control Dashboard") async def root() -> HTMLResponse: difficulty = os.getenv("AETHER_DIFFICULTY", "easy") html = f"""