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);
106
107impl BranchId {
108    /// The default "main" branch.
109    pub fn main() -> Self {
110        Self("main".to_owned())
111    }
112}
113
114/// Content-addressed blob hash (SHA-256 hex string).
115#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
116#[serde(transparent)]
117pub struct BlobHash(String);
118
119impl BlobHash {
120    pub fn from_hex(hex: impl Into<String>) -> Self {
121        Self(hex.into())
122    }
123
124    pub fn as_str(&self) -> &str {
125        &self.0
126    }
127}
128
129impl fmt::Display for BlobHash {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        write!(f, "{}", self.0)
132    }
133}
134
135impl From<String> for BlobHash {
136    fn from(s: String) -> Self {
137        Self(s)
138    }
139}
140
141impl AsRef<str> for BlobHash {
142    fn as_ref(&self) -> &str {
143        &self.0
144    }
145}
146
147/// Monotonic sequence number within a branch.
148pub type SeqNo = u64;
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn event_id_new_is_unique() {
156        let a = EventId::new_uuid();
157        let b = EventId::new_uuid();
158        assert_ne!(a, b);
159    }
160
161    #[test]
162    fn session_id_from_string() {
163        let id = SessionId::from_string("test-session");
164        assert_eq!(id.as_str(), "test-session");
165        assert_eq!(id.to_string(), "test-session");
166    }
167
168    #[test]
169    fn branch_id_main() {
170        let id = BranchId::main();
171        assert_eq!(id.as_str(), "main");
172    }
173
174    #[test]
175    fn branch_id_from_str_trait() {
176        let id: BranchId = "dev".into();
177        assert_eq!(id.as_str(), "dev");
178    }
179
180    #[test]
181    fn typed_id_serde_roundtrip() {
182        let id = EventId::from_string("EVT001");
183        let json = serde_json::to_string(&id).unwrap();
184        assert_eq!(json, "\"EVT001\"");
185        let back: EventId = serde_json::from_str(&json).unwrap();
186        assert_eq!(id, back);
187    }
188
189    #[test]
190    fn typed_id_hash_equality() {
191        use std::collections::HashSet;
192        let a = SessionId::from_string("same");
193        let b = SessionId::from_string("same");
194        let mut set = HashSet::new();
195        set.insert(a);
196        assert!(set.contains(&b));
197    }
198
199    #[test]
200    fn blob_hash_serde_roundtrip() {
201        let hash = BlobHash::from_hex("deadbeef");
202        let json = serde_json::to_string(&hash).unwrap();
203        assert_eq!(json, "\"deadbeef\"");
204        let back: BlobHash = serde_json::from_str(&json).unwrap();
205        assert_eq!(hash, back);
206    }
207
208    #[test]
209    fn memory_id_uniqueness() {
210        let a = MemoryId::new_uuid();
211        let b = MemoryId::new_uuid();
212        assert_ne!(a, b);
213    }
214}