main.py (5146 lines, 106 routes) becomes an assembly only: one package per domain — core, files, settings, dashboards, conversations, diff, notifications, forms, runs, accounts, workers, models, cron, agents, projects, services, goals, memories, plans, templates — each exposing an APIRouter; the flat domain modules move into their package behind a barrel that keeps the old `import conversations` / `import projects` spellings. The shared singletons (store, meta_store, hub, indexer, …) are built once by core.state.build_state() and attached to app.state.ai; routes take them as the `deps: State` dependency and helpers as an explicit `deps: AppState`. conversations/pricing.py carries the per-model rates out of the parser. Verified: route table and OpenAPI byte-identical; 90 read endpoints golden-diffed against the monolith on a copy of the live data (identical); write routes smoke-tested; 66 backend tests pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
52 lines
1.5 KiB
Python
52 lines
1.5 KiB
Python
"""App-level routes: the SSE event stream, deploy status and health."""
|
|
|
|
import queue
|
|
|
|
from fastapi import APIRouter
|
|
from fastapi.responses import StreamingResponse
|
|
|
|
import schemas
|
|
from core.http import _r
|
|
from core.state import State
|
|
from deploy_status import read_status as read_deploy_status
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/api/events")
|
|
def events(deps: State):
|
|
"""Server-Sent Events: ``meta`` / ``transcript`` change pings for the UI."""
|
|
q = deps.hub.subscribe()
|
|
|
|
def gen():
|
|
try:
|
|
yield "retry: 3000\n\n"
|
|
while True:
|
|
try:
|
|
data = q.get(timeout=15)
|
|
yield f"data: {data}\n\n"
|
|
except queue.Empty:
|
|
yield ": ping\n\n" # keep proxies from closing the stream
|
|
finally:
|
|
deps.hub.unsubscribe(q)
|
|
|
|
return StreamingResponse(
|
|
gen(), media_type="text/event-stream",
|
|
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no",
|
|
"Connection": "keep-alive"})
|
|
|
|
|
|
@router.get("/api/deploy-status", responses=_r(schemas.DeployStatus))
|
|
def deploy_status():
|
|
"""Current blue-green deploy snapshot (or ``null`` when none is in flight).
|
|
|
|
Written by ``services/ai-agent/deploy.sh`` into the data dir; polled by the
|
|
:class:`DeployWatcher`, which pings a ``deploy`` SSE event on any change.
|
|
"""
|
|
return read_deploy_status()
|
|
|
|
|
|
@router.get("/api/health")
|
|
def health():
|
|
return {"ok": True}
|