from __future__ import annotations import os from pathlib import Path from typing import Any from uuid import uuid4 from fastapi import FastAPI, Response, status from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from .agent import ResumeAgent from .database import Database from .fsm import FSMError from .llm_services import build_services from .models import ( ActionResponse, ComponentEventRequest, CreateResumeRequest, CreateResumeResponse, CreateSessionRequest, ErrorDetail, MessageRequest, TimelineResponse, ) from .services import ( ExperienceExtractor, ResumeRewriter, ) from .settings import Settings, load_settings API_PREFIX = "/ai-api/resume-agent" def create_app( *, database_path: str | Path | None = None, extractor: ExperienceExtractor | None = None, rewriter: ResumeRewriter | None = None, cors_origins: list[str] | None = None, settings: Settings | None = None, openai_client: Any | None = None, ) -> FastAPI: default_database = Path(__file__).resolve().parent.parent / "data" / "resume_agent.db" database = Database(database_path or os.getenv("RESUME_AGENT_DATABASE", default_database)) database.initialize() if extractor is None or rewriter is None: default_extractor, default_rewriter = build_services( settings or load_settings(), openai_client ) extractor = extractor or default_extractor rewriter = rewriter or default_rewriter agent = ResumeAgent(database, extractor, rewriter) application = FastAPI( title="Resume Agent MVP", version="0.1.0", description="SQLite-backed resume workflow implemented as an explicit finite-state machine.", ) origins = cors_origins or _cors_origins_from_environment() application.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials="*" not in origins, allow_methods=["GET", "POST", "DELETE", "OPTIONS"], allow_headers=["*"], ) application.state.database = database application.state.resume_agent = agent @application.exception_handler(FSMError) async def handle_fsm_error(_request: Any, exc: FSMError) -> JSONResponse: trace_id = f"trace_{uuid4().hex}" detail = ErrorDetail( code=exc.code, message=exc.message, missing_fields=exc.missing_fields, trace_id=trace_id, ) return JSONResponse( status_code=exc.status_code, content={"error": detail.model_dump(mode="json"), "trace_id": trace_id}, ) @application.get("/health", tags=["system"]) def health() -> dict[str, str]: return {"status": "ok"} @application.post( f"{API_PREFIX}/sessions", response_model=TimelineResponse, status_code=status.HTTP_201_CREATED, tags=["resume-agent"], ) def create_session(request: CreateSessionRequest | None = None) -> TimelineResponse: return agent.create_session(request or CreateSessionRequest()) @application.get( f"{API_PREFIX}/sessions/{{session_id}}/timeline", response_model=TimelineResponse, tags=["resume-agent"], ) def get_timeline(session_id: str) -> TimelineResponse: return agent.timeline(session_id) @application.post( f"{API_PREFIX}/sessions/{{session_id}}/component-events", response_model=ActionResponse, tags=["resume-agent"], ) def post_component_event( session_id: str, request: ComponentEventRequest ) -> ActionResponse: return agent.component_event(session_id, request) @application.post( f"{API_PREFIX}/sessions/{{session_id}}/messages", response_model=ActionResponse, tags=["resume-agent"], ) def post_message(session_id: str, request: MessageRequest) -> ActionResponse: return agent.add_message(session_id, request) @application.post( f"{API_PREFIX}/sessions/{{session_id}}/create", response_model=CreateResumeResponse, tags=["resume-agent"], ) def create_resume( session_id: str, request: CreateResumeRequest | None = None ) -> CreateResumeResponse: return agent.create_resume(session_id, request or CreateResumeRequest()) @application.delete( f"{API_PREFIX}/sessions/{{session_id}}", status_code=status.HTTP_204_NO_CONTENT, tags=["resume-agent"], ) def delete_session(session_id: str) -> Response: agent.delete_session(session_id) return Response(status_code=status.HTTP_204_NO_CONTENT) return application def _cors_origins_from_environment() -> list[str]: configured = os.getenv("RESUME_AGENT_CORS_ORIGINS") if configured: return [origin.strip() for origin in configured.split(",") if origin.strip()] return ["http://localhost:5173", "http://127.0.0.1:5173"] app = create_app()