WS Gateway:
- WebSocket lifecycle handler with RS256 JWT auth
- Redis bridge: device registry, frame publishing, tool_result routing
- Inbound routing: tool_result→LPUSH, home/floating→chat pub/sub
- Outbound: subscribes to ws:out:{user_id}, forwards to Electron
- Single-worker Dockerfile (long-lived WS connections)
Chat Service:
- Redis consumer: subscribes to chat:request:* pattern
- Redis-based ws_context: tool_call→publish, BRPOP tool_result (30s timeout)
- deep_agent: single-agent runner with home/floating/stream variants
- memory_middleware: core/associative/episodic/proactive memory with Fernet
- Domain agents: task (8 tools), note (5), project (6), timeline (4)
- LLM factory via LiteLLM (100+ providers)
- Output formatter (StreamFormatter)
- POST /chat REST fallback with Traefik header auth
- Multi-worker Dockerfile with 120s timeout for LLM calls
51 lines
1.5 KiB
Python
51 lines
1.5 KiB
Python
"""Output formatter for deep-agent stream events — Chat Service copy.
|
|
|
|
Converts (event_type, data) tuples into WebSocket frame Pydantic models.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import AsyncGenerator
|
|
from typing import Any
|
|
|
|
from shared.schemas import WsFloatingDomain, WsStreamEnd, WsStreamStart, WsStreamText
|
|
|
|
WsFrame = WsStreamStart | WsStreamText | WsStreamEnd | WsFloatingDomain
|
|
|
|
|
|
class StreamFormatter:
|
|
"""Convert `(event_type, data)` stream events into websocket frame models."""
|
|
|
|
def __init__(self, request_id: str) -> None:
|
|
self.request_id = request_id
|
|
|
|
async def format(
|
|
self,
|
|
event_stream: AsyncGenerator[tuple[str, Any], None],
|
|
) -> AsyncGenerator[WsFrame, None]:
|
|
started = False
|
|
|
|
async for event_type, data in event_stream:
|
|
if event_type == "floating_domain":
|
|
if isinstance(data, dict):
|
|
yield WsFloatingDomain(
|
|
request_id=self.request_id,
|
|
domain=data,
|
|
)
|
|
continue
|
|
|
|
if event_type != "token":
|
|
continue
|
|
|
|
if not started:
|
|
yield WsStreamStart(request_id=self.request_id)
|
|
started = True
|
|
|
|
text = str(data or "")
|
|
if text:
|
|
yield WsStreamText(request_id=self.request_id, chunk=text)
|
|
|
|
if not started:
|
|
yield WsStreamStart(request_id=self.request_id)
|
|
yield WsStreamEnd(request_id=self.request_id)
|