from __future__ import annotations from typing import Annotated from fastapi import APIRouter, Header, Path, Query, Request, status from app.copilot.schemas import ( CopilotCapabilities, CopilotContextInput, CopilotExecuteRequest, CopilotRun, CopilotRunCreate, CopilotRunList, ) from app.security.errors import ForbiddenError router = APIRouter(prefix="/v1/copilot", tags=["copilot"]) def _identity(request: Request): context = request.state.auth if not context.workspace_id or not context.user_id: raise ForbiddenError return context @router.post("/capabilities", response_model=CopilotCapabilities) async def capabilities(request: Request, context: CopilotContextInput) -> CopilotCapabilities: identity = _identity(request) return await request.app.state.container.copilot.capabilities( workspace_id=identity.workspace_id, user_id=identity.user_id, context=context, ) @router.post("/runs", response_model=CopilotRun, status_code=status.HTTP_201_CREATED) async def create_run( request: Request, payload: CopilotRunCreate, idempotency_key: Annotated[str, Header(alias="Idempotency-Key", min_length=8, max_length=255)], ) -> CopilotRun: identity = _identity(request) return await request.app.state.container.copilot.create_run( workspace_id=identity.workspace_id, user_id=identity.user_id, api_key_id=identity.api_key_id, request_id=request.state.request_id, payload=payload, idempotency_key=idempotency_key, ) @router.get("/runs", response_model=CopilotRunList) async def list_runs( request: Request, offset: Annotated[int, Query(ge=0)] = 0, limit: Annotated[int, Query(ge=1, le=100)] = 25, ) -> CopilotRunList: identity = _identity(request) return await request.app.state.container.copilot.list( workspace_id=identity.workspace_id, user_id=identity.user_id, offset=offset, limit=limit, ) @router.get("/history", response_model=CopilotRunList) async def history( request: Request, offset: Annotated[int, Query(ge=0)] = 0, limit: Annotated[int, Query(ge=1, le=100)] = 25, ) -> CopilotRunList: return await list_runs(request, offset, limit) @router.get("/runs/{run_id}", response_model=CopilotRun) async def get_run( request: Request, run_id: Annotated[str, Path(min_length=36, max_length=36)], ) -> CopilotRun: identity = _identity(request) return await request.app.state.container.copilot.get( workspace_id=identity.workspace_id, user_id=identity.user_id, run_id=run_id, ) @router.post("/runs/{run_id}/execute", response_model=CopilotRun) async def execute_run( request: Request, payload: CopilotExecuteRequest, run_id: Annotated[str, Path(min_length=36, max_length=36)], ) -> CopilotRun: identity = _identity(request) return await request.app.state.container.copilot.execute( workspace_id=identity.workspace_id, user_id=identity.user_id, api_key_id=identity.api_key_id, request_id=request.state.request_id, run_id=run_id, payload=payload, permissions=identity.scopes, ) @router.post("/runs/{run_id}/cancel", response_model=CopilotRun) async def cancel_run( request: Request, run_id: Annotated[str, Path(min_length=36, max_length=36)], ) -> CopilotRun: identity = _identity(request) return await request.app.state.container.copilot.cancel( workspace_id=identity.workspace_id, user_id=identity.user_id, api_key_id=identity.api_key_id, request_id=request.state.request_id, run_id=run_id, )