Skip to main content

gba_core/
event.rs

1//! Event handling for streaming responses.
2//!
3//! This module provides the [`EventHandler`] trait for handling streaming events
4//! from Claude, and a default [`PrintEventHandler`] implementation that outputs
5//! events to stdout.
6//!
7//! # Example
8//!
9//! ```
10//! use gba_core::event::{EventHandler, PrintEventHandler};
11//!
12//! // Use the default print handler
13//! let mut handler = PrintEventHandler::default();
14//!
15//! // Or implement your own
16//! struct MyHandler;
17//! impl EventHandler for MyHandler {
18//!     fn on_text(&mut self, text: &str) {
19//!         // Custom text handling
20//!     }
21//!     fn on_tool_use(&mut self, tool: &str, input: &serde_json::Value) {
22//!         // Custom tool use handling
23//!     }
24//!     fn on_tool_result(&mut self, result: &str) {
25//!         // Custom tool result handling
26//!     }
27//!     fn on_error(&mut self, error: &str) {
28//!         // Custom error handling
29//!     }
30//!     fn on_complete(&mut self) {
31//!         // Custom completion handling
32//!     }
33//! }
34//! ```
35
36use std::io::{self, Write};
37
38use tracing::{debug, error, trace};
39
40/// Event handler trait for streaming responses.
41///
42/// Implement this trait to handle streaming events from Claude during
43/// task execution or interactive sessions.
44///
45/// All methods have default no-op implementations, allowing you to
46/// override only the events you care about.
47pub trait EventHandler: Send {
48    /// Called when text content is received from Claude.
49    ///
50    /// # Arguments
51    ///
52    /// * `text` - The text content received
53    fn on_text(&mut self, text: &str) {
54        let _ = text;
55    }
56
57    /// Called when Claude starts using a tool.
58    ///
59    /// # Arguments
60    ///
61    /// * `tool` - The name of the tool being used
62    /// * `input` - The input parameters for the tool
63    fn on_tool_use(&mut self, tool: &str, input: &serde_json::Value) {
64        let _ = (tool, input);
65    }
66
67    /// Called when a tool returns a result.
68    ///
69    /// # Arguments
70    ///
71    /// * `result` - The result from the tool execution
72    fn on_tool_result(&mut self, result: &str) {
73        let _ = result;
74    }
75
76    /// Called when an error occurs during streaming.
77    ///
78    /// # Arguments
79    ///
80    /// * `error` - Description of the error
81    fn on_error(&mut self, error: &str) {
82        let _ = error;
83    }
84
85    /// Called when the response is complete.
86    fn on_complete(&mut self) {}
87}
88
89/// Simple event handler that prints to stdout.
90///
91/// This handler is useful for CLI applications and debugging.
92/// It prints text directly to stdout and formats tool usage
93/// and results in a readable manner.
94///
95/// # Example
96///
97/// ```
98/// use gba_core::event::PrintEventHandler;
99///
100/// let mut handler = PrintEventHandler::default();
101/// // Use with Engine::run_stream() or Session::send_stream()
102/// ```
103#[derive(Debug, Default)]
104#[non_exhaustive]
105pub struct PrintEventHandler {
106    /// Whether to show tool usage details.
107    show_tools: bool,
108    /// Whether to flush stdout after each text output.
109    auto_flush: bool,
110}
111
112impl PrintEventHandler {
113    /// Create a new print event handler.
114    #[must_use]
115    pub fn new() -> Self {
116        Self::default()
117    }
118
119    /// Enable showing tool usage details.
120    #[must_use]
121    pub fn with_tools(mut self) -> Self {
122        self.show_tools = true;
123        self
124    }
125
126    /// Enable auto-flushing stdout after each text output.
127    ///
128    /// This is useful for real-time streaming output where you want
129    /// each piece of text to appear immediately.
130    #[must_use]
131    pub fn with_auto_flush(mut self) -> Self {
132        self.auto_flush = true;
133        self
134    }
135}
136
137impl EventHandler for PrintEventHandler {
138    fn on_text(&mut self, text: &str) {
139        print!("{text}");
140        if self.auto_flush {
141            let _ = io::stdout().flush();
142        }
143        trace!(text_len = text.len(), "received text");
144    }
145
146    fn on_tool_use(&mut self, tool: &str, input: &serde_json::Value) {
147        if self.show_tools {
148            println!("\n[Tool: {tool}]");
149            if let Ok(formatted) = serde_json::to_string_pretty(input) {
150                println!("Input: {formatted}");
151            }
152        }
153        debug!(tool = tool, "tool use started");
154    }
155
156    fn on_tool_result(&mut self, result: &str) {
157        if self.show_tools {
158            let preview = if result.len() > 200 {
159                format!("{}...", &result[..200])
160            } else {
161                result.to_string()
162            };
163            println!("[Result: {preview}]");
164        }
165        trace!(result_len = result.len(), "tool result received");
166    }
167
168    fn on_error(&mut self, error_msg: &str) {
169        eprintln!("\nError: {error_msg}");
170        error!(error = error_msg, "streaming error");
171    }
172
173    fn on_complete(&mut self) {
174        println!();
175        debug!("response complete");
176    }
177}
178
179/// Event handler that collects text into a buffer.
180///
181/// This handler is useful when you need to capture the full response
182/// while also allowing streaming events to be processed.
183///
184/// # Example
185///
186/// ```
187/// use gba_core::event::CollectingEventHandler;
188///
189/// let mut handler = CollectingEventHandler::new();
190/// // Use with streaming...
191/// let collected_text = handler.text();
192/// ```
193#[derive(Debug, Default)]
194#[non_exhaustive]
195pub struct CollectingEventHandler {
196    text: String,
197    tools_used: Vec<String>,
198    has_error: bool,
199}
200
201impl CollectingEventHandler {
202    /// Create a new collecting event handler.
203    #[must_use]
204    pub fn new() -> Self {
205        Self::default()
206    }
207
208    /// Get the collected text content.
209    #[must_use]
210    pub fn text(&self) -> &str {
211        &self.text
212    }
213
214    /// Take ownership of the collected text.
215    #[must_use]
216    pub fn into_text(self) -> String {
217        self.text
218    }
219
220    /// Get the list of tools that were used.
221    #[must_use]
222    pub fn tools_used(&self) -> &[String] {
223        &self.tools_used
224    }
225
226    /// Check if any errors occurred during streaming.
227    #[must_use]
228    pub fn has_error(&self) -> bool {
229        self.has_error
230    }
231
232    /// Clear the collected content and reset state.
233    pub fn clear(&mut self) {
234        self.text.clear();
235        self.tools_used.clear();
236        self.has_error = false;
237    }
238}
239
240impl EventHandler for CollectingEventHandler {
241    fn on_text(&mut self, text: &str) {
242        self.text.push_str(text);
243    }
244
245    fn on_tool_use(&mut self, tool: &str, _input: &serde_json::Value) {
246        self.tools_used.push(tool.to_string());
247    }
248
249    fn on_tool_result(&mut self, _result: &str) {
250        // No-op for collecting handler
251    }
252
253    fn on_error(&mut self, _error: &str) {
254        self.has_error = true;
255    }
256
257    fn on_complete(&mut self) {
258        // No-op for collecting handler
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265    use serde_json::json;
266
267    #[test]
268    fn test_should_create_print_handler_with_options() {
269        let handler = PrintEventHandler::new().with_tools().with_auto_flush();
270
271        assert!(handler.show_tools);
272        assert!(handler.auto_flush);
273    }
274
275    #[test]
276    fn test_should_collect_text() {
277        let mut handler = CollectingEventHandler::new();
278
279        handler.on_text("Hello ");
280        handler.on_text("World!");
281
282        assert_eq!(handler.text(), "Hello World!");
283    }
284
285    #[test]
286    fn test_should_track_tools_used() {
287        let mut handler = CollectingEventHandler::new();
288
289        handler.on_tool_use("Read", &json!({"path": "/test"}));
290        handler.on_tool_use("Write", &json!({"path": "/out"}));
291
292        assert_eq!(handler.tools_used(), &["Read", "Write"]);
293    }
294
295    #[test]
296    fn test_should_track_errors() {
297        let mut handler = CollectingEventHandler::new();
298
299        assert!(!handler.has_error());
300
301        handler.on_error("test error");
302
303        assert!(handler.has_error());
304    }
305
306    #[test]
307    fn test_should_clear_collected_state() {
308        let mut handler = CollectingEventHandler::new();
309
310        handler.on_text("test");
311        handler.on_tool_use("Read", &json!({}));
312        handler.on_error("error");
313
314        handler.clear();
315
316        assert!(handler.text().is_empty());
317        assert!(handler.tools_used().is_empty());
318        assert!(!handler.has_error());
319    }
320
321    #[test]
322    fn test_should_take_ownership_of_text() {
323        let mut handler = CollectingEventHandler::new();
324        handler.on_text("owned text");
325
326        let text = handler.into_text();
327
328        assert_eq!(text, "owned text");
329    }
330
331    // Test that default trait implementation compiles
332    struct NoOpHandler;
333
334    impl EventHandler for NoOpHandler {}
335
336    #[test]
337    fn test_should_allow_no_op_handler() {
338        let mut handler = NoOpHandler;
339
340        // These should all be no-ops
341        handler.on_text("test");
342        handler.on_tool_use("tool", &json!({}));
343        handler.on_tool_result("result");
344        handler.on_error("error");
345        handler.on_complete();
346    }
347}