Vocero needs to query AquaPortal on behalf of the caller selected by the existing phone link. This adds server-owned conversations and MCP tool execution for voice and HTTP reasoning while leaving the speech pipeline unchanged. Release notes: - Added browser-bound conversations that keep backend credentials server-side and isolate callers. - Connected the eight-tool AquaPortal catalog through authenticated Streamable HTTP with full input schemas and structured results. - Added bounded multi-step tool execution with cancellation, expiry, and stale-result protection. - Updated browser bootstrap, tool progress, reasoning headers, and conversation cleanup. - Disabled legacy unscoped content preloads in MCP mode and documented configuration and deployment constraints. - Preserved disabled-mode contracts and added local SDK, WebSocket, and browser regression coverage. Reviewed-on: AquaPortal/s2s#1
49 lines
1.6 KiB
Python
49 lines
1.6 KiB
Python
"""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")
|