use super::{ApiClient, BareLoop, Instant, LoopError};
#[cfg(feature = "hooks")]
use super::{CompactTrigger, PostCompactContext, PreCompactContext};
use crate::compact::EnsureContextResult;
use crate::capabilities::Compactable;
#[cfg(feature = "hooks")]
use crate::capabilities::Hookable;
use crate::observer::CompactedContext;
impl<C: ApiClient> BareLoop<C> {
pub(super) async fn maybe_compact_context(&mut self, turn: usize) -> Result<(), LoopError> {
let Some(ctx_manager) = self.managers.context_manager() else {
return Ok(());
};
#[cfg(feature = "hooks")]
let messages_before = self.conversation.len();
#[cfg(feature = "hooks")]
if let Some(executor) = self.managers.hook_executor() {
let tokens_before =
crate::compact::CompactionOutcome::estimate_tokens(&self.conversation);
let ctx = PreCompactContext {
trigger: CompactTrigger::Auto,
custom_instructions: None,
message_count: messages_before,
tokens_before,
context_window: self.config.context_window,
session_id: self.config.session_id,
};
let hook_result = executor.check_pre_compact(&ctx);
if hook_result.abort {
return Ok(());
}
}
let compact_start = Instant::now();
let result = ctx_manager
.ensure_context_fits(std::mem::take(&mut self.conversation), turn)
.await;
#[cfg(feature = "hooks")]
let compact_duration_ms = u64::try_from(compact_start.elapsed().as_millis()).unwrap_or(0);
#[cfg(not(feature = "hooks"))]
let _ = compact_start;
match result {
Ok(EnsureContextResult::Compacted(outcome)) => {
self.conversation = outcome.messages;
#[cfg(feature = "hooks")]
let messages_after = self.conversation.len();
let tokens_before = outcome.tokens_after.saturating_add(outcome.tokens_saved);
self.managers.observers().on_compaction(&CompactedContext {
tokens_before,
tokens_after: outcome.tokens_after,
tokens_saved: outcome.tokens_saved,
});
#[cfg(feature = "hooks")]
if let Some(executor) = self.managers.hook_executor() {
let messages_compacted = messages_before.saturating_sub(messages_after);
let ctx = PostCompactContext {
trigger: CompactTrigger::Auto,
messages_compacted,
tokens_saved: outcome.tokens_saved,
tokens_after: outcome.tokens_after,
duration_ms: compact_duration_ms,
session_id: self.config.session_id,
};
executor.notify_post_compact(&ctx);
}
Ok(())
}
Ok(EnsureContextResult::NoAction(messages)) => {
self.conversation = messages;
Ok(())
}
Err(overflow) => Err(LoopError::ContextExceeded {
used: overflow.tokens_used,
limit: overflow.context_window,
}),
}
}
}