use crate::message::Message;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompactReason {
ThresholdExceeded,
Emergency,
Manual,
}
impl fmt::Display for CompactReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ThresholdExceeded => write!(f, "threshold exceeded"),
Self::Emergency => write!(f, "emergency"),
Self::Manual => write!(f, "manual"),
}
}
}
#[derive(Debug, Clone)]
pub struct CompactionContext {
pub tokens_before: u64,
pub reason: CompactReason,
pub context_window: u64,
pub turn: usize,
}
#[derive(Debug, Clone)]
pub struct CompactionOutcome {
pub messages: Vec<Message>,
pub tokens_after: u64,
pub tokens_saved: u64,
pub success: bool,
pub error: Option<String>,
}
impl CompactionOutcome {
#[must_use]
pub fn no_change(messages: Vec<Message>) -> Self {
let tokens = Self::estimate_tokens(&messages);
Self {
messages,
tokens_after: tokens,
tokens_saved: 0,
success: true,
error: None,
}
}
#[must_use]
pub fn compacted(messages: Vec<Message>, tokens_before: u64, tokens_after: u64) -> Self {
Self {
tokens_saved: tokens_before.saturating_sub(tokens_after),
messages,
tokens_after,
success: true,
error: None,
}
}
#[must_use]
pub fn estimate_tokens(messages: &[Message]) -> u64 {
super::ContextManager::estimate_tokens(messages)
}
}
#[derive(Debug, Clone)]
pub struct CompactTelemetry {
pub trigger: CompactReason,
pub pre_compact: PreCompactStats,
pub post_compact: PostCompactStats,
pub duration: std::time::Duration,
}
#[derive(Debug, Clone)]
pub struct PreCompactStats {
pub total_messages: usize,
pub estimated_tokens: u64,
pub user_messages: usize,
pub assistant_messages: usize,
pub tool_messages: usize,
}
#[derive(Debug, Clone)]
pub struct PostCompactStats {
pub total_messages: usize,
pub estimated_tokens: u64,
pub tokens_saved: u64,
pub percent_saved: u8,
}
#[derive(Debug, Clone)]
pub struct ContextOverflow {
pub tokens_used: u64,
pub context_window: u64,
pub message_count: usize,
pub trigger: CompactReason,
pub compactor_error: Option<String>,
}
impl ContextOverflow {
#[must_use]
pub fn overflow(&self) -> u64 {
self.tokens_used.saturating_sub(self.context_window)
}
#[must_use]
pub fn utilization(&self) -> f64 {
if self.context_window == 0 {
return f64::INFINITY;
}
f64::from(u32::try_from(self.tokens_used).unwrap_or(u32::MAX))
/ f64::from(u32::try_from(self.context_window).unwrap_or(u32::MAX))
}
}
impl fmt::Display for ContextOverflow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"context overflow: {} tokens used of {} window ({} messages, {} overflow)",
self.tokens_used,
self.context_window,
self.message_count,
self.overflow()
)
}
}
impl std::error::Error for ContextOverflow {}
#[derive(Debug, Clone)]
pub enum EnsureContextResult {
Compacted(CompactionOutcome),
NoAction(Vec<Message>),
}
impl EnsureContextResult {
#[must_use]
pub fn into_messages(self) -> Vec<Message> {
match self {
Self::Compacted(outcome) => outcome.messages,
Self::NoAction(messages) => messages,
}
}
}