105 lines
3 KiB
Rust
105 lines
3 KiB
Rust
use crate::core::types::ConversationMessage;
|
|
|
|
/// The in-memory conversation history for the current session.
|
|
///
|
|
/// Stores messages as a flat ordered list. Each [`push`][`Self::push`] appends
|
|
/// one message; [`messages`][`Self::messages`] returns a slice over all of them.
|
|
///
|
|
/// This is a flat list for Phase 1. Phases 3+ will introduce a tree structure
|
|
/// (each event carrying a `parent_id`) to support conversation branching and
|
|
/// sub-agent threads. The flat model is upward-compatible: a tree is just a
|
|
/// linear chain of parent IDs when there is no branching.
|
|
pub struct ConversationHistory {
|
|
messages: Vec<ConversationMessage>,
|
|
}
|
|
|
|
impl ConversationHistory {
|
|
/// Create an empty history.
|
|
pub fn new() -> Self {
|
|
Self {
|
|
messages: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Append one message to the end of the history.
|
|
pub fn push(&mut self, message: ConversationMessage) {
|
|
self.messages.push(message);
|
|
}
|
|
|
|
/// Return the full ordered message list, oldest-first.
|
|
///
|
|
/// This slice is what gets serialised and sent to the provider on each
|
|
/// turn -- the provider needs the full prior context to generate a coherent
|
|
/// continuation.
|
|
pub fn messages(&self) -> &[ConversationMessage] {
|
|
&self.messages
|
|
}
|
|
|
|
/// Remove all messages from the history.
|
|
pub fn clear(&mut self) {
|
|
self.messages.clear();
|
|
}
|
|
}
|
|
|
|
impl Default for ConversationHistory {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::core::types::Role;
|
|
|
|
#[test]
|
|
fn new_history_is_empty() {
|
|
let history = ConversationHistory::new();
|
|
assert!(history.messages().is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn push_and_read_roundtrip() {
|
|
let mut history = ConversationHistory::new();
|
|
history.push(ConversationMessage {
|
|
role: Role::User,
|
|
content: "hello".to_string(),
|
|
});
|
|
history.push(ConversationMessage {
|
|
role: Role::Assistant,
|
|
content: "hi there".to_string(),
|
|
});
|
|
|
|
let msgs = history.messages();
|
|
assert_eq!(msgs.len(), 2);
|
|
assert_eq!(msgs[0].role, Role::User);
|
|
assert_eq!(msgs[0].content, "hello");
|
|
assert_eq!(msgs[1].role, Role::Assistant);
|
|
assert_eq!(msgs[1].content, "hi there");
|
|
}
|
|
|
|
#[test]
|
|
fn clear_empties_history() {
|
|
let mut history = ConversationHistory::new();
|
|
history.push(ConversationMessage {
|
|
role: Role::User,
|
|
content: "hello".to_string(),
|
|
});
|
|
history.clear();
|
|
assert!(history.messages().is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn messages_preserves_insertion_order() {
|
|
let mut history = ConversationHistory::new();
|
|
for i in 0u32..5 {
|
|
history.push(ConversationMessage {
|
|
role: Role::User,
|
|
content: format!("msg {i}"),
|
|
});
|
|
}
|
|
for (i, msg) in history.messages().iter().enumerate() {
|
|
assert_eq!(msg.content, format!("msg {i}"));
|
|
}
|
|
}
|
|
}
|