coalescent 0.1.0

High-level AI coordination patterns enabling intelligent agent coalescence
Documentation
//! AI Agent definitions and capabilities

use crate::{CoalescentError, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;

/// Unique identifier for an AI agent
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct AgentId(Uuid);

impl AgentId {
    /// Create a new random agent ID
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }
    
    /// Create an agent ID from a string
    pub fn from_string(s: &str) -> Result<Self> {
        let uuid = Uuid::parse_str(s)
            .map_err(|_| CoalescentError::agent("Invalid agent ID format"))?;
        Ok(Self(uuid))
    }
    
    /// Get the UUID value
    pub fn as_uuid(&self) -> &Uuid {
        &self.0
    }
}

impl Default for AgentId {
    fn default() -> Self {
        Self::new()
    }
}

impl std::fmt::Display for AgentId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Capability that an AI agent can provide
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentCapability {
    /// Name of the capability (e.g., "code_analysis", "text_generation")
    pub name: String,
    
    /// Human-readable description
    pub description: String,
    
    /// Estimated performance score (0.0 to 1.0)
    pub performance_score: f64,
    
    /// Resource requirements or constraints
    pub requirements: HashMap<String, String>,
}

impl AgentCapability {
    /// Create a new capability
    pub fn new<S: Into<String>>(name: S, description: S, performance_score: f64) -> Result<Self> {
        if !(0.0..=1.0).contains(&performance_score) {
            return Err(CoalescentError::agent("Performance score must be between 0.0 and 1.0"));
        }
        
        Ok(Self {
            name: name.into(),
            description: description.into(),
            performance_score,
            requirements: HashMap::new(),
        })
    }
    
    /// Add a requirement to this capability
    pub fn with_requirement<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
        self.requirements.insert(key.into(), value.into());
        self
    }
}

/// AI Agent representation in the coordination system
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Agent {
    /// Unique identifier for this agent
    pub id: AgentId,
    
    /// Human-readable name
    pub name: String,
    
    /// Agent type/model (e.g., "gpt-4", "claude-3", "custom-model")
    pub agent_type: String,
    
    /// Current status of the agent
    pub status: AgentStatus,
    
    /// Capabilities this agent can provide
    pub capabilities: Vec<AgentCapability>,
    
    /// Metadata and configuration
    pub metadata: HashMap<String, String>,
}

/// Current status of an AI agent
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum AgentStatus {
    /// Agent is available for coordination
    Available,
    
    /// Agent is busy with a task
    Busy { task_id: Option<String> },
    
    /// Agent is offline or unavailable
    Offline,
    
    /// Agent has encountered an error
    Error { message: String },
}

impl Agent {
    /// Create a new AI agent
    pub fn new<S: Into<String>>(agent_type: S, name: S) -> Result<Self> {
        Ok(Self {
            id: AgentId::new(),
            name: name.into(),
            agent_type: agent_type.into(),
            status: AgentStatus::Available,
            capabilities: Vec::new(),
            metadata: HashMap::new(),
        })
    }
    
    /// Create a new AI agent with capabilities
    pub fn with_capabilities<S: Into<String>>(
        agent_type: S, 
        name: S, 
        capability_names: Vec<&str>
    ) -> Result<Self> {
        let mut agent = Self::new(agent_type, name)?;
        
        for cap_name in capability_names {
            let capability = AgentCapability::new(
                cap_name,
                cap_name, // Use the same string for description
                0.8 // Default performance score
            )?;
            agent.add_capability(capability);
        }
        
        Ok(agent)
    }
    
    /// Add a capability to this agent
    pub fn add_capability(&mut self, capability: AgentCapability) {
        self.capabilities.push(capability);
    }
    
    /// Check if agent has a specific capability
    pub fn has_capability(&self, capability_name: &str) -> bool {
        self.capabilities.iter().any(|cap| cap.name == capability_name)
    }
    
    /// Get capability by name
    pub fn get_capability(&self, capability_name: &str) -> Option<&AgentCapability> {
        self.capabilities.iter().find(|cap| cap.name == capability_name)
    }
    
    /// Set agent status
    pub fn set_status(&mut self, status: AgentStatus) {
        self.status = status;
    }
    
    /// Check if agent is available for new tasks
    pub fn is_available(&self) -> bool {
        matches!(self.status, AgentStatus::Available)
    }
    
    /// Add metadata to the agent
    pub fn add_metadata<K: Into<String>, V: Into<String>>(&mut self, key: K, value: V) {
        self.metadata.insert(key.into(), value.into());
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_agent_creation() -> Result<()> {
        let agent = Agent::new("gpt-4", "Test Agent")?;
        assert_eq!(agent.agent_type, "gpt-4");
        assert_eq!(agent.name, "Test Agent");
        assert!(agent.is_available());
        Ok(())
    }
    
    #[test]
    fn test_capability_management() -> Result<()> {
        let mut agent = Agent::new("test", "Test Agent")?;
        let capability = AgentCapability::new("text_generation", "Generate text", 0.9)?;
        
        agent.add_capability(capability);
        assert!(agent.has_capability("text_generation"));
        assert!(!agent.has_capability("image_generation"));
        
        Ok(())
    }
    
    #[test]
    fn test_agent_id_operations() -> Result<()> {
        let id1 = AgentId::new();
        let id2 = AgentId::new();
        
        // IDs should be unique
        assert_ne!(id1, id2);
        
        // String round-trip should work
        let id_str = id1.to_string();
        let id3 = AgentId::from_string(&id_str)?;
        assert_eq!(id1, id3);
        
        Ok(())
    }
}