use zeph_llm::provider::Message;
pub enum ContextSlot {
Summaries(Option<Message>),
CrossSession(Option<Message>),
SemanticRecall(Option<Message>, Option<f32>),
DocumentRag(Option<Message>),
Corrections(Option<Message>),
CodeContext(Option<String>),
GraphFacts(Option<Message>),
PersonaFacts(Option<Message>),
TrajectoryHints(Option<Message>),
TreeMemory(Option<Message>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompactionOutcome {
Compacted,
ProbeRejected,
NoChange,
}
pub const PERSONA_PREFIX: &str = "[Persona context]\n";
pub const TRAJECTORY_PREFIX: &str = "[Past experience]\n";
pub const TREE_MEMORY_PREFIX: &str = "[Memory summary]\n";
#[must_use]
pub fn chunk_messages(
messages: &[Message],
budget: usize,
oversized: usize,
tc: &zeph_memory::TokenCounter,
) -> Vec<Vec<Message>> {
let mut chunks: Vec<Vec<Message>> = Vec::new();
let mut current: Vec<Message> = Vec::new();
let mut current_tokens = 0usize;
for msg in messages {
let msg_tokens = tc.count_message_tokens(msg);
if msg_tokens >= oversized {
if !current.is_empty() {
chunks.push(std::mem::take(&mut current));
current_tokens = 0;
}
chunks.push(vec![msg.clone()]);
} else if current_tokens + msg_tokens > budget && !current.is_empty() {
chunks.push(std::mem::take(&mut current));
current_tokens = 0;
current.push(msg.clone());
current_tokens += msg_tokens;
} else {
current.push(msg.clone());
current_tokens += msg_tokens;
}
}
if !current.is_empty() {
chunks.push(current);
}
if chunks.is_empty() {
chunks.push(Vec::new());
}
chunks
}
#[must_use]
pub fn cap_summary(s: String, max_chars: usize) -> String {
match s.char_indices().nth(max_chars) {
Some((byte_idx, _)) => {
tracing::warn!(
original_chars = s.chars().count(),
cap = max_chars,
"LLM summary exceeded cap, truncating"
);
format!("{}…", &s[..byte_idx])
}
None => s,
}
}