Skip to main content

llm_agent_runtime/
types.rs

1//! Core identifier types, available without feature gates.
2//!
3//! [`AgentId`] and [`MemoryId`] are defined here so they can be used by the
4//! agent runtime regardless of whether the `memory` feature is enabled.
5
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9/// Stable identifier for an agent instance.
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub struct AgentId(pub String);
12
13impl AgentId {
14    /// Create a new `AgentId` from any string-like value.
15    ///
16    /// # Panics (debug only)
17    ///
18    /// Triggers a `debug_assert!` if `id` is empty.  In release builds a
19    /// `tracing::warn!` is emitted instead so that the misconfiguration is
20    /// surfaced in production logs without aborting the process.
21    pub fn new(id: impl Into<String>) -> Self {
22        let id = id.into();
23        if id.is_empty() {
24            debug_assert!(false, "AgentId must not be empty");
25            tracing::warn!("AgentId::new called with an empty string — agent IDs should be non-empty to avoid lookup ambiguity");
26        }
27        Self(id)
28    }
29
30    /// Create a validated `AgentId`, returning an error if `id` is empty.
31    ///
32    /// Prefer this constructor in user-facing code where empty IDs must be
33    /// rejected explicitly rather than silently warned about.
34    pub fn try_new(id: impl Into<String>) -> Result<Self, crate::error::AgentRuntimeError> {
35        let id = id.into();
36        if id.is_empty() {
37            return Err(crate::error::AgentRuntimeError::Memory(
38                "AgentId must not be empty".into(),
39            ));
40        }
41        Ok(Self(id))
42    }
43
44    /// Generate a random `AgentId` backed by a UUID v4.
45    pub fn random() -> Self {
46        Self(Uuid::new_v4().to_string())
47    }
48
49    /// Return the inner ID string as a `&str`.
50    pub fn as_str(&self) -> &str {
51        &self.0
52    }
53
54    /// Return the byte length of the inner ID string.
55    pub fn len(&self) -> usize {
56        self.0.len()
57    }
58
59    /// Return `true` if the inner ID string is empty.
60    ///
61    /// Note: `AgentId::new` warns (debug: asserts) against empty IDs.
62    /// This predicate is provided for defensive checks.
63    pub fn is_empty(&self) -> bool {
64        self.0.is_empty()
65    }
66
67    /// Return `true` if the inner ID string starts with `prefix`.
68    pub fn starts_with(&self, prefix: &str) -> bool {
69        self.0.starts_with(prefix)
70    }
71}
72
73impl AsRef<str> for AgentId {
74    fn as_ref(&self) -> &str {
75        &self.0
76    }
77}
78
79impl std::fmt::Display for AgentId {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        write!(f, "{}", self.0)
82    }
83}
84
85/// Stable identifier for a memory item.
86#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
87pub struct MemoryId(pub String);
88
89impl MemoryId {
90    /// Create a new `MemoryId` from any string-like value.
91    ///
92    /// # Panics (debug only)
93    ///
94    /// Triggers a `debug_assert!` if `id` is empty.  In release builds a
95    /// `tracing::warn!` is emitted instead so that the misconfiguration is
96    /// surfaced in production logs without aborting the process.
97    pub fn new(id: impl Into<String>) -> Self {
98        let id = id.into();
99        if id.is_empty() {
100            debug_assert!(false, "MemoryId must not be empty");
101            tracing::warn!("MemoryId::new called with an empty string — memory IDs should be non-empty to avoid lookup ambiguity");
102        }
103        Self(id)
104    }
105
106    /// Create a validated `MemoryId`, returning an error if `id` is empty.
107    pub fn try_new(id: impl Into<String>) -> Result<Self, crate::error::AgentRuntimeError> {
108        let id = id.into();
109        if id.is_empty() {
110            return Err(crate::error::AgentRuntimeError::Memory(
111                "MemoryId must not be empty".into(),
112            ));
113        }
114        Ok(Self(id))
115    }
116
117    /// Generate a random `MemoryId` backed by a UUID v4.
118    pub fn random() -> Self {
119        Self(Uuid::new_v4().to_string())
120    }
121
122    /// Return the inner ID string as a `&str`.
123    pub fn as_str(&self) -> &str {
124        &self.0
125    }
126
127    /// Return the byte length of the inner ID string.
128    pub fn len(&self) -> usize {
129        self.0.len()
130    }
131
132    /// Return `true` if the inner ID string is empty.
133    pub fn is_empty(&self) -> bool {
134        self.0.is_empty()
135    }
136
137    /// Return `true` if the inner ID string starts with `prefix`.
138    pub fn starts_with(&self, prefix: &str) -> bool {
139        self.0.starts_with(prefix)
140    }
141}
142
143impl AsRef<str> for MemoryId {
144    fn as_ref(&self) -> &str {
145        &self.0
146    }
147}
148
149impl std::fmt::Display for MemoryId {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        write!(f, "{}", self.0)
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn test_agent_id_new_stores_value() {
161        let id = AgentId::new("agent-1");
162        assert_eq!(id.as_str(), "agent-1");
163    }
164
165    #[test]
166    fn test_agent_id_try_new_rejects_empty() {
167        assert!(AgentId::try_new("").is_err());
168    }
169
170    #[test]
171    fn test_agent_id_try_new_accepts_nonempty() {
172        let id = AgentId::try_new("ok").unwrap();
173        assert_eq!(id.as_str(), "ok");
174    }
175
176    #[test]
177    fn test_agent_id_random_generates_unique_ids() {
178        let a = AgentId::random();
179        let b = AgentId::random();
180        assert_ne!(a, b);
181    }
182
183    #[test]
184    fn test_agent_id_len_and_is_empty() {
185        let id = AgentId::new("abc");
186        assert_eq!(id.len(), 3);
187        assert!(!id.is_empty());
188    }
189
190    #[test]
191    fn test_agent_id_display() {
192        let id = AgentId::new("my-agent");
193        assert_eq!(id.to_string(), "my-agent");
194    }
195
196    #[test]
197    fn test_memory_id_new_stores_value() {
198        let id = MemoryId::new("mem-42");
199        assert_eq!(id.as_str(), "mem-42");
200    }
201
202    #[test]
203    fn test_memory_id_try_new_rejects_empty() {
204        assert!(MemoryId::try_new("").is_err());
205    }
206
207    #[test]
208    fn test_memory_id_len_and_is_empty() {
209        let id = MemoryId::new("hello");
210        assert_eq!(id.len(), 5);
211        assert!(!id.is_empty());
212    }
213
214    #[test]
215    fn test_memory_id_display() {
216        let id = MemoryId::new("mem-id");
217        assert_eq!(id.to_string(), "mem-id");
218    }
219
220    #[test]
221    fn test_memory_id_random_generates_unique_ids() {
222        let a = MemoryId::random();
223        let b = MemoryId::random();
224        assert_ne!(a, b);
225    }
226
227    // ── Round 11: starts_with ─────────────────────────────────────────────────
228
229    #[test]
230    fn test_agent_id_starts_with_matching_prefix() {
231        let id = AgentId::new("agent-001");
232        assert!(id.starts_with("agent-"));
233        assert!(!id.starts_with("user-"));
234    }
235
236    #[test]
237    fn test_agent_id_starts_with_empty_prefix_always_true() {
238        let id = AgentId::new("anything");
239        assert!(id.starts_with(""));
240    }
241
242    #[test]
243    fn test_memory_id_starts_with_matching_prefix() {
244        let id = MemoryId::new("mem-42");
245        assert!(id.starts_with("mem-"));
246        assert!(!id.starts_with("ep-"));
247    }
248}