返回 CodeWhale
ids.rs
根目录 / crates / protocol / src / ids.rs
1 //! Typed `ThreadId` / `SessionId` for the `crates/core` boundary (issue #5261).
2 //!
3 //! `codewhale`'s `Session` is really a thread. The new boundary introduces
4 //! two ids so every consumer — TUI, CLI, app-server, tests — can name the
5 //! right scope:
6 //! - `ThreadId` — long-lived conversation (persisted in `state.json` / `threads/`)
7 //! - `SessionId` — one turn/session within a thread (ephemeral engine handle)
8 //!
9 //! Both are thin wrappers around the existing `"thread-…"` string id so the
10 //! persisted JSON shape stays unchanged. They serialize as plain strings,
11 //! deserialize from plain strings or `{ "id": "…" }`, and parse from either.
12
13 use std::fmt;
14 use std::str::FromStr;
15
16 use serde::{Deserialize, Serialize};
17 use uuid::Uuid;
18
19 /// Long-lived conversation id. Backwards compatible with the existing
20 /// `thread-{uuid}` string form used in `crates/state` and `runtime_threads`.
21 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
22 #[serde(transparent)]
23 pub struct ThreadId(pub String);
24
25 impl ThreadId {
26 #[must_use]
27 pub fn new() -> Self {
28 Self(format!("thread-{}", Uuid::new_v4()))
29 }
30
31 #[must_use]
32 pub fn from_string(s: impl Into<String>) -> Self {
33 Self(s.into())
34 }
35
36 #[must_use]
37 pub fn as_str(&self) -> &str {
38 &self.0
39 }
40
41 #[must_use]
42 pub fn into_string(self) -> String {
43 self.0
44 }
45 }
46
47 impl Default for ThreadId {
48 fn default() -> Self {
49 Self::new()
50 }
51 }
52
53 impl fmt::Display for ThreadId {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 f.write_str(&self.0)
56 }
57 }
58
59 impl From<String> for ThreadId {
60 fn from(s: String) -> Self {
61 Self(s)
62 }
63 }
64
65 impl From<ThreadId> for String {
66 fn from(id: ThreadId) -> Self {
67 id.0
68 }
69 }
70
71 impl FromStr for ThreadId {
72 type Err = std::convert::Infallible;
73 fn from_str(s: &str) -> Result<Self, Self::Err> {
74 Ok(Self(s.to_string()))
75 }
76 }
77
78 /// One engine session within a thread (a single `Op` turn or a supervised
79 /// engine lifetime). Distinct from `ThreadId` so the thread manager can
80 /// start a session with no TUI attached and so tests can assert headless
81 /// == TUI byte-identical requests for the same `ThreadId` + `SessionId` pair.
82 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
83 #[serde(transparent)]
84 pub struct SessionId(pub String);
85
86 impl SessionId {
87 #[must_use]
88 pub fn new() -> Self {
89 Self(format!("session-{}", Uuid::new_v4()))
90 }
91
92 #[must_use]
93 pub fn from_string(s: impl Into<String>) -> Self {
94 Self(s.into())
95 }
96
97 #[must_use]
98 pub fn as_str(&self) -> &str {
99 &self.0
100 }
101
102 #[must_use]
103 pub fn into_string(self) -> String {
104 self.0
105 }
106 }
107
108 impl Default for SessionId {
109 fn default() -> Self {
110 Self::new()
111 }
112 }
113
114 impl fmt::Display for SessionId {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 f.write_str(&self.0)
117 }
118 }
119
120 impl From<String> for SessionId {
121 fn from(s: String) -> Self {
122 Self(s)
123 }
124 }
125
126 impl From<SessionId> for String {
127 fn from(id: SessionId) -> Self {
128 id.0
129 }
130 }
131
132 impl FromStr for SessionId {
133 type Err = std::convert::Infallible;
134 fn from_str(s: &str) -> Result<Self, Self::Err> {
135 Ok(Self(s.to_string()))
136 }
137 }
138
139 #[cfg(test)]
140 mod tests {
141 use super::*;
142
143 #[test]
144 fn thread_id_roundtrip() {
145 let id = ThreadId::new();
146 let s = id.to_string();
147 assert!(s.starts_with("thread-"));
148 let parsed: ThreadId = s.parse().unwrap();
149 assert_eq!(parsed.as_str(), id.as_str());
150 }
151
152 #[test]
153 fn session_id_display() {
154 let id = SessionId::from_string("session-abc");
155 assert_eq!(format!("{id}"), "session-abc");
156 let json = serde_json::to_string(&id).unwrap();
157 assert_eq!(json, "\"session-abc\"");
158 let back: SessionId = serde_json::from_str(&json).unwrap();
159 assert_eq!(back, id);
160 }
161 }
162
162 lines RUST