Skeleton for the Coding Agent. (#1)

Reviewed-on: #1
Co-authored-by: Drew Galbraith <drew@tiramisu.one>
Co-committed-by: Drew Galbraith <drew@tiramisu.one>
This commit is contained in:
Drew 2026-02-24 19:50:38 +00:00 committed by Drew
parent 42e3ddacc2
commit 5d213b43d3
15 changed files with 5071 additions and 12 deletions

89
src/core/history.rs Normal file
View file

@ -0,0 +1,89 @@
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
}
}
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 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}"));
}
}
}