Skip to main content

aios_protocol/
ids.rs

1//! Canonical ID types for the Agent OS.
2//!
3//! IDs are opaque String wrappers (serde-transparent) to support both ULID
4//! (Lago-style, 26-char sortable) and UUID (aiOS-style) generation strategies.
5//! Consumers choose their preferred generation; the kernel only requires String.
6
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10macro_rules! typed_id {
11    ($(#[$meta:meta])* $name:ident) => {
12        $(#[$meta])*
13        #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
14        #[serde(transparent)]
15        pub struct $name(String);
16
17        impl $name {
18            /// Create from any string value.
19            pub fn from_string(s: impl Into<String>) -> Self {
20                Self(s.into())
21            }
22
23            /// Create a new ID using UUID v4 (random).
24            pub fn new_uuid() -> Self {
25                Self(uuid::Uuid::new_v4().to_string())
26            }
27
28            /// View as string slice.
29            pub fn as_str(&self) -> &str {
30                &self.0
31            }
32        }
33
34        impl Default for $name {
35            fn default() -> Self {
36                Self::new_uuid()
37            }
38        }
39
40        impl fmt::Display for $name {
41            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42                write!(f, "{}", self.0)
43            }
44        }
45
46        impl From<String> for $name {
47            fn from(s: String) -> Self {
48                Self(s)
49            }
50        }
51
52        impl From<&str> for $name {
53            fn from(s: &str) -> Self {
54                Self(s.to_string())
55            }
56        }
57
58        impl AsRef<str> for $name {
59            fn as_ref(&self) -> &str {
60                &self.0
61            }
62        }
63    };
64}
65
66typed_id!(
67    /// Unique identifier for an event.
68    EventId
69);
70typed_id!(
71    /// Unique identifier for an agent identity.
72    AgentId
73);
74typed_id!(
75    /// Unique identifier for a session.
76    SessionId
77);
78typed_id!(
79    /// Identifier for a branch within a session. Default is "main".
80    BranchId
81);
82typed_id!(
83    /// Unique identifier for an agent run (LLM invocation cycle).
84    RunId
85);
86typed_id!(
87    /// Unique identifier for a snapshot/checkpoint.
88    SnapshotId
89);
90typed_id!(
91    /// Unique identifier for an approval request.
92    ApprovalId
93);
94typed_id!(
95    /// Unique identifier for a memory entry.
96    MemoryId
97);
98typed_id!(
99    /// Unique identifier for a tool execution run.
100    ToolRunId
101);
102typed_id!(
103    /// Unique identifier for a checkpoint.
104    CheckpointId
105);
106typed_id!(
107    /// Unique identifier for a hive collaborative task.
108    HiveTaskId
109);
110
111impl BranchId {
112    /// The default "main" branch.
113    pub fn main() -> Self {
114        Self("main".to_owned())
115    }
116}
117
118/// Content-addressed blob hash (SHA-256 hex string).
119#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
120#[serde(transparent)]
121pub struct BlobHash(String);
122
123impl BlobHash {
124    pub fn from_hex(hex: impl Into<String>) -> Self {
125        Self(hex.into())
126    }
127
128    pub fn as_str(&self) -> &str {
129        &self.0
130    }
131}
132
133impl fmt::Display for BlobHash {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        write!(f, "{}", self.0)
136    }
137}
138
139impl From<String> for BlobHash {
140    fn from(s: String) -> Self {
141        Self(s)
142    }
143}
144
145impl AsRef<str> for BlobHash {
146    fn as_ref(&self) -> &str {
147        &self.0
148    }
149}
150
151/// Monotonic sequence number within a branch.
152pub type SeqNo = u64;
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn event_id_new_is_unique() {
160        let a = EventId::new_uuid();
161        let b = EventId::new_uuid();
162        assert_ne!(a, b);
163    }
164
165    #[test]
166    fn session_id_from_string() {
167        let id = SessionId::from_string("test-session");
168        assert_eq!(id.as_str(), "test-session");
169        assert_eq!(id.to_string(), "test-session");
170    }
171
172    #[test]
173    fn branch_id_main() {
174        let id = BranchId::main();
175        assert_eq!(id.as_str(), "main");
176    }
177
178    #[test]
179    fn branch_id_from_str_trait() {
180        let id: BranchId = "dev".into();
181        assert_eq!(id.as_str(), "dev");
182    }
183
184    #[test]
185    fn typed_id_serde_roundtrip() {
186        let id = EventId::from_string("EVT001");
187        let json = serde_json::to_string(&id).unwrap();
188        assert_eq!(json, "\"EVT001\"");
189        let back: EventId = serde_json::from_str(&json).unwrap();
190        assert_eq!(id, back);
191    }
192
193    #[test]
194    fn typed_id_hash_equality() {
195        use std::collections::HashSet;
196        let a = SessionId::from_string("same");
197        let b = SessionId::from_string("same");
198        let mut set = HashSet::new();
199        set.insert(a);
200        assert!(set.contains(&b));
201    }
202
203    #[test]
204    fn blob_hash_serde_roundtrip() {
205        let hash = BlobHash::from_hex("deadbeef");
206        let json = serde_json::to_string(&hash).unwrap();
207        assert_eq!(json, "\"deadbeef\"");
208        let back: BlobHash = serde_json::from_str(&json).unwrap();
209        assert_eq!(hash, back);
210    }
211
212    #[test]
213    fn memory_id_uniqueness() {
214        let a = MemoryId::new_uuid();
215        let b = MemoryId::new_uuid();
216        assert_ne!(a, b);
217    }
218}