use llm_cascade::{Conversation, Message};
use serde::Serialize;
use crate::error::Result;
use crate::memory::state::ConversationStateAccess;
#[derive(Debug, Serialize)]
pub struct ConversationState {
pub messages: Vec<Message>,
pub system_prompt: String,
pub turn_count: usize,
pub task_id: String,
pub created_at: chrono::DateTime<chrono::Utc>,
}
impl ConversationState {
pub fn new(system_prompt: String, task_id: String) -> Self {
Self {
messages: Vec::new(),
system_prompt,
turn_count: 0,
task_id,
created_at: chrono::Utc::now(),
}
}
pub fn add_user_message(&mut self, content: String) {
self.messages.push(Message::user(content));
self.turn_count += 1;
}
pub fn add_assistant_text(&mut self, content: String) {
self.messages.push(Message::assistant(content));
}
pub fn add_tool_result(&mut self, tool_call_id: &str, result: &str) {
self.messages
.push(Message::tool(result.to_string(), tool_call_id.to_string()));
}
pub fn to_conversation(&self) -> Conversation {
let mut all_msgs = vec![Message::system(&self.system_prompt)];
all_msgs.extend(self.messages.clone());
Conversation::new(all_msgs)
}
pub fn last_assistant_text(&self) -> Option<String> {
self.messages
.iter()
.rev()
.find(|m| matches!(m.role, llm_cascade::MessageRole::Assistant))
.map(|m| m.content.clone())
}
pub fn to_json(&self) -> Result<String> {
serde_json::to_string_pretty(self).map_err(Into::into)
}
pub fn to_json_file(&self, dir: &str) -> Result<std::path::PathBuf> {
std::fs::create_dir_all(dir)?;
let filename = format!(
"state_{}_{}.json",
self.created_at.format("%Y%m%d_%H%M%S"),
&self.task_id[..8.min(self.task_id.len())]
);
let path = std::path::PathBuf::from(dir).join(filename);
std::fs::write(&path, self.to_json()?)?;
Ok(path)
}
}
impl ConversationStateAccess for ConversationState {
fn messages(&self) -> &[Message] {
&self.messages
}
fn messages_mut(&mut self) -> &mut Vec<Message> {
&mut self.messages
}
fn system_prompt(&self) -> &str {
&self.system_prompt
}
}