Skip to main content

serdes_ai_core/
identifier.rs

1//! ID generation utilities.
2//!
3//! This module provides functions for generating unique identifiers
4//! for tool calls, runs, and other entities.
5
6use chrono::{DateTime, Utc};
7use uuid::Uuid;
8
9/// Generate a unique tool call ID.
10///
11/// Returns a UUID v4 string in the format used by most LLM providers.
12///
13/// # Example
14///
15/// ```rust
16/// use serdes_ai_core::identifier::generate_tool_call_id;
17///
18/// let id = generate_tool_call_id();
19/// assert!(id.starts_with("call_"));
20/// assert_eq!(id.len(), 37); // "call_" + 32 hex chars
21/// ```
22#[must_use]
23pub fn generate_tool_call_id() -> String {
24    format!("call_{}", Uuid::new_v4().simple())
25}
26
27/// Generate a unique run ID.
28///
29/// Returns a UUID v4 string prefixed with "run_".
30///
31/// # Example
32///
33/// ```rust
34/// use serdes_ai_core::identifier::generate_run_id;
35///
36/// let id = generate_run_id();
37/// assert!(id.starts_with("run_"));
38/// ```
39#[must_use]
40pub fn generate_run_id() -> String {
41    format!("run_{}", Uuid::new_v4().simple())
42}
43
44/// Generate a unique message ID.
45///
46/// Returns a UUID v4 string prefixed with "msg_".
47#[must_use]
48pub fn generate_message_id() -> String {
49    format!("msg_{}", Uuid::new_v4().simple())
50}
51
52/// Generate a unique conversation ID.
53///
54/// Returns a UUID v4 string prefixed with "conv_".
55#[must_use]
56pub fn generate_conversation_id() -> String {
57    format!("conv_{}", Uuid::new_v4().simple())
58}
59
60/// Generate a raw UUID v4 string (no prefix).
61#[must_use]
62pub fn generate_uuid() -> String {
63    Uuid::new_v4().to_string()
64}
65
66/// Generate a short ID suitable for display.
67///
68/// Returns the first 8 characters of a UUID.
69#[must_use]
70pub fn generate_short_id() -> String {
71    Uuid::new_v4().simple().to_string()[..8].to_string()
72}
73
74/// Get the current UTC timestamp.
75///
76/// # Example
77///
78/// ```rust
79/// use serdes_ai_core::identifier::now_utc;
80///
81/// let timestamp = now_utc();
82/// println!("Current time: {}", timestamp);
83/// ```
84#[must_use]
85pub fn now_utc() -> DateTime<Utc> {
86    Utc::now()
87}
88
89/// Parse a timestamp from an ISO 8601 string.
90///
91/// # Errors
92///
93/// Returns an error if the string is not a valid ISO 8601 timestamp.
94pub fn parse_timestamp(s: &str) -> Result<DateTime<Utc>, chrono::ParseError> {
95    s.parse()
96}
97
98/// Format a timestamp as ISO 8601.
99#[must_use]
100pub fn format_timestamp(dt: &DateTime<Utc>) -> String {
101    dt.to_rfc3339()
102}
103
104/// Type-safe wrapper for a tool call ID.
105#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
106#[serde(transparent)]
107pub struct ToolCallId(String);
108
109impl ToolCallId {
110    /// Create a new tool call ID.
111    #[must_use]
112    pub fn new() -> Self {
113        Self(generate_tool_call_id())
114    }
115
116    /// Create from an existing string.
117    #[must_use]
118    pub fn from_string(s: impl Into<String>) -> Self {
119        Self(s.into())
120    }
121
122    /// Get the ID as a string slice.
123    #[must_use]
124    pub fn as_str(&self) -> &str {
125        &self.0
126    }
127}
128
129impl Default for ToolCallId {
130    fn default() -> Self {
131        Self::new()
132    }
133}
134
135impl std::fmt::Display for ToolCallId {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        write!(f, "{}", self.0)
138    }
139}
140
141impl From<String> for ToolCallId {
142    fn from(s: String) -> Self {
143        Self(s)
144    }
145}
146
147impl From<&str> for ToolCallId {
148    fn from(s: &str) -> Self {
149        Self(s.to_string())
150    }
151}
152
153impl AsRef<str> for ToolCallId {
154    fn as_ref(&self) -> &str {
155        &self.0
156    }
157}
158
159/// Type-safe wrapper for a run ID.
160#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
161#[serde(transparent)]
162pub struct RunId(String);
163
164impl RunId {
165    /// Create a new run ID.
166    #[must_use]
167    pub fn new() -> Self {
168        Self(generate_run_id())
169    }
170
171    /// Create from an existing string.
172    #[must_use]
173    pub fn from_string(s: impl Into<String>) -> Self {
174        Self(s.into())
175    }
176
177    /// Get the ID as a string slice.
178    #[must_use]
179    pub fn as_str(&self) -> &str {
180        &self.0
181    }
182}
183
184impl Default for RunId {
185    fn default() -> Self {
186        Self::new()
187    }
188}
189
190impl std::fmt::Display for RunId {
191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192        write!(f, "{}", self.0)
193    }
194}
195
196impl From<String> for RunId {
197    fn from(s: String) -> Self {
198        Self(s)
199    }
200}
201
202impl From<&str> for RunId {
203    fn from(s: &str) -> Self {
204        Self(s.to_string())
205    }
206}
207
208impl AsRef<str> for RunId {
209    fn as_ref(&self) -> &str {
210        &self.0
211    }
212}
213
214/// Type-safe wrapper for a conversation ID.
215#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
216#[serde(transparent)]
217pub struct ConversationId(String);
218
219impl ConversationId {
220    /// Create a new conversation ID.
221    #[must_use]
222    pub fn new() -> Self {
223        Self(generate_conversation_id())
224    }
225
226    /// Create from an existing string.
227    #[must_use]
228    pub fn from_string(s: impl Into<String>) -> Self {
229        Self(s.into())
230    }
231
232    /// Get the ID as a string slice.
233    #[must_use]
234    pub fn as_str(&self) -> &str {
235        &self.0
236    }
237}
238
239impl Default for ConversationId {
240    fn default() -> Self {
241        Self::new()
242    }
243}
244
245impl std::fmt::Display for ConversationId {
246    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247        write!(f, "{}", self.0)
248    }
249}
250
251impl From<String> for ConversationId {
252    fn from(s: String) -> Self {
253        Self(s)
254    }
255}
256
257impl From<&str> for ConversationId {
258    fn from(s: &str) -> Self {
259        Self(s.to_string())
260    }
261}
262
263impl AsRef<str> for ConversationId {
264    fn as_ref(&self) -> &str {
265        &self.0
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn test_generate_tool_call_id() {
275        let id = generate_tool_call_id();
276        assert!(id.starts_with("call_"));
277        assert_eq!(id.len(), 37);
278    }
279
280    #[test]
281    fn test_generate_run_id() {
282        let id = generate_run_id();
283        assert!(id.starts_with("run_"));
284    }
285
286    #[test]
287    fn test_generate_unique_ids() {
288        let id1 = generate_tool_call_id();
289        let id2 = generate_tool_call_id();
290        assert_ne!(id1, id2);
291    }
292
293    #[test]
294    fn test_tool_call_id_type() {
295        let id = ToolCallId::new();
296        assert!(id.as_str().starts_with("call_"));
297
298        let from_str = ToolCallId::from_string("call_custom");
299        assert_eq!(from_str.as_str(), "call_custom");
300    }
301
302    #[test]
303    fn test_now_utc() {
304        let now = now_utc();
305        let formatted = format_timestamp(&now);
306        let parsed = parse_timestamp(&formatted).unwrap();
307        // Times should be very close (within a second)
308        assert!((now - parsed).num_seconds().abs() <= 1);
309    }
310
311    #[test]
312    fn test_serde_roundtrip() {
313        let id = ToolCallId::new();
314        let json = serde_json::to_string(&id).unwrap();
315        let parsed: ToolCallId = serde_json::from_str(&json).unwrap();
316        assert_eq!(id, parsed);
317    }
318}