use super::compaction::*;
use super::config::*;
use super::token::*;
use crate::types::*;
use chrono::Utc;
#[allow(unused_imports)]
use std::sync::Arc;
pub trait CompactionStrategy: Send + Sync {
fn compact(
&self,
messages: Vec<AgentMessage>, config: &ContextConfig, ) -> Vec<AgentMessage>;
}
pub struct DefaultCompaction;
impl CompactionStrategy for DefaultCompaction {
fn compact(
&self,
messages: Vec<AgentMessage>, config: &ContextConfig, ) -> Vec<AgentMessage> {
super::compact_messages::compact_messages_with_counter(
messages,
config,
config.token_counter.as_ref(),
)
}
}
use crate::session::LoopRecord;
pub trait BlockCompactionStrategy: Send + Sync {
fn keep_first(
&self,
record: &LoopRecord,
turn_map: &TurnMap,
config: &CompactionConfig,
) -> Option<TurnRange>;
fn keep_recent(
&self,
record: &LoopRecord,
turn_map: &TurnMap,
config: &CompactionConfig,
) -> Option<CompactedSection>;
fn keep_compacted(
&self,
record: &LoopRecord,
turn_map: &TurnMap,
config: &CompactionConfig,
is_most_recent: bool,
) -> Option<CompactedSection>;
fn compact(
&self,
record: &LoopRecord,
config: &CompactionConfig,
is_most_recent: bool,
) -> CompactionBlock {
let turn_map = TurnMap::from_messages(&record.messages);
CompactionBlock {
keep_first: if is_most_recent {
self.keep_first(record, &turn_map, config)
} else {
None
},
keep_recent: if is_most_recent {
self.keep_recent(record, &turn_map, config)
} else {
None
},
keep_compacted: self.keep_compacted(record, &turn_map, config, is_most_recent),
created_at: Utc::now(),
}
}
}
pub struct DefaultBlockCompaction;
impl BlockCompactionStrategy for DefaultBlockCompaction {
fn keep_first(
&self,
_record: &LoopRecord,
turn_map: &TurnMap,
config: &CompactionConfig,
) -> Option<TurnRange> {
let total = turn_map.turn_count();
if total == 0 {
return None;
}
let end = (config.keep_first_turns as u32)
.min(total)
.saturating_sub(1);
Some(TurnRange {
start_turn: 0,
end_turn: end,
})
}
fn keep_recent(
&self,
record: &LoopRecord,
turn_map: &TurnMap,
config: &CompactionConfig,
) -> Option<CompactedSection> {
let total = turn_map.turn_count();
if total == 0 {
return None;
}
let recent_start = total.saturating_sub(config.keep_recent_turns as u32);
let range = TurnRange {
start_turn: recent_start,
end_turn: total - 1,
};
let msgs = turn_map.messages_for_range(&range, &record.messages);
let truncated: Vec<AgentMessage> = msgs
.iter()
.map(|m| {
if let AgentMessage::Llm(lm) = m {
if let Message::ToolResult {
tool_call_id,
tool_name,
content,
is_error,
timestamp,
} = &lm.message
{
let truncated_content: Vec<Content> = content
.iter()
.map(|c| match c {
Content::Text { text } => Content::Text {
text: super::compact_messages::truncate_text_head_tail(
text,
config.tool_output_max_lines,
),
},
other => other.clone(),
})
.collect();
return AgentMessage::Llm(LlmMessage {
message: Message::ToolResult {
tool_call_id: tool_call_id.clone(),
tool_name: tool_name.clone(),
content: truncated_content,
is_error: *is_error,
timestamp: *timestamp,
},
turn_id: lm.turn_id.clone(),
});
}
}
m.clone()
})
.collect();
Some(CompactedSection {
range,
messages: truncated,
})
}
fn keep_compacted(
&self,
record: &LoopRecord,
turn_map: &TurnMap,
config: &CompactionConfig,
is_most_recent: bool,
) -> Option<CompactedSection> {
let total = turn_map.turn_count();
if total == 0 {
return None;
}
let (start, end) = if is_most_recent {
let first_end = (config.keep_first_turns as u32).min(total);
let recent_start = total.saturating_sub(config.keep_recent_turns as u32);
if first_end >= recent_start {
return None; }
(first_end, recent_start.saturating_sub(1))
} else {
(0, total.saturating_sub(1))
};
let range = TurnRange {
start_turn: start,
end_turn: end,
};
let msgs = turn_map.messages_for_range(&range, &record.messages);
let mut summaries: Vec<AgentMessage> = Vec::new();
let mut token_budget = config.max_summary_tokens;
for msg in msgs {
if let AgentMessage::Llm(lm) = msg {
if let Message::Assistant { content, .. } = &lm.message {
let text_parts: Vec<&str> = content
.iter()
.filter_map(|c| match c {
Content::Text { text } if text.len() <= 200 => Some(text.as_str()),
_ => None,
})
.collect();
let tool_count = content
.iter()
.filter(|c| matches!(c, Content::ToolCall { .. }))
.count();
let summary = if !text_parts.is_empty() {
text_parts.join(" ")
} else if tool_count > 0 {
format!("[Assistant used {} tool(s)]", tool_count)
} else {
"[Assistant response]".into()
};
let summary_text = format!("[Summary] {}", summary);
let est_tokens = estimate_tokens(&summary_text);
if est_tokens > token_budget {
break; }
token_budget -= est_tokens;
summaries.push(AgentMessage::Llm(LlmMessage::new(Message::user(
&summary_text,
))));
}
}
}
if summaries.is_empty() {
return None;
}
Some(CompactedSection {
range,
messages: summaries,
})
}
}