skate/src/core/history.rs

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}"));
}
}
}