zeroclaw/src/observability/verbose.rs

101 lines
2.9 KiB
Rust

use super::traits::{Observer, ObserverEvent, ObserverMetric};
use std::any::Any;
/// Human-readable progress observer for interactive CLI sessions.
///
/// This observer prints compact `>` / `<` progress lines without exposing
/// prompt contents. It is intended to be opt-in (e.g. `--verbose`).
pub struct VerboseObserver;
impl VerboseObserver {
pub fn new() -> Self {
Self
}
}
impl Observer for VerboseObserver {
fn record_event(&self, event: &ObserverEvent) {
match event {
ObserverEvent::LlmRequest {
provider,
model,
messages_count,
} => {
eprintln!("> Thinking");
eprintln!(
"> Send (provider={}, model={}, messages={})",
provider, model, messages_count
);
}
ObserverEvent::LlmResponse {
duration, success, ..
} => {
let ms = u64::try_from(duration.as_millis()).unwrap_or(u64::MAX);
eprintln!("< Receive (success={success}, duration_ms={ms})");
}
ObserverEvent::ToolCallStart { tool } => {
eprintln!("> Tool {tool}");
}
ObserverEvent::ToolCall {
tool,
duration,
success,
} => {
let ms = u64::try_from(duration.as_millis()).unwrap_or(u64::MAX);
eprintln!("< Tool {tool} (success={success}, duration_ms={ms})");
}
ObserverEvent::TurnComplete => {
eprintln!("< Complete");
}
_ => {}
}
}
#[inline(always)]
fn record_metric(&self, _metric: &ObserverMetric) {}
fn name(&self) -> &str {
"verbose"
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn verbose_name() {
assert_eq!(VerboseObserver::new().name(), "verbose");
}
#[test]
fn verbose_events_do_not_panic() {
let obs = VerboseObserver::new();
obs.record_event(&ObserverEvent::LlmRequest {
provider: "openrouter".into(),
model: "claude".into(),
messages_count: 3,
});
obs.record_event(&ObserverEvent::LlmResponse {
provider: "openrouter".into(),
model: "claude".into(),
duration: Duration::from_millis(12),
success: true,
error_message: None,
});
obs.record_event(&ObserverEvent::ToolCallStart {
tool: "shell".into(),
});
obs.record_event(&ObserverEvent::ToolCall {
tool: "shell".into(),
duration: Duration::from_millis(2),
success: true,
});
obs.record_event(&ObserverEvent::TurnComplete);
}
}