use crate::session::{Message, Role};
pub fn truncate_messages(messages: Vec<Message>, keep_recent: usize) -> Vec<Message> {
if messages.len() <= keep_recent {
return messages;
}
if keep_recent == 0 {
if let Some(first) = messages.first() {
if first.role == Role::System {
return vec![messages.into_iter().next().unwrap()];
}
}
return Vec::new();
}
let has_system_prefix = messages
.first()
.map(|m| m.role == Role::System)
.unwrap_or(false);
if has_system_prefix {
let total = messages.len();
let skip = (total - 1).saturating_sub(keep_recent);
let mut result = Vec::with_capacity(1 + keep_recent);
let mut iter = messages.into_iter();
result.push(iter.next().unwrap()); for msg in iter.skip(skip) {
result.push(msg);
}
result
} else {
let skip = messages.len() - keep_recent;
messages.into_iter().skip(skip).collect()
}
}
pub fn summarize_messages(
messages: Vec<Message>,
keep_recent: usize,
summary_text: &str,
) -> Vec<Message> {
if messages.is_empty() {
return vec![Message::system(&format!(
"[Conversation Summary]\n{}",
summary_text
))];
}
if messages.len() <= keep_recent {
return messages;
}
let has_system_prefix = messages
.first()
.map(|m| m.role == Role::System)
.unwrap_or(false);
let summary_msg = Message::system(&format!("[Conversation Summary]\n{}", summary_text));
if has_system_prefix {
let total = messages.len();
let skip = (total - 1).saturating_sub(keep_recent);
let mut result = Vec::with_capacity(2 + keep_recent);
let mut iter = messages.into_iter();
result.push(iter.next().unwrap()); result.push(summary_msg);
for msg in iter.skip(skip) {
result.push(msg);
}
result
} else {
let total = messages.len();
let skip = total - keep_recent;
let mut result = Vec::with_capacity(1 + keep_recent);
result.push(summary_msg);
for msg in messages.into_iter().skip(skip) {
result.push(msg);
}
result
}
}
pub fn build_summary_prompt(messages: &[Message]) -> String {
let mut transcript = String::new();
for msg in messages {
transcript.push_str(&format!("{}: {}\n", msg.role, msg.content));
}
format!(
"Summarize the following conversation focusing on key decisions, \
information exchanged, and actions taken. Be concise.\n\n{}",
transcript
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_truncate_keeps_n_recent() {
let msgs = vec![
Message::user("one"),
Message::user("two"),
Message::user("three"),
Message::user("four"),
];
let result = truncate_messages(msgs, 2);
assert_eq!(result.len(), 2);
assert_eq!(result[0].content, "three");
assert_eq!(result[1].content, "four");
}
#[test]
fn test_truncate_preserves_system_message() {
let msgs = vec![
Message::system("system prompt"),
Message::user("one"),
Message::user("two"),
Message::user("three"),
];
let result = truncate_messages(msgs, 2);
assert_eq!(result.len(), 3); assert_eq!(result[0].role, Role::System);
assert_eq!(result[0].content, "system prompt");
assert_eq!(result[1].content, "two");
assert_eq!(result[2].content, "three");
}
#[test]
fn test_truncate_empty_messages() {
let result = truncate_messages(Vec::new(), 5);
assert!(result.is_empty());
}
#[test]
fn test_truncate_keep_greater_than_len() {
let msgs = vec![Message::user("one"), Message::user("two")];
let result = truncate_messages(msgs, 10);
assert_eq!(result.len(), 2);
assert_eq!(result[0].content, "one");
assert_eq!(result[1].content, "two");
}
#[test]
fn test_truncate_keep_equal_to_len() {
let msgs = vec![
Message::user("one"),
Message::user("two"),
Message::user("three"),
];
let result = truncate_messages(msgs, 3);
assert_eq!(result.len(), 3);
}
#[test]
fn test_truncate_keep_zero() {
let msgs = vec![Message::user("one"), Message::user("two")];
let result = truncate_messages(msgs, 0);
assert!(result.is_empty());
}
#[test]
fn test_truncate_keep_zero_with_system() {
let msgs = vec![
Message::system("sys"),
Message::user("one"),
Message::user("two"),
];
let result = truncate_messages(msgs, 0);
assert_eq!(result.len(), 1);
assert_eq!(result[0].role, Role::System);
assert_eq!(result[0].content, "sys");
}
#[test]
fn test_truncate_single_message() {
let msgs = vec![Message::user("only")];
let result = truncate_messages(msgs, 1);
assert_eq!(result.len(), 1);
assert_eq!(result[0].content, "only");
}
#[test]
fn test_summarize_with_system_message() {
let msgs = vec![
Message::system("You are helpful."),
Message::user("Tell me about Rust"),
Message::assistant("Rust is great."),
Message::user("And async?"),
Message::assistant("Use tokio."),
];
let result = summarize_messages(msgs, 2, "Discussed Rust basics.");
assert_eq!(result.len(), 4);
assert_eq!(result[0].role, Role::System);
assert_eq!(result[0].content, "You are helpful.");
assert_eq!(result[1].role, Role::System);
assert!(result[1].content.contains("[Conversation Summary]"));
assert!(result[1].content.contains("Discussed Rust basics."));
assert_eq!(result[2].content, "And async?");
assert_eq!(result[3].content, "Use tokio.");
}
#[test]
fn test_summarize_without_system_message() {
let msgs = vec![
Message::user("Hello"),
Message::assistant("Hi!"),
Message::user("Bye"),
Message::assistant("Goodbye!"),
];
let result = summarize_messages(msgs, 2, "User greeted.");
assert_eq!(result.len(), 3);
assert_eq!(result[0].role, Role::System);
assert!(result[0].content.contains("[Conversation Summary]"));
assert!(result[0].content.contains("User greeted."));
assert_eq!(result[1].content, "Bye");
assert_eq!(result[2].content, "Goodbye!");
}
#[test]
fn test_summarize_empty_messages() {
let result = summarize_messages(Vec::new(), 2, "Nothing happened.");
assert_eq!(result.len(), 1);
assert!(result[0].content.contains("[Conversation Summary]"));
assert!(result[0].content.contains("Nothing happened."));
}
#[test]
fn test_summarize_keep_greater_than_len() {
let msgs = vec![Message::user("one"), Message::user("two")];
let result = summarize_messages(msgs, 10, "summary");
assert_eq!(result.len(), 2);
assert_eq!(result[0].content, "one");
assert_eq!(result[1].content, "two");
}
#[test]
fn test_build_summary_prompt_includes_content() {
let msgs = vec![
Message::user("What is Rust?"),
Message::assistant("A systems programming language."),
];
let prompt = build_summary_prompt(&msgs);
assert!(prompt.contains("What is Rust?"));
assert!(prompt.contains("A systems programming language."));
}
#[test]
fn test_build_summary_prompt_includes_role_labels() {
let msgs = vec![
Message::user("Hi"),
Message::assistant("Hello"),
Message::system("Be concise"),
];
let prompt = build_summary_prompt(&msgs);
assert!(prompt.contains("user: Hi"));
assert!(prompt.contains("assistant: Hello"));
assert!(prompt.contains("system: Be concise"));
}
#[test]
fn test_build_summary_prompt_includes_instruction() {
let msgs = vec![Message::user("test")];
let prompt = build_summary_prompt(&msgs);
assert!(prompt.contains("Summarize the following conversation"));
assert!(prompt.contains("key decisions"));
assert!(prompt.contains("Be concise"));
}
#[test]
fn test_build_summary_prompt_empty_messages() {
let prompt = build_summary_prompt(&[]);
assert!(prompt.contains("Summarize the following conversation"));
assert!(!prompt.contains("user:"));
}
}