1use 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 pub fn from_string(s: impl Into<String>) -> Self {
20 Self(s.into())
21 }
22
23 pub fn new_uuid() -> Self {
25 Self(uuid::Uuid::new_v4().to_string())
26 }
27
28 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 EventId
69);
70typed_id!(
71 AgentId
73);
74typed_id!(
75 SessionId
77);
78typed_id!(
79 BranchId
81);
82typed_id!(
83 RunId
85);
86typed_id!(
87 SnapshotId
89);
90typed_id!(
91 ApprovalId
93);
94typed_id!(
95 MemoryId
97);
98typed_id!(
99 ToolRunId
101);
102typed_id!(
103 CheckpointId
105);
106
107impl BranchId {
108 pub fn main() -> Self {
110 Self("main".to_owned())
111 }
112}
113
114#[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
147pub 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}