from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import text from sqlalchemy.orm import Session from core_domain import ServiceHealth from app.application.services import AgentApplicationService, build_agent_application_service from app.bootstrap.settings import AgentServiceSettings from app.db.session import get_db from app.schemas.agent import ( AgentCreateRequest, AgentResponse, AgentRunCreateRequest, AgentRunExecuteRequest, AgentRunExecuteResponse, AgentRunResponse, AgentRunStatusUpdateRequest, AgentStatusUpdateRequest, AgentToolInvocationResponse, AgentWorkerExecuteNextRequest, AgentWorkerExecuteNextResponse, AgentVersionCreateRequest, AgentVersionResponse, ) router = APIRouter() def get_agent_service_settings() -> AgentServiceSettings: return AgentServiceSettings() def get_agent_application_service( db: Session = Depends(get_db), settings: AgentServiceSettings = Depends(get_agent_service_settings), ) -> AgentApplicationService: return build_agent_application_service(db=db, settings=settings) @router.get("/health", response_model=ServiceHealth) def health_check(db: Session = Depends(get_db)) -> ServiceHealth: db.execute(text("SELECT 1")) return ServiceHealth(service="agent-service", status="ok", database="ok") @router.post("", response_model=AgentResponse) def create_agent( payload: AgentCreateRequest, service: AgentApplicationService = Depends(get_agent_application_service), ) -> AgentResponse: entity = service.create_agent(payload) return AgentResponse.from_entity(entity) @router.get("", response_model=list[AgentResponse]) def list_agents( tenant_id: str = Query(...), service: AgentApplicationService = Depends(get_agent_application_service), ) -> list[AgentResponse]: return [AgentResponse.from_entity(item) for item in service.list_agents(tenant_id=tenant_id)] @router.patch("/{agent_id}/status", response_model=AgentResponse) def update_agent_status( agent_id: str, payload: AgentStatusUpdateRequest, service: AgentApplicationService = Depends(get_agent_application_service), ) -> AgentResponse: entity = service.update_agent_status(agent_id=agent_id, payload=payload) if entity is None: raise HTTPException(status_code=404, detail=f"agent not found: {agent_id}") return AgentResponse.from_entity(entity) @router.post("/versions", response_model=AgentVersionResponse) def create_agent_version( payload: AgentVersionCreateRequest, service: AgentApplicationService = Depends(get_agent_application_service), ) -> AgentVersionResponse: try: entity = service.create_agent_version(payload) except ValueError as exc: raise HTTPException(status_code=422, detail=str(exc)) from exc return AgentVersionResponse.from_entity(entity) @router.get("/versions", response_model=list[AgentVersionResponse]) def list_agent_versions( tenant_id: str = Query(...), agent_id: str = Query(...), service: AgentApplicationService = Depends(get_agent_application_service), ) -> list[AgentVersionResponse]: return [ AgentVersionResponse.from_entity(item) for item in service.list_agent_versions(tenant_id=tenant_id, agent_id=agent_id) ] @router.post("/runs", response_model=AgentRunResponse) def create_agent_run( payload: AgentRunCreateRequest, service: AgentApplicationService = Depends(get_agent_application_service), ) -> AgentRunResponse: try: entity = service.create_agent_run(payload) except ValueError as exc: raise HTTPException(status_code=422, detail=str(exc)) from exc return AgentRunResponse.from_entity(entity) @router.get("/runs", response_model=list[AgentRunResponse]) def list_agent_runs( tenant_id: str = Query(...), agent_id: str | None = Query(default=None), session_id: str | None = Query(default=None), service: AgentApplicationService = Depends(get_agent_application_service), ) -> list[AgentRunResponse]: return [ AgentRunResponse.from_entity(item) for item in service.list_agent_runs( tenant_id=tenant_id, agent_id=agent_id, session_id=session_id, ) ] @router.get( "/runs/{agent_run_id}/tool-invocations", response_model=list[AgentToolInvocationResponse], ) def list_agent_tool_invocations( agent_run_id: str, tenant_id: str = Query(...), service: AgentApplicationService = Depends(get_agent_application_service), ) -> list[AgentToolInvocationResponse]: return [ AgentToolInvocationResponse.from_entity(item) for item in service.list_agent_tool_invocations( tenant_id=tenant_id, agent_run_id=agent_run_id, ) ] @router.post("/runs/{agent_run_id}/status", response_model=AgentRunResponse) def update_agent_run_status( agent_run_id: str, payload: AgentRunStatusUpdateRequest, service: AgentApplicationService = Depends(get_agent_application_service), ) -> AgentRunResponse: entity = service.update_agent_run_status(agent_run_id=agent_run_id, payload=payload) if entity is None: raise HTTPException(status_code=404, detail=f"agent_run not found: {agent_run_id}") return AgentRunResponse.from_entity(entity) @router.post("/runs/{agent_run_id}/execute", response_model=AgentRunExecuteResponse) def execute_agent_run( agent_run_id: str, payload: AgentRunExecuteRequest, service: AgentApplicationService = Depends(get_agent_application_service), ) -> AgentRunExecuteResponse: entity = service.execute_agent_run(agent_run_id=agent_run_id, payload=payload) if entity is None: raise HTTPException(status_code=404, detail=f"agent_run not found: {agent_run_id}") output_json = entity.output_json or {} model_value = output_json.get("model") dry_run_value = output_json.get("dry_run") return AgentRunExecuteResponse( run=AgentRunResponse.from_entity(entity), model=model_value if isinstance(model_value, str) else None, dry_run=dry_run_value if isinstance(dry_run_value, bool) else False, ) @router.post("/workers/execute-next", response_model=AgentWorkerExecuteNextResponse) def execute_next_worker_task( payload: AgentWorkerExecuteNextRequest, settings: AgentServiceSettings = Depends(get_agent_service_settings), service: AgentApplicationService = Depends(get_agent_application_service), ) -> AgentWorkerExecuteNextResponse: result = service.execute_next_claimed_agent_run( worker_key=payload.worker_key, lease_seconds=payload.lease_seconds or settings.worker_lease_seconds, dry_run=payload.dry_run if payload.dry_run is not None else settings.worker_dry_run, ) if result is None: raise HTTPException(status_code=404, detail="queued agent_run not found") entity, released_lease_count = result output_json = entity.output_json or {} model_value = output_json.get("model") dry_run_value = output_json.get("dry_run") return AgentWorkerExecuteNextResponse( run=AgentRunResponse.from_entity(entity), model=model_value if isinstance(model_value, str) else None, dry_run=dry_run_value if isinstance(dry_run_value, bool) else False, released_lease_count=released_lease_count, )