use super::{ApiClient, BareLoop, LoopError};
#[cfg(feature = "hooks")]
use super::{CompactTrigger, Instant, PostCompactContext, PreCompactContext};
use crate::compact::EnsureContextResult;
use crate::capabilities::Compactable;
#[cfg(feature = "hooks")]
use crate::capabilities::Hookable;
use crate::message::Message;
use crate::observer::CompactedContext;
impl<C: ApiClient> BareLoop<C> {
pub(super) async fn run_compaction(
&mut self,
turn: usize,
reason: crate::compact::types::CompactReason,
) -> Result<(Vec<Message>, u64), LoopError> {
let history = self.machine.full_history();
let Some(ctx_manager) = self.managers.context_manager() else {
return Ok((history, 0));
};
#[cfg(feature = "hooks")]
if self.pre_compact_hook_aborts(&history) {
return Ok((history, 0));
}
#[cfg(feature = "hooks")]
let messages_before = history.len();
#[cfg(feature = "hooks")]
let compact_start = Instant::now();
let result = ctx_manager.compact_with_reason(history, turn, reason).await;
match result {
Ok(EnsureContextResult::Compacted(outcome)) => {
let tokens_after = outcome.tokens_after;
let tokens_saved = outcome.tokens_saved;
#[cfg(feature = "hooks")]
let messages_after = outcome.messages.len();
let tokens_before = tokens_after.saturating_add(tokens_saved);
self.managers.observers().on_compaction(&CompactedContext {
tokens_before,
tokens_after,
tokens_saved,
});
#[cfg(feature = "hooks")]
self.notify_post_compact_hook(
messages_before,
messages_after,
tokens_after,
tokens_saved,
compact_start.elapsed(),
);
Ok((outcome.messages, tokens_after))
}
Ok(EnsureContextResult::NoAction(messages)) => Ok((messages, 0)),
Err(overflow) => Err(LoopError::ContextExceeded {
used: overflow.tokens_used,
limit: overflow.context_window,
}),
}
}
#[cfg(feature = "hooks")]
fn pre_compact_hook_aborts(&self, history: &[Message]) -> bool {
let Some(executor) = self.managers.hook_executor() else {
return false;
};
let tokens_before = crate::compact::CompactionOutcome::estimate_tokens(history);
let ctx = PreCompactContext {
trigger: CompactTrigger::Auto,
custom_instructions: None,
message_count: history.len(),
tokens_before,
context_window: self.session.config.context_window,
session_id: self.session.id,
};
executor.check_pre_compact(&ctx).abort
}
#[cfg(feature = "hooks")]
fn notify_post_compact_hook(
&self,
messages_before: usize,
messages_after: usize,
tokens_after: u64,
tokens_saved: u64,
duration: std::time::Duration,
) {
let Some(executor) = self.managers.hook_executor() else {
return;
};
let ctx = PostCompactContext {
trigger: CompactTrigger::Auto,
messages_compacted: messages_before.saturating_sub(messages_after),
tokens_saved,
tokens_after,
duration_ms: u64::try_from(duration.as_millis()).unwrap_or(0),
session_id: self.session.id,
};
executor.notify_post_compact(&ctx);
}
}