use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
use crate::{AgentContext, AgentId, TokenUsageRecord};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandRecord {
pub command_id: Uuid,
pub agent_id: AgentId,
pub timestamp: DateTime<Utc>,
pub duration_ms: u64,
pub input: CommandInput,
pub context_snapshot: HistoryContextSnapshot,
pub execution: CommandExecution,
pub output: CommandOutput,
pub token_usage: Option<TokenUsageRecord>,
pub cost_usd: Option<f64>,
pub quality_score: Option<f64>,
pub lessons_learned: Vec<String>,
pub memory_updates: Vec<String>,
pub error: Option<CommandError>,
}
impl CommandRecord {
pub fn new(agent_id: AgentId, input: CommandInput) -> Self {
Self {
command_id: Uuid::new_v4(),
agent_id,
timestamp: Utc::now(),
duration_ms: 0,
input,
context_snapshot: HistoryContextSnapshot::empty(agent_id),
execution: CommandExecution::default(),
output: CommandOutput::default(),
token_usage: None,
cost_usd: None,
quality_score: None,
lessons_learned: Vec::new(),
memory_updates: Vec::new(),
error: None,
}
}
pub fn complete(mut self, output: CommandOutput, duration_ms: u64) -> Self {
self.output = output;
self.duration_ms = duration_ms;
self
}
pub fn with_token_usage(mut self, token_usage: TokenUsageRecord, cost_usd: f64) -> Self {
self.token_usage = Some(token_usage);
self.cost_usd = Some(cost_usd);
self
}
pub fn with_quality_score(mut self, score: f64) -> Self {
self.quality_score = Some(score.clamp(0.0, 1.0));
self
}
pub fn with_context_snapshot(mut self, snapshot: HistoryContextSnapshot) -> Self {
self.context_snapshot = snapshot;
self
}
pub fn with_execution(mut self, execution: CommandExecution) -> Self {
self.execution = execution;
self
}
pub fn with_lessons(mut self, lessons: Vec<String>) -> Self {
self.lessons_learned = lessons;
self
}
pub fn with_memory_updates(mut self, updates: Vec<String>) -> Self {
self.memory_updates = updates;
self
}
pub fn with_error(mut self, error: CommandError) -> Self {
self.error = Some(error);
self
}
pub fn is_successful(&self) -> bool {
self.error.is_none()
}
pub fn effective_quality_score(&self) -> f64 {
if self.is_successful() {
self.quality_score.unwrap_or(0.5)
} else {
0.0
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandInput {
pub text: String,
pub command_type: CommandType,
pub parameters: HashMap<String, serde_json::Value>,
pub source: CommandSource,
pub priority: CommandPriority,
pub timeout_ms: Option<u64>,
}
impl CommandInput {
pub fn new(text: String, command_type: CommandType) -> Self {
Self {
text,
command_type,
parameters: HashMap::new(),
source: CommandSource::User,
priority: CommandPriority::Normal,
timeout_ms: None,
}
}
pub fn with_parameters(mut self, parameters: HashMap<String, serde_json::Value>) -> Self {
self.parameters = parameters;
self
}
pub fn with_source(mut self, source: CommandSource) -> Self {
self.source = source;
self
}
pub fn with_priority(mut self, priority: CommandPriority) -> Self {
self.priority = priority;
self
}
pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
self.timeout_ms = Some(timeout_ms);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum CommandType {
Generate,
Answer,
Execute,
Search,
Analyze,
Create,
Edit,
Review,
Plan,
System,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum CommandSource {
User,
System,
Agent(AgentId),
Automated,
Api,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum CommandPriority {
Low,
Normal,
High,
Critical,
Emergency,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CommandExecution {
pub workflow_pattern: Option<String>,
pub steps: Vec<ExecutionStep>,
pub tools_used: Vec<String>,
pub intermediate_results: Vec<String>,
pub retry_count: u32,
pub used_cache: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionStep {
pub step_id: Uuid,
pub name: String,
pub started_at: DateTime<Utc>,
pub duration_ms: u64,
pub status: StepStatus,
pub input: Option<String>,
pub output: Option<String>,
pub error: Option<String>,
}
impl ExecutionStep {
pub fn new(name: String) -> Self {
Self {
step_id: Uuid::new_v4(),
name,
started_at: Utc::now(),
duration_ms: 0,
status: StepStatus::Running,
input: None,
output: None,
error: None,
}
}
pub fn complete(mut self, output: String, duration_ms: u64) -> Self {
self.output = Some(output);
self.duration_ms = duration_ms;
self.status = StepStatus::Completed;
self
}
pub fn fail(mut self, error: String, duration_ms: u64) -> Self {
self.error = Some(error);
self.duration_ms = duration_ms;
self.status = StepStatus::Failed;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum StepStatus {
Pending,
Running,
Completed,
Failed,
Skipped,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CommandOutput {
pub text: String,
pub data: Option<serde_json::Value>,
pub output_type: OutputType,
pub confidence: Option<f64>,
pub sources: Vec<String>,
pub metadata: HashMap<String, serde_json::Value>,
}
impl CommandOutput {
pub fn new(text: String) -> Self {
Self {
text,
data: None,
output_type: OutputType::Text,
confidence: None,
sources: Vec::new(),
metadata: HashMap::new(),
}
}
pub fn with_data(mut self, data: serde_json::Value) -> Self {
self.data = Some(data);
self.output_type = OutputType::Structured;
self
}
pub fn with_confidence(mut self, confidence: f64) -> Self {
self.confidence = Some(confidence.clamp(0.0, 1.0));
self
}
pub fn with_sources(mut self, sources: Vec<String>) -> Self {
self.sources = sources;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub enum OutputType {
#[default]
Text,
Structured,
Binary,
Stream,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandError {
pub error_type: ErrorType,
pub message: String,
pub code: Option<String>,
pub details: Option<String>,
pub recoverable: bool,
pub suggestions: Vec<String>,
}
impl CommandError {
pub fn new(error_type: ErrorType, message: String) -> Self {
Self {
error_type,
message,
code: None,
details: None,
recoverable: false,
suggestions: Vec::new(),
}
}
pub fn with_code(mut self, code: String) -> Self {
self.code = Some(code);
self
}
pub fn with_details(mut self, details: String) -> Self {
self.details = Some(details);
self
}
pub fn recoverable(mut self) -> Self {
self.recoverable = true;
self
}
pub fn with_suggestions(mut self, suggestions: Vec<String>) -> Self {
self.suggestions = suggestions;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ErrorType {
Validation,
Auth,
RateLimit,
NotFound,
ServiceUnavailable,
Timeout,
Network,
LlmProvider,
Configuration,
Internal,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryContextSnapshot {
pub agent_id: AgentId,
pub timestamp: DateTime<Utc>,
pub item_count: usize,
pub token_count: u64,
pub key_items: Vec<ContextSummary>,
}
impl HistoryContextSnapshot {
pub fn empty(agent_id: AgentId) -> Self {
Self {
agent_id,
timestamp: Utc::now(),
item_count: 0,
token_count: 0,
key_items: Vec::new(),
}
}
pub fn from_context(context: &AgentContext) -> Self {
let key_items = context
.get_relevant_items(1000) .into_iter()
.map(|item| ContextSummary {
item_type: format!("{:?}", item.item_type),
content_preview: item.content.chars().take(100).collect(),
token_count: item.token_count,
relevance_score: item.relevance_score,
})
.collect();
Self {
agent_id: context.agent_id,
timestamp: Utc::now(),
item_count: context.items.len(),
token_count: context.current_tokens,
key_items,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextSummary {
pub item_type: String,
pub content_preview: String,
pub token_count: u64,
pub relevance_score: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandHistory {
pub agent_id: AgentId,
pub records: Vec<CommandRecord>,
pub max_records: usize,
pub last_updated: DateTime<Utc>,
}
impl CommandHistory {
pub fn new(agent_id: AgentId, max_records: usize) -> Self {
Self {
agent_id,
records: Vec::new(),
max_records,
last_updated: Utc::now(),
}
}
pub fn add_record(&mut self, record: CommandRecord) {
self.records.push(record);
self.last_updated = Utc::now();
if self.records.len() > self.max_records {
self.records.remove(0);
}
}
pub fn get_records_in_range(
&self,
start: DateTime<Utc>,
end: DateTime<Utc>,
) -> Vec<&CommandRecord> {
self.records
.iter()
.filter(|record| record.timestamp >= start && record.timestamp <= end)
.collect()
}
pub fn get_records_by_type(&self, command_type: CommandType) -> Vec<&CommandRecord> {
self.records
.iter()
.filter(|record| record.input.command_type == command_type)
.collect()
}
pub fn get_successful_records(&self) -> Vec<&CommandRecord> {
self.records
.iter()
.filter(|record| record.is_successful())
.collect()
}
pub fn get_failed_records(&self) -> Vec<&CommandRecord> {
self.records
.iter()
.filter(|record| !record.is_successful())
.collect()
}
pub fn success_rate(&self) -> f64 {
if self.records.is_empty() {
return 0.0;
}
let successful_count = self.get_successful_records().len();
successful_count as f64 / self.records.len() as f64
}
pub fn average_quality_score(&self) -> f64 {
let quality_scores: Vec<f64> = self
.records
.iter()
.filter_map(|record| record.quality_score)
.collect();
if quality_scores.is_empty() {
return 0.0;
}
quality_scores.iter().sum::<f64>() / quality_scores.len() as f64
}
pub fn get_recent_records(&self, limit: usize) -> Vec<&CommandRecord> {
self.records.iter().rev().take(limit).collect()
}
pub fn get_statistics(&self) -> CommandStatistics {
let total_commands = self.records.len();
let successful_commands = self.get_successful_records().len();
let failed_commands = self.get_failed_records().len();
let total_duration_ms: u64 = self.records.iter().map(|r| r.duration_ms).sum();
let total_cost: f64 = self.records.iter().filter_map(|r| r.cost_usd).sum();
let total_tokens: u64 = self
.records
.iter()
.filter_map(|r| r.token_usage.as_ref().map(|t| t.total_tokens))
.sum();
CommandStatistics {
total_commands: total_commands as u64,
successful_commands: successful_commands as u64,
failed_commands: failed_commands as u64,
success_rate: self.success_rate(),
average_quality_score: self.average_quality_score(),
total_duration_ms,
average_duration_ms: if total_commands > 0 {
total_duration_ms / total_commands as u64
} else {
0
},
total_cost_usd: total_cost,
average_cost_per_command: if total_commands > 0 {
total_cost / total_commands as f64
} else {
0.0
},
total_tokens,
average_tokens_per_command: if total_commands > 0 {
total_tokens as f64 / total_commands as f64
} else {
0.0
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandStatistics {
pub total_commands: u64,
pub successful_commands: u64,
pub failed_commands: u64,
pub success_rate: f64,
pub average_quality_score: f64,
pub total_duration_ms: u64,
pub average_duration_ms: u64,
pub total_cost_usd: f64,
pub average_cost_per_command: f64,
pub total_tokens: u64,
pub average_tokens_per_command: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_command_record_creation() {
let agent_id = AgentId::new_v4();
let input = CommandInput::new("Test command".to_string(), CommandType::Generate);
let record = CommandRecord::new(agent_id, input);
assert_eq!(record.agent_id, agent_id);
assert_eq!(record.input.text, "Test command");
assert_eq!(record.input.command_type, CommandType::Generate);
assert!(record.is_successful());
}
#[test]
fn test_command_history() {
let agent_id = AgentId::new_v4();
let mut history = CommandHistory::new(agent_id, 100);
let input = CommandInput::new("Test".to_string(), CommandType::Answer);
let record = CommandRecord::new(agent_id, input);
history.add_record(record);
assert_eq!(history.records.len(), 1);
assert_eq!(history.success_rate(), 1.0);
}
#[test]
fn test_execution_step() {
let step =
ExecutionStep::new("Test step".to_string()).complete("Success".to_string(), 1000);
assert_eq!(step.name, "Test step");
assert_eq!(step.status, StepStatus::Completed);
assert_eq!(step.output, Some("Success".to_string()));
assert_eq!(step.duration_ms, 1000);
}
#[test]
fn test_command_statistics() {
let agent_id = AgentId::new_v4();
let mut history = CommandHistory::new(agent_id, 100);
let input1 = CommandInput::new("Success".to_string(), CommandType::Generate);
let record1 = CommandRecord::new(agent_id, input1)
.complete(CommandOutput::new("Done".to_string()), 1000)
.with_quality_score(0.8);
history.add_record(record1);
let input2 = CommandInput::new("Fail".to_string(), CommandType::Generate);
let record2 = CommandRecord::new(agent_id, input2)
.with_error(CommandError::new(ErrorType::Internal, "Failed".to_string()));
history.add_record(record2);
let stats = history.get_statistics();
assert_eq!(stats.total_commands, 2);
assert_eq!(stats.successful_commands, 1);
assert_eq!(stats.failed_commands, 1);
assert_eq!(stats.success_rate, 0.5);
}
}