from __future__ import annotations from typing import Any from uuid import UUID from fastapi import APIRouter, Body, Depends, HTTPException, Response, status from pydantic import BaseModel, Field from src.application.agent_jobs import ( cancel_agent_job, claim_next_agent_job, complete_agent_job, create_test_codex_job, get_agent_job, list_agent_jobs, retry_agent_job, ) from src.domain.auth import ADMIN_ROLES from src.domain.contracts import ( AgentJobListResponse, AgentJobResponse, AgentJobTestCodexRequest, CurrentUser, ) from src.infrastructure.repositories import BackendRepository from src.presentation.dependencies import get_repository, require_roles router = APIRouter(prefix="/api", tags=["agent-jobs"]) internal_router = APIRouter(prefix="/internal/agent-jobs", include_in_schema=False) class AgentJobCompletionRequest(BaseModel): workspace_path: str | None = None stdout: str = "" stderr: str = "" exit_code: int | None = None duration_ms: int | None = Field(default=None, ge=0) output: dict[str, Any] = Field(default_factory=dict) @router.post( "/agent-jobs/test-codex", response_model=AgentJobResponse, status_code=status.HTTP_201_CREATED, ) def post_test_codex_job( request: AgentJobTestCodexRequest = Body( default_factory=AgentJobTestCodexRequest ), _: CurrentUser = Depends(require_roles(ADMIN_ROLES)), repository: BackendRepository = Depends(get_repository), ) -> AgentJobResponse: return create_test_codex_job(repository, request) @router.get("/agent-jobs", response_model=AgentJobListResponse) def get_agent_jobs( _: CurrentUser = Depends(require_roles(ADMIN_ROLES)), repository: BackendRepository = Depends(get_repository), ) -> AgentJobListResponse: return list_agent_jobs(repository) @router.get("/agent-jobs/{job_id}", response_model=AgentJobResponse) def get_agent_job_route( job_id: UUID, _: CurrentUser = Depends(require_roles(ADMIN_ROLES)), repository: BackendRepository = Depends(get_repository), ) -> AgentJobResponse: try: return get_agent_job(repository, job_id) except LookupError as error: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not Found") from error @router.post( "/agent-jobs/{job_id}/retry", response_model=AgentJobResponse, status_code=status.HTTP_201_CREATED, ) def post_retry_agent_job( job_id: UUID, current_user: CurrentUser = Depends(require_roles(ADMIN_ROLES)), repository: BackendRepository = Depends(get_repository), ) -> AgentJobResponse: try: return retry_agent_job( repository, job_id, actor_user_id=current_user.id, ) except LookupError as error: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not Found") from error except PermissionError as error: raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(error)) from error @router.post("/agent-jobs/{job_id}/cancel", response_model=AgentJobResponse) def post_cancel_agent_job( job_id: UUID, current_user: CurrentUser = Depends(require_roles(ADMIN_ROLES)), repository: BackendRepository = Depends(get_repository), ) -> AgentJobResponse: try: return cancel_agent_job( repository, job_id, actor_user_id=current_user.id, ) except LookupError as error: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not Found") from error @internal_router.post("/claim", response_model=None) def post_claim_agent_job( repository: BackendRepository = Depends(get_repository), ): claimed = claim_next_agent_job(repository) if claimed is None: return Response(status_code=status.HTTP_204_NO_CONTENT) return claimed @internal_router.post("/{job_id}/complete", response_model=AgentJobResponse) def post_complete_agent_job( job_id: UUID, request: AgentJobCompletionRequest, repository: BackendRepository = Depends(get_repository), ) -> AgentJobResponse: try: return complete_agent_job( repository, job_id=job_id, workspace_path=request.workspace_path, stdout=request.stdout, stderr=request.stderr, exit_code=request.exit_code, duration_ms=request.duration_ms, output=request.output, ) except LookupError as error: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not Found") from error