TUIにThinkingを表示する実装
This commit is contained in:
@@ -7,8 +7,8 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::handler::{
|
||||
Handler, Kind, TextBlockEvent, TextBlockKind, ToolUseBlockEvent, ToolUseBlockKind,
|
||||
ToolUseBlockStart,
|
||||
Handler, Kind, TextBlockEvent, TextBlockKind, ThinkingBlockEvent, ThinkingBlockKind,
|
||||
ToolUseBlockEvent, ToolUseBlockKind, ToolUseBlockStart,
|
||||
};
|
||||
use crate::tool::ToolCall;
|
||||
|
||||
@@ -95,6 +95,81 @@ impl Handler<TextBlockKind> for ClosureTextBlockHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 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
|
||||
// =============================================================================
|
||||
|
||||
@@ -53,7 +53,7 @@ pub mod tool;
|
||||
pub mod tool_server;
|
||||
pub mod usage_record;
|
||||
|
||||
pub use callback::{TextBlockScope, ToolUseBlockScope};
|
||||
pub use callback::{TextBlockScope, ThinkingBlockScope, ToolUseBlockScope};
|
||||
pub use handler::ToolUseBlockStart;
|
||||
pub use interceptor::Interceptor;
|
||||
pub use message::{ContentPart, Item, Message, Role};
|
||||
|
||||
@@ -8,8 +8,8 @@ use tracing::{debug, info, trace, warn};
|
||||
use crate::{
|
||||
Item,
|
||||
callback::{
|
||||
ClosureMetaHandler, ClosureTextBlockHandler, ClosureToolUseBlockHandler, TextBlockScope,
|
||||
ToolUseBlockScope,
|
||||
ClosureMetaHandler, ClosureTextBlockHandler, ClosureThinkingBlockHandler,
|
||||
ClosureToolUseBlockHandler, TextBlockScope, ThinkingBlockScope, ToolUseBlockScope,
|
||||
},
|
||||
handler::{ErrorKind, StatusKind, ToolUseBlockStart, UsageKind},
|
||||
interceptor::{
|
||||
@@ -237,6 +237,21 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Register a thinking block observer with scoped callbacks.
|
||||
///
|
||||
/// Mirrors `on_text_block`. Some providers don't expose plaintext
|
||||
/// reasoning content; in that case the block fires Start and Stop
|
||||
/// with no Delta in between, and `on_stop` receives an empty string.
|
||||
pub fn on_thinking_block(
|
||||
&mut self,
|
||||
setup: impl FnMut(&mut ThinkingBlockScope) + Send + Sync + 'static,
|
||||
) {
|
||||
self.timeline
|
||||
.on_thinking_block(ClosureThinkingBlockHandler {
|
||||
setup: Box::new(setup),
|
||||
});
|
||||
}
|
||||
|
||||
/// Register a tool use block observer with scoped callbacks.
|
||||
///
|
||||
/// The setup closure receives `&ToolUseBlockStart` (containing `id` and `name`)
|
||||
|
||||
Reference in New Issue
Block a user