use super::{ApiClient, BareLoop, Duration, LoopError, Run};
#[cfg(feature = "hooks")]
use crate::capabilities::Hookable;
#[cfg(feature = "hooks")]
use crate::hooks::context::{
RunEndContext as HookRunEndContext, RunEndReason, RunStartContext as HookRunStartContext,
};
use crate::observer::{RunEndContext, RunStartContext};
impl<C: ApiClient> BareLoop<C> {
pub(super) fn notify_run_start(&self) {
self.managers.observers().on_run_start(&RunStartContext {
session_id: self.session.id,
});
#[cfg(feature = "hooks")]
self.notify_run_start_hook();
}
pub(super) fn notify_run_end(
&self,
result: &Run,
duration: Duration,
error: Option<&LoopError>,
) {
#[cfg(feature = "hooks")]
self.notify_run_end_hook(result, error, duration);
self.managers.observers().on_run_end(&RunEndContext {
success: error.is_none(),
error: error.map_or_else(|| None, |e| Some(e.to_string())),
total_turns: result.turn_count(),
duration_ms: Self::millis_u64(duration),
});
}
#[cfg(feature = "hooks")]
fn run_end_reason(&self, error: Option<&LoopError>) -> RunEndReason {
if self.is_cancelled() {
return RunEndReason::Cancelled;
}
match error {
Some(LoopError::ContextExceeded { .. }) => RunEndReason::ContextOverflow,
Some(LoopError::MaxTurnsExceeded { .. }) => RunEndReason::MaxTurns,
Some(LoopError::Cancelled) => RunEndReason::Cancelled,
Some(_) => RunEndReason::Error,
None => RunEndReason::Complete,
}
}
#[cfg(feature = "hooks")]
fn notify_run_start_hook(&self) {
let Some(executor) = self.managers.hook_executor() else {
return;
};
let ctx = HookRunStartContext {
session_id: self.session.id,
model: self.client.model(),
working_directory: std::env::current_dir()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_default(),
};
executor.notify_run_start(&ctx);
}
#[cfg(feature = "hooks")]
fn notify_run_end_hook(&self, result: &Run, error: Option<&LoopError>, duration: Duration) {
let Some(executor) = self.managers.hook_executor() else {
return;
};
let reason = self.run_end_reason(error);
let ctx = HookRunEndContext {
session_id: self.session.id,
reason,
total_turns: result.turn_count(),
total_tokens: result.total_tokens(),
duration_secs: duration.as_secs(),
};
executor.notify_run_end(&ctx);
}
pub(super) fn millis_u64(duration: Duration) -> u64 {
u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
}
}