"""API routes for evaluation runs. Thin HTTP translation layer: lifecycle orchestration and the ``/logs`` assembly live in :mod:`agenteval.services.runs`. """ from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from sqlmodel import Session from agenteval.models import RunTrigger from agenteval.services import runs as run_service from agenteval.storage.repository import RunRepository, ScenarioRepository, TargetRepository from agenteval.web.deps import get_db from agenteval.web.websocket import ws_manager router = APIRouter() class StartRunRequest(BaseModel): target_id: str scenario_id: str triggered_by: RunTrigger = RunTrigger.MANUAL def _progress_callback(run_id: str): return lambda event, data: ws_manager.emit(run_id, event, data) @router.get("") async def list_runs(session: Session = Depends(get_db)) -> list[dict]: scenario_names = ScenarioRepository(session).name_map() target_names = {t.id: t.name for t in TargetRepository(session).list_all()} return [ { **r.model_dump(), "scenario_name": scenario_names.get(r.scenario_id), "target_name": target_names.get(r.target_id), } for r in RunRepository(session).list_all() ] @router.post("") async def start_run( request: StartRunRequest, session: Session = Depends(get_db), ) -> dict: try: run = run_service.start_run( session, request.target_id, request.scenario_id, request.triggered_by, on_progress=_progress_callback, ) except run_service.RunStartError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return run.model_dump() @router.get("/{run_id}") async def get_run(run_id: str, session: Session = Depends(get_db)) -> dict: run = RunRepository(session).get(run_id) if not run: raise HTTPException(status_code=404, detail="run not found") return run.model_dump() @router.post("/{run_id}/cancel") async def cancel_run(run_id: str, session: Session = Depends(get_db)) -> dict: try: run = run_service.cancel_run(session, run_id) except run_service.RunNotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc except run_service.RunNotCancellableError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc return run.model_dump() @router.get("/{run_id}/logs") async def get_run_logs(run_id: str, session: Session = Depends(get_db)) -> dict: try: return run_service.build_run_logs(session, run_id) except run_service.RunNotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc