Skip to main content

agent_uri/
type_class.rs

1//! Type classification for agents.
2
3use std::fmt;
4use std::str::FromStr;
5
6/// Primary type classification for agents.
7///
8/// Represents what kind of agent this is at the most fundamental level.
9/// These are the core classes defined in the specification.
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub enum TypeClass {
12    /// Large language model based agent
13    Llm,
14    /// Deterministic rule/logic based agent
15    Rule,
16    /// Human-in-the-loop agent
17    Human,
18    /// Meta-agent that orchestrates other agents
19    Composite,
20    /// Agent that observes/monitors (read-only)
21    Sensor,
22    /// Agent that effects changes (write-only)
23    Actuator,
24    /// Mixed LLM + rule-based reasoning
25    Hybrid,
26    /// Extension class not in the core set
27    Extension(ExtensionClass),
28}
29
30/// An extension class name (custom type classes).
31#[derive(Debug, Clone, PartialEq, Eq, Hash)]
32pub struct ExtensionClass(String);
33
34impl ExtensionClass {
35    /// Creates a new extension class.
36    ///
37    /// # Errors
38    ///
39    /// Returns an error if the name is empty, less than 2 characters,
40    /// or contains non-lowercase letters.
41    pub fn new(name: &str) -> Result<Self, &'static str> {
42        if name.is_empty() {
43            return Err("extension class name cannot be empty");
44        }
45        if name.len() < 2 {
46            return Err("extension class name must be at least 2 characters");
47        }
48        if !name.chars().all(|c| c.is_ascii_lowercase()) {
49            return Err("extension class name must be all lowercase letters");
50        }
51        Ok(Self(name.to_string()))
52    }
53
54    /// Returns the class name as a string.
55    #[must_use]
56    pub fn as_str(&self) -> &str {
57        &self.0
58    }
59}
60
61impl fmt::Display for ExtensionClass {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "{}", self.0)
64    }
65}
66
67impl TypeClass {
68    /// Returns the string representation of this type class.
69    #[must_use]
70    pub fn as_str(&self) -> &str {
71        match self {
72            Self::Llm => "llm",
73            Self::Rule => "rule",
74            Self::Human => "human",
75            Self::Composite => "composite",
76            Self::Sensor => "sensor",
77            Self::Actuator => "actuator",
78            Self::Hybrid => "hybrid",
79            Self::Extension(ext) => ext.as_str(),
80        }
81    }
82
83    /// Returns true if this is a core type class (not an extension).
84    #[must_use]
85    pub const fn is_core(&self) -> bool {
86        !matches!(self, Self::Extension(_))
87    }
88}
89
90impl fmt::Display for TypeClass {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        write!(f, "{}", self.as_str())
93    }
94}
95
96impl FromStr for TypeClass {
97    type Err = &'static str;
98
99    fn from_str(s: &str) -> Result<Self, Self::Err> {
100        match s {
101            "llm" => Ok(Self::Llm),
102            "rule" => Ok(Self::Rule),
103            "human" => Ok(Self::Human),
104            "composite" => Ok(Self::Composite),
105            "sensor" => Ok(Self::Sensor),
106            "actuator" => Ok(Self::Actuator),
107            "hybrid" => Ok(Self::Hybrid),
108            other => {
109                let ext = ExtensionClass::new(other)?;
110                Ok(Self::Extension(ext))
111            }
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn parse_core_classes() {
122        assert_eq!("llm".parse::<TypeClass>().unwrap(), TypeClass::Llm);
123        assert_eq!("rule".parse::<TypeClass>().unwrap(), TypeClass::Rule);
124        assert_eq!("human".parse::<TypeClass>().unwrap(), TypeClass::Human);
125        assert_eq!("composite".parse::<TypeClass>().unwrap(), TypeClass::Composite);
126        assert_eq!("sensor".parse::<TypeClass>().unwrap(), TypeClass::Sensor);
127        assert_eq!("actuator".parse::<TypeClass>().unwrap(), TypeClass::Actuator);
128        assert_eq!("hybrid".parse::<TypeClass>().unwrap(), TypeClass::Hybrid);
129    }
130
131    #[test]
132    fn core_classes_are_core() {
133        assert!(TypeClass::Llm.is_core());
134        assert!(TypeClass::Rule.is_core());
135        assert!(TypeClass::Human.is_core());
136    }
137
138    #[test]
139    fn parse_extension_class() {
140        let tc = "custom".parse::<TypeClass>().unwrap();
141        assert!(matches!(tc, TypeClass::Extension(_)));
142        assert!(!tc.is_core());
143        assert_eq!(tc.as_str(), "custom");
144    }
145
146    #[test]
147    fn extension_class_validation() {
148        // Too short
149        assert!(ExtensionClass::new("a").is_err());
150        // Empty
151        assert!(ExtensionClass::new("").is_err());
152        // Contains uppercase
153        assert!(ExtensionClass::new("Custom").is_err());
154        // Contains digit
155        assert!(ExtensionClass::new("v2").is_err());
156        // Valid
157        assert!(ExtensionClass::new("custom").is_ok());
158    }
159}