use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use uuid::Uuid;
use super::garrison::GarrisonEntry;
use super::paladin::MaxLoops;
use crate::base::entity::node::Node;
pub type Paladin = Node<PaladinData>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaladinData {
pub system_prompt: String,
pub name: String,
pub user_name: String,
pub model: String,
pub temperature: f32,
pub max_loops: MaxLoops,
pub stop_words: Vec<String>,
pub status: PaladinStatus,
pub vision_enabled: bool,
pub autonomous_planning: bool,
pub autonomous_prompts: bool,
pub agent_description: String,
pub dynamic_temperature: bool,
}
impl Default for PaladinData {
fn default() -> Self {
Self {
system_prompt: String::new(),
name: String::new(),
user_name: String::new(),
model: String::new(),
temperature: 0.7,
max_loops: MaxLoops::Fixed(3),
stop_words: Vec::new(),
status: PaladinStatus::Idle,
vision_enabled: false,
autonomous_planning: false,
autonomous_prompts: false,
agent_description: String::new(),
dynamic_temperature: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum PaladinStatus {
Idle,
Executing,
Completed,
Failed(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaladinState {
pub paladin: Paladin,
pub garrison: Vec<GarrisonEntry>,
pub execution_history: Vec<ExecutionRecord>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub schema_version: String,
}
impl PaladinState {
pub fn new(
paladin: Paladin,
garrison: Vec<GarrisonEntry>,
execution_history: Vec<ExecutionRecord>,
) -> Self {
let now = Utc::now();
Self {
paladin,
garrison,
execution_history,
created_at: now,
updated_at: now,
schema_version: "1.0.0".to_string(),
}
}
pub fn update(
&mut self,
garrison: Vec<GarrisonEntry>,
execution_history: Vec<ExecutionRecord>,
) {
self.garrison = garrison;
self.execution_history = execution_history;
self.updated_at = Utc::now();
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionRecord {
pub timestamp: DateTime<Utc>,
pub input: String,
pub output: String,
pub status: ExecutionStatus,
pub loops_used: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ExecutionStatus {
Success,
Failed(String),
Timeout,
StopWordDetected,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BattalionState {
pub id: Uuid,
pub battalion_type: String,
pub config: BattalionConfig,
pub paladin_states: Vec<PaladinState>,
pub checkpoint: Option<CheckpointData>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub schema_version: String,
}
impl BattalionState {
pub fn new(
battalion_type: impl Into<String>,
config: BattalionConfig,
paladin_states: Vec<PaladinState>,
checkpoint: Option<CheckpointData>,
) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4(),
battalion_type: battalion_type.into(),
config,
paladin_states,
checkpoint,
created_at: now,
updated_at: now,
schema_version: "1.0.0".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BattalionConfig {
#[serde(default)]
pub max_concurrency: Option<usize>,
#[serde(default)]
pub timeout_seconds: Option<u64>,
#[serde(default)]
pub continue_on_error: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CheckpointData {
pub last_completed_index: Option<usize>,
pub completed_paladins: Vec<Uuid>,
pub failed_paladins: Vec<Uuid>,
pub checkpoint_time: DateTime<Utc>,
}
impl CheckpointData {
pub fn new() -> Self {
Self {
last_completed_index: None,
completed_paladins: Vec::new(),
failed_paladins: Vec::new(),
checkpoint_time: Utc::now(),
}
}
pub fn mark_completed(&mut self, paladin_id: Uuid, index: usize) {
self.completed_paladins.push(paladin_id);
self.last_completed_index = Some(index);
self.checkpoint_time = Utc::now();
}
pub fn mark_failed(&mut self, paladin_id: Uuid) {
self.failed_paladins.push(paladin_id);
self.checkpoint_time = Utc::now();
}
}
impl Default for CheckpointData {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateSummary {
pub id: Uuid,
pub state_type: StateType,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub file_path: PathBuf,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum StateType {
Paladin,
Battalion,
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_paladin_data() -> PaladinData {
PaladinData {
system_prompt: "You are a helpful assistant".to_string(),
name: "TestPaladin".to_string(),
user_name: "TestUser".to_string(),
model: "gpt-4".to_string(),
temperature: 0.7,
max_loops: MaxLoops::Fixed(3),
stop_words: vec!["STOP".to_string()],
status: PaladinStatus::Idle,
vision_enabled: false,
..Default::default()
}
}
fn create_test_paladin() -> Paladin {
Node::new(create_test_paladin_data(), Some("TestPaladin".to_string()))
}
#[test]
fn test_paladin_state_creation() {
let paladin = create_test_paladin();
let garrison = vec![];
let history = vec![];
let state = PaladinState::new(paladin, garrison, history);
assert_eq!(state.schema_version, "1.0.0");
assert_eq!(state.garrison.len(), 0);
assert_eq!(state.execution_history.len(), 0);
assert!(state.created_at <= Utc::now());
assert!(state.updated_at <= Utc::now());
}
#[test]
fn test_paladin_state_serialization_roundtrip() {
let paladin = create_test_paladin();
let garrison = vec![];
let history = vec![ExecutionRecord {
timestamp: Utc::now(),
input: "test input".to_string(),
output: "test output".to_string(),
status: ExecutionStatus::Success,
loops_used: 1,
}];
let state = PaladinState::new(paladin, garrison, history);
let json = serde_json::to_string(&state).expect("Failed to serialize");
let deserialized: PaladinState =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(deserialized.schema_version, state.schema_version);
assert_eq!(deserialized.execution_history.len(), 1);
assert_eq!(deserialized.execution_history[0].input, "test input");
}
#[test]
fn test_battalion_state_creation() {
let paladin = create_test_paladin();
let paladin_state = PaladinState::new(paladin, vec![], vec![]);
let config = BattalionConfig::default();
let battalion_state = BattalionState::new("Formation", config, vec![paladin_state], None);
assert_eq!(battalion_state.battalion_type, "Formation");
assert_eq!(battalion_state.schema_version, "1.0.0");
assert_eq!(battalion_state.paladin_states.len(), 1);
assert!(battalion_state.checkpoint.is_none());
}
#[test]
fn test_battalion_state_serialization_roundtrip() {
let paladin = create_test_paladin();
let paladin_state = PaladinState::new(paladin, vec![], vec![]);
let config = BattalionConfig {
max_concurrency: Some(4),
timeout_seconds: Some(300),
continue_on_error: true,
};
let checkpoint = Some(CheckpointData::new());
let battalion_state =
BattalionState::new("Phalanx", config, vec![paladin_state], checkpoint);
let json = serde_json::to_string(&battalion_state).expect("Failed to serialize");
let deserialized: BattalionState =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(deserialized.battalion_type, "Phalanx");
assert_eq!(deserialized.schema_version, "1.0.0");
assert!(deserialized.checkpoint.is_some());
}
#[test]
fn test_state_summary_creation() {
let summary = StateSummary {
id: Uuid::new_v4(),
state_type: StateType::Paladin,
created_at: Utc::now(),
updated_at: Utc::now(),
file_path: PathBuf::from("/tmp/test-state.json"),
};
assert_eq!(summary.state_type, StateType::Paladin);
assert_eq!(summary.file_path, PathBuf::from("/tmp/test-state.json"));
}
#[test]
fn test_checkpoint_data_serialization() {
let mut checkpoint = CheckpointData::new();
let paladin_id = Uuid::new_v4();
checkpoint.mark_completed(paladin_id, 0);
let json = serde_json::to_string(&checkpoint).expect("Failed to serialize");
let deserialized: CheckpointData =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(deserialized.last_completed_index, Some(0));
assert_eq!(deserialized.completed_paladins.len(), 1);
assert_eq!(deserialized.completed_paladins[0], paladin_id);
}
#[test]
fn test_schema_version_field_present() {
let paladin = create_test_paladin();
let state = PaladinState::new(paladin, vec![], vec![]);
let json = serde_json::to_string(&state).expect("Failed to serialize");
assert!(json.contains("schema_version"));
assert!(json.contains("1.0.0"));
}
#[test]
fn test_json_output_human_readable() {
let paladin = create_test_paladin();
let state = PaladinState::new(paladin, vec![], vec![]);
let json = serde_json::to_string_pretty(&state).expect("Failed to serialize");
assert!(json.contains('\n'));
assert!(json.contains("\"schema_version\""));
assert!(json.contains("\"created_at\""));
assert!(json.contains("\"updated_at\""));
}
#[test]
fn test_checkpoint_mark_completed() {
let mut checkpoint = CheckpointData::new();
let id1 = Uuid::new_v4();
let id2 = Uuid::new_v4();
checkpoint.mark_completed(id1, 0);
checkpoint.mark_completed(id2, 1);
assert_eq!(checkpoint.last_completed_index, Some(1));
assert_eq!(checkpoint.completed_paladins.len(), 2);
assert!(checkpoint.failed_paladins.is_empty());
}
#[test]
fn test_checkpoint_mark_failed() {
let mut checkpoint = CheckpointData::new();
let id = Uuid::new_v4();
checkpoint.mark_failed(id);
assert_eq!(checkpoint.failed_paladins.len(), 1);
assert_eq!(checkpoint.failed_paladins[0], id);
assert!(checkpoint.completed_paladins.is_empty());
}
#[test]
fn test_paladin_state_update() {
let paladin = create_test_paladin();
let mut state = PaladinState::new(paladin, vec![], vec![]);
let original_updated = state.updated_at;
std::thread::sleep(std::time::Duration::from_millis(10));
let new_history = vec![ExecutionRecord {
timestamp: Utc::now(),
input: "new input".to_string(),
output: "new output".to_string(),
status: ExecutionStatus::Success,
loops_used: 2,
}];
state.update(vec![], new_history);
assert!(state.updated_at > original_updated);
assert_eq!(state.execution_history.len(), 1);
}
#[test]
fn test_execution_status_variants() {
let success = ExecutionStatus::Success;
let failed = ExecutionStatus::Failed("error".to_string());
let timeout = ExecutionStatus::Timeout;
let stop = ExecutionStatus::StopWordDetected;
assert!(serde_json::to_string(&success).is_ok());
assert!(serde_json::to_string(&failed).is_ok());
assert!(serde_json::to_string(&timeout).is_ok());
assert!(serde_json::to_string(&stop).is_ok());
}
#[test]
fn test_state_summary_serialization() {
let summary = StateSummary {
id: Uuid::new_v4(),
state_type: StateType::Paladin,
created_at: Utc::now(),
updated_at: Utc::now(),
file_path: PathBuf::from("/test/path.json"),
};
let json = serde_json::to_string(&summary).expect("Failed to serialize StateSummary");
let deserialized: StateSummary =
serde_json::from_str(&json).expect("Failed to deserialize StateSummary");
assert_eq!(deserialized.id, summary.id);
assert_eq!(deserialized.state_type, summary.state_type);
assert_eq!(deserialized.file_path, summary.file_path);
}
#[test]
fn test_state_type_serialization() {
let paladin_type = StateType::Paladin;
let battalion_type = StateType::Battalion;
let paladin_json = serde_json::to_string(&paladin_type).expect("Failed to serialize");
let battalion_json = serde_json::to_string(&battalion_type).expect("Failed to serialize");
let deser_paladin: StateType =
serde_json::from_str(&paladin_json).expect("Failed to deserialize");
let deser_battalion: StateType =
serde_json::from_str(&battalion_json).expect("Failed to deserialize");
assert_eq!(deser_paladin, StateType::Paladin);
assert_eq!(deser_battalion, StateType::Battalion);
}
#[test]
fn test_all_domain_types_are_serializable() {
let paladin = create_test_paladin();
let garrison_entry = GarrisonEntry::new(
crate::platform::container::garrison::ConversationRole::User,
"test".to_string(),
);
let exec_record = ExecutionRecord {
timestamp: Utc::now(),
input: "test".to_string(),
output: "result".to_string(),
status: ExecutionStatus::Success,
loops_used: 1,
};
let checkpoint = CheckpointData::new();
let config = BattalionConfig::default();
assert!(serde_json::to_string(&paladin).is_ok());
assert!(serde_json::to_string(&garrison_entry).is_ok());
assert!(serde_json::to_string(&exec_record).is_ok());
assert!(serde_json::to_string(&checkpoint).is_ok());
assert!(serde_json::to_string(&config).is_ok());
}
}