//! Closure-based event callback API //! //! Provides a closure-based alternative to implementing `Handler` directly. //! Register callbacks on `Worker` via `on_text_block()`, `on_tool_use_block()`, //! `on_usage()`, etc. use std::marker::PhantomData; use crate::handler::{ Handler, Kind, TextBlockEvent, TextBlockKind, ToolUseBlockEvent, ToolUseBlockKind, ToolUseBlockStart, }; use crate::hook::ToolCall; // ============================================================================= // TextBlock Closure Handler // ============================================================================= /// Callback scope for a text block. /// /// Passed to the setup closure registered with `Worker::on_text_block()`. /// Register per-block callbacks via `on_delta()` and `on_stop()`. /// /// # Examples /// /// ```ignore /// worker.on_text_block(|block| { /// block.on_delta(|text| print!("{}", text)); /// block.on_stop(|full_text| println!("\n--- {} chars ---", full_text.len())); /// }); /// ``` pub struct TextBlockScope { pub(crate) on_delta: Option>, pub(crate) on_stop: Option>, } impl TextBlockScope { fn new() -> Self { Self { on_delta: None, on_stop: None, } } /// Register a callback for each text delta (streaming fragment). pub fn on_delta(&mut self, f: impl FnMut(&str) + Send + Sync + 'static) { self.on_delta = Some(Box::new(f)); } /// Register a callback invoked when the block completes. /// /// Receives the full accumulated text of the block. pub fn on_stop(&mut self, f: impl FnMut(&str) + Send + Sync + 'static) { self.on_stop = Some(Box::new(f)); } } /// Per-block state created by Timeline's scope lifecycle. #[derive(Default)] pub(crate) struct TextBlockClosureState { on_delta: Option>, on_stop: Option>, buffer: String, } /// Closure-based `Handler` adapter. pub(crate) struct ClosureTextBlockHandler { pub(crate) setup: Box, } impl Handler for ClosureTextBlockHandler { type Scope = TextBlockClosureState; fn on_event(&mut self, scope: &mut Self::Scope, event: &TextBlockEvent) { match event { TextBlockEvent::Start(_) => { scope.buffer.clear(); let mut builder = TextBlockScope::new(); (self.setup)(&mut builder); scope.on_delta = builder.on_delta; scope.on_stop = builder.on_stop; } TextBlockEvent::Delta(text) => { scope.buffer.push_str(text); if let Some(f) = &mut scope.on_delta { f(text); } } TextBlockEvent::Stop(_) => { if let Some(f) = &mut scope.on_stop { f(&scope.buffer); } } } } } // ============================================================================= // ToolUseBlock Closure Handler // ============================================================================= /// Callback scope for a tool use block. /// /// Passed to the setup closure registered with `Worker::on_tool_use_block()`. /// The setup closure also receives `&ToolUseBlockStart` with `id` and `name`. /// /// # Examples /// /// ```ignore /// worker.on_tool_use_block(|start, block| { /// println!("Tool: {} ({})", start.name, start.id); /// block.on_delta(|json| { /* streaming JSON fragment */ }); /// block.on_stop(|call| println!("Done: {}", call.name)); /// }); /// ``` pub struct ToolUseBlockScope { pub(crate) on_delta: Option>, pub(crate) on_stop: Option>, } impl ToolUseBlockScope { fn new() -> Self { Self { on_delta: None, on_stop: None, } } /// Register a callback for each JSON input delta (streaming fragment). pub fn on_delta(&mut self, f: impl FnMut(&str) + Send + Sync + 'static) { self.on_delta = Some(Box::new(f)); } /// Register a callback invoked when the block completes. /// /// Receives the fully assembled `ToolCall` with parsed JSON input. pub fn on_stop(&mut self, f: impl FnMut(&ToolCall) + Send + Sync + 'static) { self.on_stop = Some(Box::new(f)); } } /// Per-block state for tool use closure handler. #[derive(Default)] pub(crate) struct ToolUseBlockClosureState { on_delta: Option>, on_stop: Option>, id: String, name: String, input_json: String, } /// Closure-based `Handler` adapter. pub(crate) struct ClosureToolUseBlockHandler { pub(crate) setup: Box, } impl Handler for ClosureToolUseBlockHandler { type Scope = ToolUseBlockClosureState; fn on_event(&mut self, scope: &mut Self::Scope, event: &ToolUseBlockEvent) { match event { ToolUseBlockEvent::Start(start) => { scope.id = start.id.clone(); scope.name = start.name.clone(); scope.input_json.clear(); let mut builder = ToolUseBlockScope::new(); (self.setup)(start, &mut builder); scope.on_delta = builder.on_delta; scope.on_stop = builder.on_stop; } ToolUseBlockEvent::InputJsonDelta(json) => { scope.input_json.push_str(json); if let Some(f) = &mut scope.on_delta { f(json); } } ToolUseBlockEvent::Stop(_) => { let input: serde_json::Value = serde_json::from_str(&scope.input_json).unwrap_or_default(); let tool_call = ToolCall { id: std::mem::take(&mut scope.id), name: std::mem::take(&mut scope.name), input, }; if let Some(f) = &mut scope.on_stop { f(&tool_call); } } } } } // ============================================================================= // Generic Meta Event Closure Handler // ============================================================================= /// Closure-based `Handler` adapter for meta events (Usage, Status, Error). pub(crate) struct ClosureMetaHandler where K: Kind, { pub(crate) callback: F, pub(crate) _kind: PhantomData, } impl Handler for ClosureMetaHandler where F: FnMut(&K::Event) + Send + Sync, K: Kind, { type Scope = (); fn on_event(&mut self, _scope: &mut (), event: &K::Event) { (self.callback)(event); } }