Skip to main content

selfware/agent/
tui_events.rs

1#[cfg(feature = "tui")]
2use crate::ui::tui::TuiEvent;
3
4/// Lightweight event type that is always available (not feature-gated).
5///
6/// This allows agent code to emit events unconditionally without
7/// `#[cfg(feature = "tui")]` at every call site. When the TUI feature
8/// is enabled, these are translated to `TuiEvent` and sent to the UI.
9#[derive(Debug, Clone)]
10pub enum AgentEvent {
11    Started,
12    Completed {
13        message: String,
14    },
15    Error {
16        message: String,
17    },
18    Status {
19        message: String,
20    },
21    TokenUsage {
22        prompt_tokens: u64,
23        completion_tokens: u64,
24    },
25    ToolStarted {
26        name: String,
27    },
28    ToolCompleted {
29        name: String,
30        success: bool,
31        duration_ms: u64,
32    },
33    /// Streaming content chunk from the assistant
34    AssistantDelta {
35        text: String,
36    },
37    /// Streaming reasoning/thinking chunk
38    ThinkingDelta {
39        text: String,
40    },
41    /// Reasoning phase finished
42    ThinkingEnd,
43    /// Tool execution progress update
44    ToolProgress {
45        name: String,
46        status: String,
47    },
48    /// Loading spinner started
49    SpinnerStart {
50        message: String,
51    },
52    /// Loading spinner message changed
53    SpinnerUpdate {
54        message: String,
55    },
56    /// Loading spinner finished
57    SpinnerStop,
58    /// User queued a message during generation
59    InputQueued {
60        message: String,
61        position: usize,
62    },
63}
64
65/// Trait for emitting real-time events during agent execution.
66///
67/// This decouples the core agent logic from TUI-specific implementations.
68pub trait EventEmitter: Send + Sync {
69    fn emit(&self, event: AgentEvent);
70}
71
72/// A no-op event emitter that does nothing.
73pub struct NoopEmitter;
74
75impl EventEmitter for NoopEmitter {
76    fn emit(&self, _event: AgentEvent) {}
77}
78
79/// An event emitter that sends events via an mpsc channel to the TUI.
80#[cfg(feature = "tui")]
81pub struct TuiEmitter {
82    tx: std::sync::mpsc::Sender<TuiEvent>,
83}
84
85#[cfg(feature = "tui")]
86impl TuiEmitter {
87    pub fn new(tx: std::sync::mpsc::Sender<TuiEvent>) -> Self {
88        Self { tx }
89    }
90}
91
92#[cfg(feature = "tui")]
93impl EventEmitter for TuiEmitter {
94    fn emit(&self, event: AgentEvent) {
95        let tui_event = match event {
96            AgentEvent::Started => TuiEvent::AgentStarted,
97            AgentEvent::Completed { message } => TuiEvent::AgentCompleted { message },
98            AgentEvent::Error { message } => TuiEvent::AgentError { message },
99            AgentEvent::Status { message } => TuiEvent::StatusUpdate { message },
100            AgentEvent::TokenUsage {
101                prompt_tokens,
102                completion_tokens,
103            } => TuiEvent::TokenUsage {
104                prompt_tokens,
105                completion_tokens,
106            },
107            AgentEvent::ToolStarted { name } => TuiEvent::ToolStarted { name },
108            AgentEvent::ToolCompleted {
109                name,
110                success,
111                duration_ms,
112            } => TuiEvent::ToolCompleted {
113                name,
114                success,
115                duration_ms,
116            },
117            AgentEvent::AssistantDelta { text } => TuiEvent::AssistantDelta { text },
118            AgentEvent::ThinkingDelta { text } => TuiEvent::ThinkingDelta { text },
119            AgentEvent::ThinkingEnd => TuiEvent::ThinkingEnd,
120            AgentEvent::ToolProgress { name, status } => TuiEvent::ToolProgress { name, status },
121            AgentEvent::SpinnerStart { message } => TuiEvent::SpinnerStart { message },
122            AgentEvent::SpinnerUpdate { message } => TuiEvent::SpinnerUpdate { message },
123            AgentEvent::SpinnerStop => TuiEvent::SpinnerStop,
124            AgentEvent::InputQueued { message, position } => {
125                TuiEvent::InputQueued { message, position }
126            }
127        };
128        let _ = self.tx.send(tui_event);
129    }
130}