gemini_memory_rs/core/
ids.rs

1//! Newtype identifiers for the memory engine.
2//!
3//! Every identifier is a distinct type so a `SessionId` can never be passed
4//! where a `UserId` is expected — the memory engine's namespacing and privacy
5//! guarantees depend on that distinction being enforced by the compiler rather
6//! than by review.
7
8use serde::{Deserialize, Serialize};
9use std::fmt;
10
11macro_rules! string_id {
12    ($(#[$meta:meta])* $name:ident, $prefix:literal) => {
13        $(#[$meta])*
14        #[derive(
15            Debug,
16            Clone,
17            PartialEq,
18            Eq,
19            Hash,
20            PartialOrd,
21            Ord,
22            Serialize,
23            Deserialize,
24            schemars::JsonSchema,
25        )]
26        #[serde(transparent)]
27        pub struct $name(String);
28
29        impl $name {
30            /// Wrap an existing string as this identifier.
31            pub fn new(value: impl Into<String>) -> Self {
32                Self(value.into())
33            }
34
35            /// Mint a fresh random identifier with this type's conventional prefix.
36            pub fn generate() -> Self {
37                let raw = uuid::Uuid::new_v4().simple().to_string();
38                Self(format!("{}_{}", $prefix, &raw[..12]))
39            }
40
41            /// Borrow the identifier as a string slice.
42            pub fn as_str(&self) -> &str {
43                &self.0
44            }
45
46            /// Consume the identifier, yielding the underlying string.
47            pub fn into_string(self) -> String {
48                self.0
49            }
50        }
51
52        impl fmt::Display for $name {
53            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54                f.write_str(&self.0)
55            }
56        }
57
58        impl From<&str> for $name {
59            fn from(value: &str) -> Self {
60                Self(value.to_string())
61            }
62        }
63
64        impl From<String> for $name {
65            fn from(value: String) -> Self {
66                Self(value)
67            }
68        }
69    };
70}
71
72string_id!(
73    /// The person who owns a memory namespace. Never accepted from model output.
74    UserId,
75    "usr"
76);
77string_id!(
78    /// A durable canonical memory record.
79    MemoryId,
80    "mem"
81);
82string_id!(
83    /// A logical conversation, which may span several transport sessions.
84    SessionId,
85    "ses"
86);
87string_id!(
88    /// One Gemini Live WebSocket connection within a logical session.
89    ConnectionId,
90    "con"
91);
92string_id!(
93    /// A single extracted interpretation of one user statement.
94    ObservationId,
95    "obs"
96);
97string_id!(
98    /// A retrieval plan produced from a transcript.
99    PlanId,
100    "pln"
101);
102string_id!(
103    /// An immutable prepared-memory snapshot.
104    SnapshotId,
105    "snp"
106);
107string_id!(
108    /// An entry in the append-only memory event log.
109    EventId,
110    "evt"
111);
112string_id!(
113    /// A person, place or thing referenced by memories.
114    EntityId,
115    "ent"
116);
117
118/// Monotonic per-logical-session turn counter.
119///
120/// Turn identity is assigned locally rather than derived from wire ordering:
121/// the Live API delivers input transcription independently of turn boundaries,
122/// so a locally-assigned turn id is the only stable correlation key.
123#[derive(
124    Debug,
125    Clone,
126    Copy,
127    PartialEq,
128    Eq,
129    Hash,
130    PartialOrd,
131    Ord,
132    Serialize,
133    Deserialize,
134    Default,
135    schemars::JsonSchema,
136)]
137#[serde(transparent)]
138pub struct TurnId(pub u64);
139
140impl TurnId {
141    /// The turn id before any user turn has started.
142    pub const ZERO: Self = Self(0);
143
144    /// The next turn in sequence.
145    pub fn next(self) -> Self {
146        Self(self.0 + 1)
147    }
148}
149
150impl fmt::Display for TurnId {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        write!(f, "turn_{}", self.0)
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn generated_ids_carry_their_prefix_and_are_unique() {
162        let a = MemoryId::generate();
163        let b = MemoryId::generate();
164        assert!(a.as_str().starts_with("mem_"));
165        assert_ne!(a, b);
166    }
167
168    #[test]
169    fn ids_round_trip_as_transparent_strings() {
170        let id = UserId::new("usr_72ab");
171        let json = serde_json::to_string(&id).unwrap();
172        assert_eq!(json, "\"usr_72ab\"");
173        assert_eq!(serde_json::from_str::<UserId>(&json).unwrap(), id);
174    }
175
176    #[test]
177    fn turn_ids_advance() {
178        assert_eq!(TurnId::ZERO.next(), TurnId(1));
179    }
180}