use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum GraphRole {
User,
Assistant,
Tool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GraphMessage {
pub role: GraphRole,
pub content: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GraphRunState {
pub messages: Vec<GraphMessage>,
pub resolved: bool,
pub iterations: u32,
#[serde(default)]
pub scratchpad: serde_json::Value,
}
impl GraphRunState {
pub fn push_message(&mut self, role: GraphRole, content: impl Into<String>) {
self.messages.push(GraphMessage {
role,
content: content.into(),
});
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn push_message_appends_never_replaces() {
let mut s = GraphRunState::default();
s.push_message(GraphRole::User, "hi");
assert_eq!(s.messages.len(), 1);
s.push_message(GraphRole::Assistant, "hello");
s.push_message(GraphRole::Tool, "{\"found\":true}");
assert_eq!(s.messages.len(), 3);
assert_eq!(s.messages[0].role, GraphRole::User);
assert_eq!(s.messages[2].content, "{\"found\":true}");
}
#[test]
fn json_round_trips_camel_case() {
let mut s = GraphRunState::default();
s.push_message(GraphRole::User, "hi");
s.resolved = true;
s.iterations = 2;
let json = serde_json::to_string(&s).unwrap();
assert!(json.contains("\"iterations\":2"), "json: {json}");
let back: GraphRunState = serde_json::from_str(&json).unwrap();
assert_eq!(back.resolved, s.resolved);
assert_eq!(back.iterations, s.iterations);
assert_eq!(back.messages.len(), s.messages.len());
}
#[test]
fn role_serialises_lowercase() {
let json = serde_json::to_string(&GraphRole::Assistant).unwrap();
assert_eq!(json, r#""assistant""#);
}
}