Add SSE endpoint and dream notify — /api/events and /api/events/notify

This commit is contained in:
2026-04-27 02:20:50 +00:00
parent 9088b5643d
commit 9b312d936f
2 changed files with 60 additions and 0 deletions
+48
View File
@@ -26,6 +26,8 @@ from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
import asyncio
from fastapi.responses import StreamingResponse
load_dotenv(Path.home() / "aaronai" / ".env")
@@ -767,5 +769,51 @@ async def clear_all_conversations(auth: str = Depends(require_auth)):
return JSONResponse({"cleared": True})
# SSE client registry
sse_clients: list[asyncio.Queue] = []
async def sse_generator(queue: asyncio.Queue):
try:
yield 'data: {"type": "connected"}\n\n'
while True:
try:
event = await asyncio.wait_for(queue.get(), timeout=30.0)
import json as _json
yield 'data: ' + _json.dumps(event) + '\n\n'
except asyncio.TimeoutError:
yield 'data: {"type": "heartbeat"}\n\n'
except asyncio.CancelledError:
pass
finally:
if queue in sse_clients:
sse_clients.remove(queue)
@app.get("/api/events")
async def sse_endpoint(request: Request, auth: str = Depends(require_auth)):
queue: asyncio.Queue = asyncio.Queue()
sse_clients.append(queue)
return StreamingResponse(
sse_generator(queue),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
"Connection": "keep-alive",
}
)
@app.post("/api/events/notify")
async def notify_clients(request: Request):
"""Internal endpoint — called by dream.py when a dream is delivered"""
# Only allow from localhost
client_host = request.client.host if request.client else ""
if client_host not in ("127.0.0.1", "::1", "localhost"):
raise HTTPException(status_code=403, detail="Internal only")
data = await request.json()
for queue in sse_clients:
await queue.put(data)
return JSONResponse({"notified": len(sse_clients)})
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)