rightkit_agent_engine_protocol/
ids.rs1use std::sync::atomic::{AtomicU64, Ordering};
26use std::time::{SystemTime, UNIX_EPOCH};
27
28static COUNTER: AtomicU64 = AtomicU64::new(0);
32
33struct SplitMix64(u64);
36
37impl SplitMix64 {
38 fn next(&mut self) -> u64 {
39 self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
40 let mut z = self.0;
41 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
42 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
43 z ^ (z >> 31)
44 }
45}
46
47fn seed_mix() -> u64 {
51 let nanos = SystemTime::now()
52 .duration_since(UNIX_EPOCH)
53 .map(|d| d.as_nanos() as u64)
54 .unwrap_or(0);
55 let pid = std::process::id() as u64;
56 let ctr = COUNTER.fetch_add(1, Ordering::Relaxed);
57 nanos ^ pid.rotate_left(17) ^ ctr.rotate_left(31)
58}
59
60pub fn uuid_v7() -> String {
64 let ms: u64 = SystemTime::now()
65 .duration_since(UNIX_EPOCH)
66 .map(|d| d.as_millis() as u64)
67 .unwrap_or(0);
68
69 let mut sm = SplitMix64(seed_mix());
70 let r1 = sm.next();
71 let r2 = sm.next();
72
73 let mut bytes = [0u8; 16];
74 bytes[0..6].copy_from_slice(&ms.to_be_bytes()[2..8]);
75 bytes[6] = ((ms >> 8) & 0x0f) as u8 | 0x70;
76 bytes[7] = (ms & 0xff) as u8;
77 bytes[8] = ((r1 & 0x3f) as u8) | 0x80;
78 bytes[9] = ((r1 >> 8) & 0xff) as u8;
79 bytes[10] = ((r1 >> 16) & 0xff) as u8;
80 bytes[11] = ((r1 >> 24) & 0xff) as u8;
81 bytes[12] = ((r1 >> 32) & 0xff) as u8;
82 bytes[13] = ((r1 >> 40) & 0xff) as u8;
83 bytes[14] = (r2 & 0xff) as u8;
84 bytes[15] = ((r2 >> 8) & 0xff) as u8;
85
86 format!(
87 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
88 bytes[0], bytes[1], bytes[2], bytes[3],
89 bytes[4], bytes[5],
90 bytes[6], bytes[7],
91 bytes[8], bytes[9],
92 bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
93 )
94}
95
96pub fn is_canonical_uuid_v7(id: &str) -> bool {
100 if id.len() != 36 {
101 return false;
102 }
103 let bytes = id.as_bytes();
104 bytes[8] == b'-'
105 && bytes[13] == b'-'
106 && bytes[18] == b'-'
107 && bytes[23] == b'-'
108 && bytes[14] == b'7'
109 && matches!(bytes[19], b'8' | b'9' | b'a' | b'b')
110}
111
112pub fn message_id(row_seq: i64) -> String {
116 format!("message-{row_seq}")
117}
118
119pub fn tool_call_origin_id(call_id: &str, row_seq: i64) -> String {
125 format!("tool_call_origin:{call_id}:{row_seq}")
126}
127
128pub fn tool_result_id(call_id: &str, row_seq: i64) -> String {
130 format!("tool:{call_id}:{row_seq}")
131}
132
133pub fn assistant_message_id(row_seq: i64) -> String {
135 format!("assistant:{row_seq}")
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn uuid_v7_has_canonical_version_and_variant() {
144 let id = uuid_v7();
145 assert_eq!(id.len(), 36);
146 assert!(is_canonical_uuid_v7(&id), "{id} not v7");
147 }
148
149 #[test]
150 fn uuid_v7_is_lexicographically_sortable_by_minting_time() {
151 let a = uuid_v7();
152 std::thread::sleep(std::time::Duration::from_millis(2));
153 let b = uuid_v7();
154 assert!(a < b, "expected {a} < {b}");
155 }
156
157 #[test]
158 fn sequential_mints_differ_in_their_random_tail() {
159 let a = uuid_v7();
160 let b = uuid_v7();
161 assert_ne!(a, b);
162 }
163
164 #[test]
165 fn message_id_derives_from_row_seq() {
166 assert_eq!(message_id(7), "message-7");
167 assert_eq!(tool_call_origin_id("tc1", 11), "tool_call_origin:tc1:11");
168 assert_eq!(tool_result_id("tc1", 11), "tool:tc1:11");
169 assert_eq!(assistant_message_id(11), "assistant:11");
170 }
171
172 #[test]
173 fn is_canonical_uuid_v7_only_accepts_v7() {
174 assert!(is_canonical_uuid_v7("01920000-0000-7000-8000-000000000000"));
175 assert!(!is_canonical_uuid_v7(
176 "01920000-0000-4000-8000-000000000000"
177 ));
178 assert!(!is_canonical_uuid_v7("not-a-uuid"));
179 assert!(!is_canonical_uuid_v7(""));
180 }
181}