"""FastAPI app factory — lifespan, CORS, health, routers.""" from contextlib import asynccontextmanager import httpx from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from .agents.registry import AgentRegistry, register_builtin_agents from .agents.session import InMemorySessionStore from .api import ( assessment_router, chat_router, lab_router, mentor_router, proctor_router, ) from .config import Settings from .llm import create_provider def create_app(settings: Settings | None = None) -> FastAPI: settings = settings or Settings() @asynccontextmanager async def lifespan(app: FastAPI): # Shared HTTP client pool (D-017): 10s connect / 300s read for cloud TTFT timeout = httpx.Timeout(connect=10.0, read=300.0, write=30.0, pool=10.0) app.state.http_client = httpx.AsyncClient(timeout=timeout) app.state.settings = settings app.state.provider = create_provider(settings, app.state.http_client) app.state.session_store = InMemorySessionStore() app.state.agent_registry = AgentRegistry() register_builtin_agents(app.state.agent_registry) yield await app.state.http_client.aclose() app = FastAPI(title="Nextcraft AI Service", version="0.2.0", lifespan=lifespan) # A-008: localhost-only CORS, no credentials app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000", "http://127.0.0.1:3000"], allow_methods=["GET", "POST", "OPTIONS"], allow_headers=["Content-Type"], allow_credentials=False, ) @app.get("/health") async def health() -> dict: return { "status": "ok", "provider": settings.provider, "model": settings.model, } app.include_router(chat_router) app.include_router(lab_router) app.include_router(assessment_router) app.include_router(mentor_router) app.include_router(proctor_router) return app app = create_app()