use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::types::{AgentId, AgentState, Message, SessionId, ToolCall, ToolResult};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionContext {
pub session_id: SessionId,
pub agent_id: AgentId,
pub state: AgentState,
pub iteration: i32,
pub max_iterations: i32,
pub messages: Vec<Message>,
pub tools_registered: Vec<String>,
pub tool_calls_pending: Vec<ToolCall>,
pub tool_results_cache: Vec<ToolResult>,
pub model: String,
pub temperature: f32,
pub system_prompt: String,
pub tokens_total: i64,
pub tokens_prompt: i64,
pub tokens_completion: i64,
pub cost_estimate: f64,
pub created_at: DateTime<Utc>,
pub last_updated: DateTime<Utc>,
pub checkpoint_count: i32,
#[serde(default)]
pub metadata: HashMap<String, serde_json::Value>,
}
impl ExecutionContext {
pub fn new() -> Self {
let now = Utc::now();
Self {
session_id: SessionId::new(),
agent_id: AgentId::new(),
state: AgentState::Idle,
iteration: 0,
max_iterations: 100,
messages: Vec::new(),
tools_registered: Vec::new(),
tool_calls_pending: Vec::new(),
tool_results_cache: Vec::new(),
model: "claude-sonnet-4-6".to_string(),
temperature: 0.7,
system_prompt: String::new(),
tokens_total: 0,
tokens_prompt: 0,
tokens_completion: 0,
cost_estimate: 0.0,
created_at: now,
last_updated: now,
checkpoint_count: 0,
metadata: HashMap::new(),
}
}
pub fn with_config(
model: impl Into<String>,
temperature: f32,
system_prompt: impl Into<String>,
) -> Self {
let mut ctx = Self::new();
ctx.model = model.into();
ctx.temperature = temperature;
ctx.system_prompt = system_prompt.into();
ctx
}
pub fn add_message(&mut self, role: &str, content: &str) {
use crate::types::MessageRole;
let role = match role {
"user" => MessageRole::User,
"assistant" => MessageRole::Assistant,
"system" => MessageRole::System,
"tool" => MessageRole::Tool,
_ => MessageRole::User,
};
self.messages.push(Message::new(role, content));
self.iteration += 1;
self.touch();
}
pub fn touch(&mut self) {
self.last_updated = Utc::now();
}
pub fn increment_checkpoint(&mut self) {
self.checkpoint_count += 1;
self.touch();
}
pub fn add_tokens(&mut self, prompt: i64, completion: i64) {
self.tokens_prompt += prompt;
self.tokens_completion += completion;
self.tokens_total += prompt + completion;
self.touch();
}
pub fn to_dict(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or(serde_json::Value::Null)
}
pub fn from_dict(data: &serde_json::Value) -> serde_json::Result<Self> {
serde_json::from_value(data.clone())
}
pub fn to_json(&self) -> serde_json::Result<String> {
serde_json::to_string_pretty(self)
}
pub fn from_json(json: &str) -> serde_json::Result<Self> {
serde_json::from_str(json)
}
pub fn set_state(&mut self, state: AgentState) {
self.state = state;
self.touch();
}
pub fn can_continue(&self) -> bool {
self.iteration < self.max_iterations
&& matches!(
self.state,
AgentState::Running | AgentState::Idle | AgentState::WaitingTool
)
}
pub fn message_count(&self) -> usize {
self.messages.len()
}
}
impl Default for ExecutionContext {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_execution_context_creation() {
let ctx = ExecutionContext::new();
assert_eq!(ctx.state, AgentState::Idle);
assert_eq!(ctx.iteration, 0);
assert!(ctx.messages.is_empty());
}
#[test]
fn test_add_message() {
let mut ctx = ExecutionContext::new();
ctx.add_message("user", "Hello");
assert_eq!(ctx.messages.len(), 1);
assert_eq!(ctx.iteration, 1);
}
#[test]
fn test_add_tokens() {
let mut ctx = ExecutionContext::new();
ctx.add_tokens(100, 50);
assert_eq!(ctx.tokens_prompt, 100);
assert_eq!(ctx.tokens_completion, 50);
assert_eq!(ctx.tokens_total, 150);
}
#[test]
fn test_serialization() {
let ctx = ExecutionContext::new();
let json = ctx.to_json().unwrap();
let restored = ExecutionContext::from_json(&json).unwrap();
assert_eq!(ctx.session_id, restored.session_id);
}
}