Skip to main content

autumn_plugin_agent/
ids.rs

1//! Strongly typed identifiers for runs and sessions.
2//!
3//! Both are thin `String` newtypes. They serialize as plain strings, so they
4//! fit a database column or a job payload without a custom codec.
5
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::time::{SystemTime, UNIX_EPOCH};
8
9use serde::{Deserialize, Serialize};
10
11/// Identifies one agent run.
12///
13/// Tools receive it in [`ToolContext`](crate::tools::ToolContext) and can use
14/// it, with the call id, as an idempotency key. A paused run keeps its id
15/// across [`Agent::resume`](crate::agent::Agent::resume).
16#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
17#[serde(transparent)]
18pub struct RunId(String);
19
20impl RunId {
21    /// Wrap an id the caller already owns (a job id, a request id).
22    #[must_use]
23    pub fn new(id: impl Into<String>) -> Self {
24        Self(id.into())
25    }
26
27    /// Make a new process-unique id: `run_<unix-nanos-hex>_<counter-hex>`.
28    ///
29    /// The counter makes ids unique inside one process. Across replicas the
30    /// nanosecond clock makes a clash unlikely, not impossible: pass your own
31    /// id with [`RunId::new`] when you need a global guarantee.
32    #[must_use]
33    pub fn generate() -> Self {
34        static COUNTER: AtomicU64 = AtomicU64::new(0);
35        let nanos = SystemTime::now()
36            .duration_since(UNIX_EPOCH)
37            .map_or(0, |elapsed| elapsed.as_nanos());
38        let count = COUNTER.fetch_add(1, Ordering::Relaxed);
39        Self(format!("run_{nanos:x}_{count:x}"))
40    }
41
42    /// The id as a string slice.
43    #[must_use]
44    pub fn as_str(&self) -> &str {
45        &self.0
46    }
47}
48
49impl std::fmt::Display for RunId {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        f.write_str(&self.0)
52    }
53}
54
55/// Identifies one persisted conversation.
56///
57/// The app picks the key. Map a chat thread, an email `Message-ID` chain, or
58/// a user id to a `SessionId`, and every run with that id continues the same
59/// transcript.
60#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
61#[serde(transparent)]
62pub struct SessionId(String);
63
64impl SessionId {
65    /// Wrap an app-chosen session key.
66    #[must_use]
67    pub fn new(id: impl Into<String>) -> Self {
68        Self(id.into())
69    }
70
71    /// The id as a string slice.
72    #[must_use]
73    pub fn as_str(&self) -> &str {
74        &self.0
75    }
76}
77
78impl std::fmt::Display for SessionId {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        f.write_str(&self.0)
81    }
82}
83
84impl From<&str> for SessionId {
85    fn from(id: &str) -> Self {
86        Self::new(id)
87    }
88}
89
90impl From<String> for SessionId {
91    fn from(id: String) -> Self {
92        Self(id)
93    }
94}
95
96#[cfg(test)]
97mod tests;