feat(api): implement websocket gateway and event streaming for phase 1
This commit is contained in:
@@ -9,7 +9,7 @@ between the GUI main thread and background workers:
|
||||
- Thread-safe: Callbacks execute on emitter's thread
|
||||
- Example: ai_client.py emits 'request_start' and 'response_received' events
|
||||
|
||||
2. SyncEventQueue: Producer-consumer pattern via queue.Queue
|
||||
2. AsyncEventQueue: Producer-consumer pattern via queue.Queue
|
||||
- Used for: Decoupled task submission where consumer polls at its own pace
|
||||
- Thread-safe: Built on Python's thread-safe queue.Queue
|
||||
- Example: Background workers submit tasks, main thread drains queue
|
||||
@@ -21,16 +21,16 @@ between the GUI main thread and background workers:
|
||||
Integration Points:
|
||||
- ai_client.py: EventEmitter for API lifecycle events
|
||||
- gui_2.py: Consumes events via _process_event_queue()
|
||||
- multi_agent_conductor.py: Uses SyncEventQueue for state updates
|
||||
- multi_agent_conductor.py: Uses AsyncEventQueue for state updates
|
||||
- api_hooks.py: Pushes events to _api_event_queue for external visibility
|
||||
|
||||
Thread Safety:
|
||||
- EventEmitter: NOT thread-safe for concurrent on/emit (use from single thread)
|
||||
- SyncEventQueue: FULLY thread-safe (built on queue.Queue)
|
||||
- AsyncEventQueue: FULLY thread-safe (built on queue.Queue)
|
||||
- UserRequestEvent: Immutable, safe for concurrent access
|
||||
"""
|
||||
import queue
|
||||
from typing import Callable, Any, Dict, List, Tuple
|
||||
from typing import Callable, Any, Dict, List, Tuple, Optional
|
||||
|
||||
class EventEmitter:
|
||||
"""
|
||||
@@ -70,14 +70,16 @@ class EventEmitter:
|
||||
"""Clears all registered listeners."""
|
||||
self._listeners.clear()
|
||||
|
||||
class SyncEventQueue:
|
||||
class AsyncEventQueue:
|
||||
"""
|
||||
Synchronous event queue for decoupled communication using queue.Queue.
|
||||
(Named AsyncEventQueue for architectural consistency, but is synchronous)
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initializes the SyncEventQueue with an internal queue.Queue."""
|
||||
"""Initializes the AsyncEventQueue with an internal queue.Queue."""
|
||||
self._queue: queue.Queue[Tuple[str, Any]] = queue.Queue()
|
||||
self.websocket_server: Optional[Any] = None
|
||||
|
||||
def put(self, event_name: str, payload: Any = None) -> None:
|
||||
"""
|
||||
@@ -88,6 +90,8 @@ class SyncEventQueue:
|
||||
payload: Optional data associated with the event.
|
||||
"""
|
||||
self._queue.put((event_name, payload))
|
||||
if self.websocket_server:
|
||||
self.websocket_server.broadcast("events", {"event": event_name, "payload": payload})
|
||||
|
||||
def get(self) -> Tuple[str, Any]:
|
||||
"""
|
||||
@@ -106,6 +110,9 @@ class SyncEventQueue:
|
||||
"""Blocks until all items in the queue have been gotten and processed."""
|
||||
self._queue.join()
|
||||
|
||||
# Alias for backward compatibility
|
||||
SyncEventQueue = AsyncEventQueue
|
||||
|
||||
class UserRequestEvent:
|
||||
"""
|
||||
Payload for a user request event.
|
||||
@@ -126,4 +133,3 @@ class UserRequestEvent:
|
||||
"disc_text": self.disc_text,
|
||||
"base_dir": self.base_dir
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user