use std::time::Duration; use serde::{Deserialize, Serialize}; use serde_json::Value; use tokio_util::sync::CancellationToken; use super::persistence::AgentRunContext; use crate::error::{AiError, AiResult}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AgentRequest { pub input: String, pub messages: Vec, pub context: Vec, pub experts: Vec, pub run_context: Option, #[serde(skip)] pub prefill_messages: Vec, #[serde(skip)] pub cancellation_token: Option, #[serde(skip)] pub timeout: Option, } impl AgentRequest { pub fn new(input: impl Into) -> Self { Self { input: input.into(), messages: Vec::new(), context: Vec::new(), experts: Vec::new(), run_context: None, prefill_messages: Vec::new(), cancellation_token: None, timeout: None, } } pub fn validate(&self) -> AiResult<()> { if self.input.trim().is_empty() { return Err(AiError::Config("agent request input is required".to_string())); } if self.input.len() > 1_000_000 { return Err(AiError::Config( "agent request input exceeds maximum length (1MB)".to_string(), )); } if self.experts.len() > 32 { return Err(AiError::Config( "agent request experts count exceeds maximum (32)".to_string(), )); } Ok(()) } pub fn with_messages(mut self, messages: Vec) -> Self { self.messages = messages; self } pub fn with_context(mut self, context: Vec) -> Self { self.context = context; self } pub fn add_context(mut self, chunk: AgentContextChunk) -> Self { self.context.push(chunk); self } pub fn with_experts(mut self, experts: Vec) -> Self { self.experts = experts; self } pub fn add_expert(mut self, expert: AgentExpert) -> Self { self.experts.push(expert); self } pub fn with_run_context(mut self, run_context: AgentRunContext) -> Self { self.run_context = Some(run_context); self } pub fn with_prefill_messages(mut self, prefill_messages: Vec) -> Self { self.prefill_messages = prefill_messages; self } pub fn with_cancellation_token(mut self, cancellation_token: CancellationToken) -> Self { self.cancellation_token = Some(cancellation_token); self } pub fn with_timeout(mut self, timeout: Duration) -> Self { self.timeout = Some(timeout); self } } #[derive(Clone, Debug, Serialize, Deserialize)] pub enum AgentMessage { User(String), Assistant(String), } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AgentExpert { pub id: String, pub role: String, pub task: String, pub system_prompt: Option, pub context: Vec, /// Override the master agent's temperature for this subagent. pub temperature: Option, /// Override the master agent's max_completion_tokens for this subagent. pub max_completion_tokens: Option, } impl AgentExpert { pub fn new(id: impl Into, role: impl Into, task: impl Into) -> Self { Self { id: id.into(), role: role.into(), task: task.into(), system_prompt: None, context: Vec::new(), temperature: None, max_completion_tokens: None, } } pub fn with_system_prompt(mut self, system_prompt: impl Into) -> Self { self.system_prompt = Some(system_prompt.into()); self } pub fn with_context(mut self, context: Vec) -> Self { self.context = context; self } pub fn with_temperature(mut self, temperature: f64) -> Self { self.temperature = Some(temperature); self } pub fn with_max_completion_tokens(mut self, max_tokens: u64) -> Self { self.max_completion_tokens = Some(max_tokens); self } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AgentContextChunk { pub id: String, pub content: String, pub source: Option, pub score: Option, pub metadata: Value, } impl AgentContextChunk { pub fn new(id: impl Into, content: impl Into) -> Self { Self { id: id.into(), content: content.into(), source: None, score: None, metadata: Value::Null, } } } impl From for AgentContextChunk { fn from(hit: crate::rag::RagSearchHit) -> Self { Self { id: hit.id, content: hit.content, source: Some(hit.session_id), score: Some(hit.score), metadata: Value::Object(hit.metadata.into_iter().collect()), } } } impl From<&AgentExpertOutput> for AgentContextChunk { fn from(output: &AgentExpertOutput) -> Self { Self { id: format!("subagent:{}", output.id), content: output.output.clone(), source: Some(output.role.clone()), score: None, metadata: serde_json::json!({ "kind": "subagent", "task": output.task, }), } } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AgentResult { pub output: String, pub steps: Vec, pub expert_outputs: Vec, pub input_tokens: i64, pub output_tokens: i64, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AgentStep { pub index: usize, pub assistant: Option, pub reasoning_content: Option, pub tool_calls: Vec, pub reflection: Option, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ToolCallRecord { pub id: String, pub name: String, pub arguments: Value, pub output: Option, pub error: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub elapsed_ms: Option, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AgentExpertOutput { pub id: String, pub role: String, pub task: String, pub output: String, pub input_tokens: i64, pub output_tokens: i64, }