Aaron Brown
fix(app): mount Gradio dashboard at /dashboard to avoid /web conflict
0fd9230
"""FastAPI application for OpenRange."""
from __future__ import annotations
import inspect
import logging
import os
from fastapi import FastAPI
logger = logging.getLogger(__name__)
def _extract_openenv_server(fastapp: FastAPI) -> object | None:
"""Best-effort extraction of OpenEnv's HTTPEnvServer from route closure."""
for route in fastapp.router.routes:
if getattr(route, "path", None) != "/ws":
continue
endpoint = getattr(route, "endpoint", None)
if endpoint is None:
continue
try:
closure = inspect.getclosurevars(endpoint)
except Exception:
continue
server = closure.nonlocals.get("self")
if server is not None and hasattr(server, "active_sessions"):
return server
return None
def create_app() -> FastAPI:
"""Create the OpenRange app through the canonical OpenEnv factory."""
from openenv.core.env_server import create_app as create_openenv_app
from open_range.models import RangeAction, RangeObservation
from open_range.server.environment import RangeEnvironment
runtime = None
runtime_enabled = os.getenv("OPENRANGE_ENABLE_MANAGED_RUNTIME", "").lower() in {
"1",
"true",
"yes",
} or bool(os.getenv("OPENRANGE_RUNTIME_MANIFEST"))
if runtime_enabled:
from open_range.server.runtime import ManagedSnapshotRuntime
runtime = ManagedSnapshotRuntime.from_env()
def env_factory() -> RangeEnvironment:
return RangeEnvironment(runtime=runtime)
fastapp = create_openenv_app(
env_factory,
RangeAction,
RangeObservation,
env_name="open_range",
)
openenv_server = _extract_openenv_server(fastapp)
if openenv_server is not None:
fastapp.state.openenv_server = openenv_server
# Mount custom Gradio dashboard at /dashboard (separate from the OpenEnv
# Playground at /web which provides interactive reset/step via the
# WebInterfaceManager's persistent environment instance).
try:
import gradio as gr
from open_range.server.gradio_ui import build_openrange_gradio_app
blocks = build_openrange_gradio_app(
web_manager=None,
action_fields=[],
metadata=None,
is_chat_env=False,
title="OpenRange",
quick_start_md="",
)
fastapp = gr.mount_gradio_app(fastapp, blocks, path="/dashboard")
except Exception:
pass # Gradio is optional
fastapp.state.env = env_factory()
if runtime is not None:
fastapp.state.runtime = runtime
# NOTE: Do NOT register runtime.start() as a startup event — it
# synchronously generates snapshots which blocks the health check on
# resource-constrained hardware (HF Spaces cpu-basic). The runtime
# lazy-starts on the first acquire_snapshot() call (triggered by reset()).
fastapp.add_event_handler("shutdown", runtime.stop)
try:
from open_range.server.console import console_router
fastapp.include_router(console_router)
except Exception:
pass # Console router is optional
return fastapp
def main() -> None:
"""Run the installed package entrypoint via uvicorn."""
import uvicorn
uvicorn.run("open_range.server.app:app", host="0.0.0.0", port=8000)
app = create_app()
if __name__ == "__main__":
main()