use std::sync::Mutex;
use std::time::{Duration, Instant};
use tau_cli_term::RendererDeliveryId;
use tau_proto::Event;
use super::LAST_HANDLER_STALL_WARNING;
use crate::MUTEX_POISONED;
pub(super) fn admit_handler_stall_warning(now: Instant) -> bool {
let mut last = LAST_HANDLER_STALL_WARNING
.get_or_init(|| Mutex::new(None))
.lock()
.expect(MUTEX_POISONED);
if last.is_some_and(|last| now.duration_since(last) < Duration::from_secs(5)) {
return false;
}
*last = Some(now);
true
}
pub(super) struct HandlerProgress {
pub(super) delivery_id: Option<RendererDeliveryId>,
pub(super) event_name: tau_proto::EventName,
pub(super) started_at: Instant,
}
impl Drop for HandlerProgress {
fn drop(&mut self) {
let elapsed = self.started_at.elapsed();
tracing::trace!(
target: "tau_cli::frontend_progress",
delivery_id = self.delivery_id.map(RendererDeliveryId::get),
event_name = %self.event_name,
handler_us = elapsed.as_micros(),
"renderer handler finished"
);
if Duration::from_millis(500) <= elapsed && admit_handler_stall_warning(Instant::now()) {
tracing::warn!(
target: "tau_cli::frontend_progress",
delivery_id = self.delivery_id.map(RendererDeliveryId::get),
event_name = %self.event_name,
handler_ms = elapsed.as_millis(),
"renderer handler stalled"
);
}
}
}
pub(super) fn presentation_fact(event: &Event) -> Option<PresentationFactClass> {
presentation_fact_name(&event.name())
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub(crate) enum PresentationFactClass {
PromptQueued,
PromptSubmitted,
PromptSteered,
ResponseUpdated,
ResponseFinished,
PromptTerminated,
}
impl PresentationFactClass {
pub(super) const fn label(self) -> &'static str {
match self {
Self::PromptQueued => "agent.prompt_queued/prompt_queued",
Self::PromptSubmitted => "agent.prompt_submitted/prompt_submitted",
Self::PromptSteered => "agent.prompt_steered/prompt_steered",
Self::ResponseUpdated => "provider.response_updated/response_updated",
Self::ResponseFinished => "provider.response_finished/response_finished",
Self::PromptTerminated => "agent.prompt_terminated/prompt_terminated",
}
}
fn key(self) -> tau_cli_term::PresentationObservationKey {
tau_cli_term::PresentationObservationKey::new(self as u8)
.expect("finite CLI presentation class must fit the raw invalidation mask")
}
fn invalidates(self) -> tau_cli_term::PresentationInvalidation {
let none = tau_cli_term::PresentationInvalidation::none();
match self {
Self::PromptSubmitted => none.with(Self::PromptQueued.key()),
Self::ResponseFinished => none.with(Self::ResponseUpdated.key()),
Self::PromptTerminated => none
.with(Self::PromptQueued.key())
.with(Self::PromptSubmitted.key())
.with(Self::ResponseUpdated.key()),
_ => none,
}
}
pub(crate) fn opaque_fact(self) -> tau_cli_term::OpaquePresentationFact {
tau_cli_term::OpaquePresentationFact::new(self.label(), self.key(), self.invalidates())
}
pub(super) const fn invalidates_pending(self) -> bool {
matches!(
self,
Self::PromptSubmitted | Self::ResponseFinished | Self::PromptTerminated
)
}
}
pub(super) fn presentation_fact_name(
event_name: &tau_proto::EventName,
) -> Option<PresentationFactClass> {
use PresentationFactClass as Class;
match event_name {
name if name == &tau_proto::EventName::AGENT_PROMPT_QUEUED => Some(Class::PromptQueued),
name if name == &tau_proto::EventName::AGENT_PROMPT_SUBMITTED => {
Some(Class::PromptSubmitted)
}
name if name == &tau_proto::EventName::AGENT_PROMPT_STEERED => Some(Class::PromptSteered),
name if name == &tau_proto::EventName::PROVIDER_RESPONSE_UPDATED => {
Some(Class::ResponseUpdated)
}
name if name == &tau_proto::EventName::PROVIDER_RESPONSE_FINISHED => {
Some(Class::ResponseFinished)
}
name if name == &tau_proto::EventName::AGENT_PROMPT_TERMINATED => {
Some(Class::PromptTerminated)
}
_ => None,
}
}