"""sandbox-runner: tiny internal HTTP service. POST /run {code, files_in, timeout_s}.""" from __future__ import annotations import asyncio import os from concurrent.futures import ThreadPoolExecutor from fastapi import FastAPI, Header, HTTPException from pydantic import BaseModel, Field from runner import run TOKEN = os.environ.get("SANDBOX_TOKEN", "") MAX_PARALLEL = int(os.environ.get("SANDBOX_MAX_PARALLEL", "4")) app = FastAPI(title="uqo-chat sandbox-runner", docs_url=None, redoc_url=None) pool = ThreadPoolExecutor(max_workers=MAX_PARALLEL) sem = asyncio.Semaphore(MAX_PARALLEL) class RunReq(BaseModel): code: str = Field(..., max_length=200_000) files_in: list[dict] = Field(default_factory=list) timeout_s: int = Field(30, ge=1, le=60) @app.get("/healthz") async def healthz() -> dict: return {"ok": True} @app.post("/run") async def run_code(req: RunReq, authorization: str | None = Header(default=None)) -> dict: if TOKEN and authorization != f"Bearer {TOKEN}": raise HTTPException(401, "unauthorized") async with sem: loop = asyncio.get_running_loop() return await loop.run_in_executor(pool, run, req.code, req.files_in, req.timeout_s)