Skip to main content

agents_kit/
identity.rs

1//! Opaque durable identities and descriptive agent inventory.
2//!
3//! Persist identities through their canonical string representation and parse
4//! them through `FromStr`. Parsing validates shape; it does not establish that
5//! the referenced resource exists. Identity creation is owned by the runtime.
6
7use std::fmt;
8use std::str::FromStr;
9
10use crate::KitError;
11
12pub use crate::agent::AgentInfo;
13
14#[derive(Clone, Copy)]
15enum IdentityKind {
16    Agent,
17    Workspace,
18    Conversation,
19    Notification,
20}
21
22impl IdentityKind {
23    fn prefix(self) -> &'static str {
24        match self {
25            Self::Agent => "agent_",
26            Self::Workspace => "workspace_",
27            Self::Conversation => "conversation_",
28            Self::Notification => "notification_",
29        }
30    }
31}
32
33#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
34struct Identity {
35    canonical: String,
36}
37
38impl Identity {
39    fn new(kind: IdentityKind) -> Self {
40        Self {
41            canonical: format!("{}{}", kind.prefix(), uuid::Uuid::new_v4().simple()),
42        }
43    }
44
45    fn parse(kind: IdentityKind, value: String) -> Result<Self, KitError> {
46        let payload = value
47            .strip_prefix(kind.prefix())
48            .ok_or_else(|| KitError::invalid_input("Kit identity category is invalid"))?;
49        if payload.len() != 32
50            || !payload
51                .bytes()
52                .all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f'))
53        {
54            return Err(KitError::invalid_input(
55                "Kit identity canonical form is invalid",
56            ));
57        }
58        let parsed = uuid::Uuid::parse_str(payload)
59            .map_err(|_| KitError::invalid_input("Kit identity canonical form is invalid"))?;
60        if parsed.simple().to_string() != payload {
61            return Err(KitError::invalid_input(
62                "Kit identity canonical form is invalid",
63            ));
64        }
65        Ok(Self { canonical: value })
66    }
67
68    fn as_str(&self) -> &str {
69        &self.canonical
70    }
71}
72
73/// Validated identity of an agent, parseable from its canonical string.
74///
75/// ```
76/// use agents_kit::identity::AgentId;
77///
78/// let value = "agent_0123456789abcdef0123456789abcdef";
79/// let id: AgentId = value.parse()?;
80/// assert_eq!(id.to_string(), value);
81/// # Ok::<(), agents_kit::KitError>(())
82/// ```
83#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
84pub struct AgentId(Identity);
85
86impl AgentId {
87    pub(crate) fn new() -> Self {
88        Self(Identity::new(IdentityKind::Agent))
89    }
90
91    /// Borrows the canonical agent identity string.
92    ///
93    /// ```
94    /// use agents_kit::identity::AgentId;
95    ///
96    /// let value = "agent_0123456789abcdef0123456789abcdef";
97    /// let id: AgentId = value.parse()?;
98    /// assert_eq!(id.as_str(), value);
99    /// # Ok::<(), agents_kit::KitError>(())
100    /// ```
101    pub fn as_str(&self) -> &str {
102        self.0.as_str()
103    }
104}
105
106impl fmt::Display for AgentId {
107    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
108        formatter.write_str(self.as_str())
109    }
110}
111
112impl FromStr for AgentId {
113    type Err = KitError;
114
115    fn from_str(value: &str) -> Result<Self, Self::Err> {
116        Identity::parse(IdentityKind::Agent, value.to_owned()).map(Self)
117    }
118}
119
120impl serde::Serialize for AgentId {
121    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
122    where
123        S: serde::Serializer,
124    {
125        serializer.serialize_str(self.as_str())
126    }
127}
128
129impl<'de> serde::Deserialize<'de> for AgentId {
130    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131    where
132        D: serde::Deserializer<'de>,
133    {
134        let value = <String as serde::Deserialize>::deserialize(deserializer)?;
135        Self::from_str(&value).map_err(serde::de::Error::custom)
136    }
137}
138
139/// Validated identity of a workspace, parseable from its canonical string.
140///
141/// ```
142/// use agents_kit::identity::WorkspaceId;
143///
144/// let value = "workspace_0123456789abcdef0123456789abcdef";
145/// let id: WorkspaceId = value.parse()?;
146/// assert_eq!(id.to_string(), value);
147/// # Ok::<(), agents_kit::KitError>(())
148/// ```
149#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
150pub struct WorkspaceId(Identity);
151
152impl WorkspaceId {
153    pub(crate) fn new() -> Self {
154        Self(Identity::new(IdentityKind::Workspace))
155    }
156
157    /// Borrows the canonical workspace identity string.
158    ///
159    /// ```
160    /// use agents_kit::identity::WorkspaceId;
161    ///
162    /// let value = "workspace_0123456789abcdef0123456789abcdef";
163    /// let id: WorkspaceId = value.parse()?;
164    /// assert_eq!(id.as_str(), value);
165    /// # Ok::<(), agents_kit::KitError>(())
166    /// ```
167    pub fn as_str(&self) -> &str {
168        self.0.as_str()
169    }
170}
171
172impl fmt::Display for WorkspaceId {
173    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
174        formatter.write_str(self.as_str())
175    }
176}
177
178impl FromStr for WorkspaceId {
179    type Err = KitError;
180
181    fn from_str(value: &str) -> Result<Self, Self::Err> {
182        Identity::parse(IdentityKind::Workspace, value.to_owned()).map(Self)
183    }
184}
185
186impl serde::Serialize for WorkspaceId {
187    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
188    where
189        S: serde::Serializer,
190    {
191        serializer.serialize_str(self.as_str())
192    }
193}
194
195impl<'de> serde::Deserialize<'de> for WorkspaceId {
196    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
197    where
198        D: serde::Deserializer<'de>,
199    {
200        let value = <String as serde::Deserialize>::deserialize(deserializer)?;
201        Self::from_str(&value).map_err(serde::de::Error::custom)
202    }
203}
204
205/// Validated identity of a conversation, parseable from its canonical string.
206///
207/// ```
208/// use agents_kit::identity::ConversationId;
209///
210/// let value = "conversation_0123456789abcdef0123456789abcdef";
211/// let id: ConversationId = value.parse()?;
212/// assert_eq!(id.to_string(), value);
213/// # Ok::<(), agents_kit::KitError>(())
214/// ```
215#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
216pub struct ConversationId(Identity);
217
218impl ConversationId {
219    pub(crate) fn new() -> Self {
220        Self(Identity::new(IdentityKind::Conversation))
221    }
222
223    /// Borrows the canonical conversation identity string.
224    ///
225    /// ```
226    /// use agents_kit::identity::ConversationId;
227    ///
228    /// let value = "conversation_0123456789abcdef0123456789abcdef";
229    /// let id: ConversationId = value.parse()?;
230    /// assert_eq!(id.as_str(), value);
231    /// # Ok::<(), agents_kit::KitError>(())
232    /// ```
233    pub fn as_str(&self) -> &str {
234        self.0.as_str()
235    }
236}
237
238impl fmt::Display for ConversationId {
239    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
240        formatter.write_str(self.as_str())
241    }
242}
243
244impl FromStr for ConversationId {
245    type Err = KitError;
246
247    fn from_str(value: &str) -> Result<Self, Self::Err> {
248        Identity::parse(IdentityKind::Conversation, value.to_owned()).map(Self)
249    }
250}
251
252impl serde::Serialize for ConversationId {
253    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
254    where
255        S: serde::Serializer,
256    {
257        serializer.serialize_str(self.as_str())
258    }
259}
260
261impl<'de> serde::Deserialize<'de> for ConversationId {
262    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
263    where
264        D: serde::Deserializer<'de>,
265    {
266        let value = <String as serde::Deserialize>::deserialize(deserializer)?;
267        Self::from_str(&value).map_err(serde::de::Error::custom)
268    }
269}
270
271/// Validated identity of a notification, parseable from its canonical string.
272///
273/// ```
274/// use agents_kit::identity::NotificationId;
275///
276/// let value = "notification_0123456789abcdef0123456789abcdef";
277/// let id: NotificationId = value.parse()?;
278/// assert_eq!(id.to_string(), value);
279/// # Ok::<(), agents_kit::KitError>(())
280/// ```
281#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
282pub struct NotificationId(Identity);
283
284impl NotificationId {
285    pub(crate) fn new() -> Self {
286        Self(Identity::new(IdentityKind::Notification))
287    }
288
289    /// Borrows the canonical notification identity string.
290    ///
291    /// ```
292    /// use agents_kit::identity::NotificationId;
293    ///
294    /// let value = "notification_0123456789abcdef0123456789abcdef";
295    /// let id: NotificationId = value.parse()?;
296    /// assert_eq!(id.as_str(), value);
297    /// # Ok::<(), agents_kit::KitError>(())
298    /// ```
299    pub fn as_str(&self) -> &str {
300        self.0.as_str()
301    }
302}
303
304impl fmt::Display for NotificationId {
305    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
306        formatter.write_str(self.as_str())
307    }
308}
309
310impl FromStr for NotificationId {
311    type Err = KitError;
312
313    fn from_str(value: &str) -> Result<Self, Self::Err> {
314        Identity::parse(IdentityKind::Notification, value.to_owned()).map(Self)
315    }
316}
317
318impl serde::Serialize for NotificationId {
319    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
320    where
321        S: serde::Serializer,
322    {
323        serializer.serialize_str(self.as_str())
324    }
325}
326
327impl<'de> serde::Deserialize<'de> for NotificationId {
328    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
329    where
330        D: serde::Deserializer<'de>,
331    {
332        let value = <String as serde::Deserialize>::deserialize(deserializer)?;
333        Self::from_str(&value).map_err(serde::de::Error::custom)
334    }
335}