"""Assemble the client API and retain its existing documentation URLs.""" import asyncio import os from collections.abc import AsyncIterator from contextlib import asynccontextmanager, suppress from fastapi import FastAPI from fastapi.responses import RedirectResponse from fastapi.staticfiles import StaticFiles from vocero_api.api.router import router from vocero_api.core import config, diagnostics # noqa: F401 - retain the startup banner from vocero_api.services.conversations import store @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncIterator[None]: async def expire_conversations() -> None: while True: await asyncio.sleep(30) store.prune() cleanup = asyncio.create_task(expire_conversations()) if config.S2S_MCP_URL else None try: yield finally: if cleanup is not None: cleanup.cancel() with suppress(asyncio.CancelledError): await cleanup store.clear() app = FastAPI(title="s2s-demo", lifespan=lifespan) app.include_router(router) @app.get("/project", include_in_schema=False) @app.get("/project/", include_in_schema=False) @app.get("/project/index.html", include_in_schema=False) @app.get("/public/docs.html", include_in_schema=False) async def project_documentation() -> RedirectResponse: return RedirectResponse("/docs/proposals/vocero-architecture.html", status_code=307) # The existing FastAPI /docs page remains registered ahead of this mount. app.mount("/docs", StaticFiles(directory=os.path.join(config.ROOT_DIR, "docs"), html=True), name="documentation")