""" Events - Decoupled event emission and queuing for cross-thread communication. This module provides three complementary patterns for thread-safe communication between the GUI main thread and background workers: 1. EventEmitter: Pub/sub pattern for synchronous event broadcast - Used for: API lifecycle events (request_start, response_received, tool_execution) - 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 - 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 3. UserRequestEvent: Structured payload for AI request data - Used for: Bundling prompt, context, files, and base_dir into single object - Immutable data transfer object for cross-thread handoff 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 - 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) - UserRequestEvent: Immutable, safe for concurrent access """ import queue from typing import Callable, Any, Dict, List, Tuple class EventEmitter: """ Simple event emitter for decoupled communication between modules. """ def __init__(self) -> None: """Initializes the EventEmitter with an empty listener map.""" self._listeners: Dict[str, List[Callable[..., Any]]] = {} def on(self, event_name: str, callback: Callable[..., Any]) -> None: """ Registers a callback for a specific event. Args: event_name: The name of the event to listen for. callback: The function to call when the event is emitted. """ if event_name not in self._listeners: self._listeners[event_name] = [] self._listeners[event_name].append(callback) def emit(self, event_name: str, *args: Any, **kwargs: Any) -> None: """ Emits an event, calling all registered callbacks. Args: event_name: The name of the event to emit. *args: Positional arguments to pass to callbacks. **kwargs: Keyword arguments to pass to callbacks. """ if event_name in self._listeners: for callback in self._listeners[event_name]: callback(*args, **kwargs) def clear(self) -> None: """Clears all registered listeners.""" self._listeners.clear() class SyncEventQueue: """ Synchronous event queue for decoupled communication using queue.Queue. """ def __init__(self) -> None: """Initializes the SyncEventQueue with an internal queue.Queue.""" self._queue: queue.Queue[Tuple[str, Any]] = queue.Queue() def put(self, event_name: str, payload: Any = None) -> None: """ Puts an event into the queue. Args: event_name: The name of the event. payload: Optional data associated with the event. """ self._queue.put((event_name, payload)) def get(self) -> Tuple[str, Any]: """ Gets an event from the queue. Returns: A tuple containing (event_name, payload). """ return self._queue.get() def task_done(self) -> None: """Signals that a formerly enqueued task is complete.""" self._queue.task_done() def join(self) -> None: """Blocks until all items in the queue have been gotten and processed.""" self._queue.join() class UserRequestEvent: """ Payload for a user request event. """ def __init__(self, prompt: str, stable_md: str, file_items: List[Any], disc_text: str, base_dir: str) -> None: self.prompt = prompt self.stable_md = stable_md self.file_items = file_items self.disc_text = disc_text self.base_dir = base_dir def to_dict(self) -> Dict[str, Any]: return { "prompt": self.prompt, "stable_md": self.stable_md, "file_items": self.file_items, "disc_text": self.disc_text, "base_dir": self.base_dir }