WebSocket endpoint for assistant chat. Message protocol: Client -> Server: - {"type": "start", "conversation_id": int | null} - Start/resume session - {"type": "message", "content": "..."} - Send user message - {"type": "ping"} - Keep-alive ping Server -> Client:
(websocket: WebSocket, project_name: str)
| 199 | |
| 200 | @router.websocket("/ws/{project_name}") |
| 201 | async def assistant_chat_websocket(websocket: WebSocket, project_name: str): |
| 202 | """ |
| 203 | WebSocket endpoint for assistant chat. |
| 204 | |
| 205 | Message protocol: |
| 206 | |
| 207 | Client -> Server: |
| 208 | - {"type": "start", "conversation_id": int | null} - Start/resume session |
| 209 | - {"type": "message", "content": "..."} - Send user message |
| 210 | - {"type": "ping"} - Keep-alive ping |
| 211 | |
| 212 | Server -> Client: |
| 213 | - {"type": "conversation_created", "conversation_id": int} - New conversation created |
| 214 | - {"type": "text", "content": "..."} - Text chunk from Claude |
| 215 | - {"type": "tool_call", "tool": "...", "input": {...}} - Tool being called |
| 216 | - {"type": "response_done"} - Response complete |
| 217 | - {"type": "error", "content": "..."} - Error message |
| 218 | - {"type": "pong"} - Keep-alive pong |
| 219 | """ |
| 220 | if not validate_project_name(project_name): |
| 221 | await websocket.close(code=4000, reason="Invalid project name") |
| 222 | return |
| 223 | |
| 224 | project_dir = _get_project_path(project_name) |
| 225 | if not project_dir: |
| 226 | await websocket.close(code=4004, reason="Project not found in registry") |
| 227 | return |
| 228 | |
| 229 | if not project_dir.exists(): |
| 230 | await websocket.close(code=4004, reason="Project directory not found") |
| 231 | return |
| 232 | |
| 233 | await websocket.accept() |
| 234 | logger.info(f"Assistant WebSocket connected for project: {project_name}") |
| 235 | |
| 236 | session: Optional[AssistantChatSession] = None |
| 237 | |
| 238 | try: |
| 239 | while True: |
| 240 | try: |
| 241 | data = await websocket.receive_text() |
| 242 | message = json.loads(data) |
| 243 | msg_type = message.get("type") |
| 244 | logger.debug(f"Assistant received message type: {msg_type}") |
| 245 | |
| 246 | if msg_type == "ping": |
| 247 | await websocket.send_json({"type": "pong"}) |
| 248 | continue |
| 249 | |
| 250 | elif msg_type == "start": |
| 251 | # Get optional conversation_id to resume |
| 252 | conversation_id = message.get("conversation_id") |
| 253 | logger.debug(f"Processing start message with conversation_id={conversation_id}") |
| 254 | |
| 255 | try: |
| 256 | # Create a new session |
| 257 | logger.debug(f"Creating session for {project_name}") |
| 258 | session = await create_session( |
nothing calls this directly
no test coverage detected