refactor: rename llm worker crate to engine
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
//! Closure-based event callback API
|
||||
//!
|
||||
//! Provides a closure-based alternative to implementing `Handler<K>` directly.
|
||||
//! Register callbacks on `Engine` via `on_text_block()`, `on_tool_use_block()`,
|
||||
//! `on_usage()`, etc.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::handler::{
|
||||
Handler, Kind, TextBlockEvent, TextBlockKind, ThinkingBlockEvent, ThinkingBlockKind,
|
||||
ToolUseBlockEvent, ToolUseBlockKind, ToolUseBlockStart,
|
||||
};
|
||||
use crate::tool::ToolCall;
|
||||
|
||||
// =============================================================================
|
||||
// TextBlock Closure Handler
|
||||
// =============================================================================
|
||||
|
||||
/// Callback scope for a text block.
|
||||
///
|
||||
/// Passed to the setup closure registered with `Engine::on_text_block()`.
|
||||
/// Register per-block callbacks via `on_delta()` and `on_stop()`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// engine.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<Box<dyn FnMut(&str) + Send + Sync>>,
|
||||
pub(crate) on_stop: Option<Box<dyn FnMut(&str) + Send + Sync>>,
|
||||
}
|
||||
|
||||
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<Box<dyn FnMut(&str) + Send + Sync>>,
|
||||
on_stop: Option<Box<dyn FnMut(&str) + Send + Sync>>,
|
||||
buffer: String,
|
||||
}
|
||||
|
||||
/// Closure-based `Handler<TextBlockKind>` adapter.
|
||||
pub(crate) struct ClosureTextBlockHandler {
|
||||
pub(crate) setup: Box<dyn FnMut(&mut TextBlockScope) + Send + Sync>,
|
||||
}
|
||||
|
||||
impl Handler<TextBlockKind> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ThinkingBlock Closure Handler
|
||||
// =============================================================================
|
||||
|
||||
/// Callback scope for a thinking block.
|
||||
///
|
||||
/// Mirrors `TextBlockScope`. Some providers (or some configurations)
|
||||
/// emit thinking metadata without plaintext deltas — in that case the
|
||||
/// block fires `Start` and `Stop` with no `Delta` in between, which is
|
||||
/// expected and not an error.
|
||||
pub struct ThinkingBlockScope {
|
||||
pub(crate) on_delta: Option<Box<dyn FnMut(&str) + Send + Sync>>,
|
||||
pub(crate) on_stop: Option<Box<dyn FnMut(&str) + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl ThinkingBlockScope {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
on_delta: None,
|
||||
on_stop: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a callback for each thinking 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 thinking text. May be empty when
|
||||
/// the provider didn't emit any plaintext deltas.
|
||||
pub fn on_stop(&mut self, f: impl FnMut(&str) + Send + Sync + 'static) {
|
||||
self.on_stop = Some(Box::new(f));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ThinkingBlockClosureState {
|
||||
on_delta: Option<Box<dyn FnMut(&str) + Send + Sync>>,
|
||||
on_stop: Option<Box<dyn FnMut(&str) + Send + Sync>>,
|
||||
buffer: String,
|
||||
}
|
||||
|
||||
pub(crate) struct ClosureThinkingBlockHandler {
|
||||
pub(crate) setup: Box<dyn FnMut(&mut ThinkingBlockScope) + Send + Sync>,
|
||||
}
|
||||
|
||||
impl Handler<ThinkingBlockKind> for ClosureThinkingBlockHandler {
|
||||
type Scope = ThinkingBlockClosureState;
|
||||
|
||||
fn on_event(&mut self, scope: &mut Self::Scope, event: &ThinkingBlockEvent) {
|
||||
match event {
|
||||
ThinkingBlockEvent::Start(_) => {
|
||||
scope.buffer.clear();
|
||||
let mut builder = ThinkingBlockScope::new();
|
||||
(self.setup)(&mut builder);
|
||||
scope.on_delta = builder.on_delta;
|
||||
scope.on_stop = builder.on_stop;
|
||||
}
|
||||
ThinkingBlockEvent::Delta(text) => {
|
||||
scope.buffer.push_str(text);
|
||||
if let Some(f) = &mut scope.on_delta {
|
||||
f(text);
|
||||
}
|
||||
}
|
||||
ThinkingBlockEvent::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 `Engine::on_tool_use_block()`.
|
||||
/// The setup closure also receives `&ToolUseBlockStart` with `id` and `name`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// engine.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<Box<dyn FnMut(&str) + Send + Sync>>,
|
||||
pub(crate) on_stop: Option<Box<dyn FnMut(&ToolCall) + Send + Sync>>,
|
||||
}
|
||||
|
||||
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<Box<dyn FnMut(&str) + Send + Sync>>,
|
||||
on_stop: Option<Box<dyn FnMut(&ToolCall) + Send + Sync>>,
|
||||
id: String,
|
||||
name: String,
|
||||
input_json: String,
|
||||
}
|
||||
|
||||
/// Closure-based `Handler<ToolUseBlockKind>` adapter.
|
||||
pub(crate) struct ClosureToolUseBlockHandler {
|
||||
pub(crate) setup: Box<dyn FnMut(&ToolUseBlockStart, &mut ToolUseBlockScope) + Send + Sync>,
|
||||
}
|
||||
|
||||
impl Handler<ToolUseBlockKind> 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<K>` adapter for meta events (Usage, Status, Error).
|
||||
pub(crate) struct ClosureMetaHandler<F, K>
|
||||
where
|
||||
K: Kind,
|
||||
{
|
||||
pub(crate) callback: F,
|
||||
pub(crate) _kind: PhantomData<K>,
|
||||
}
|
||||
|
||||
impl<F, K> Handler<K> for ClosureMetaHandler<F, K>
|
||||
where
|
||||
F: FnMut(&K::Event) + Send + Sync,
|
||||
K: Kind,
|
||||
{
|
||||
type Scope = ();
|
||||
|
||||
fn on_event(&mut self, _scope: &mut (), event: &K::Event) {
|
||||
(self.callback)(event);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user