1use std::collections::HashMap;
9
10use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct Session {
20 pub key: String,
22
23 #[serde(default)]
25 pub messages: Vec<serde_json::Value>,
26
27 #[serde(default = "Utc::now")]
29 pub created_at: DateTime<Utc>,
30
31 #[serde(default = "Utc::now")]
33 pub updated_at: DateTime<Utc>,
34
35 #[serde(default)]
37 pub metadata: HashMap<String, serde_json::Value>,
38
39 #[serde(default)]
41 pub last_consolidated: usize,
42}
43
44impl Session {
45 pub fn new(key: impl Into<String>) -> Self {
47 let now = Utc::now();
48 Self {
49 key: key.into(),
50 messages: Vec::new(),
51 created_at: now,
52 updated_at: now,
53 metadata: HashMap::new(),
54 last_consolidated: 0,
55 }
56 }
57
58 pub fn add_message(
63 &mut self,
64 role: &str,
65 content: &str,
66 extras: Option<HashMap<String, serde_json::Value>>,
67 ) {
68 let mut msg = serde_json::json!({
69 "role": role,
70 "content": content,
71 "timestamp": Utc::now().to_rfc3339(),
72 });
73
74 if let Some(extras) = extras
75 && let Some(obj) = msg.as_object_mut()
76 {
77 for (k, v) in extras {
78 obj.insert(k, v);
79 }
80 }
81
82 self.messages.push(msg);
83 self.updated_at = Utc::now();
84 }
85
86 pub fn get_history(&self, max_messages: usize) -> Vec<serde_json::Value> {
92 let start = self.messages.len().saturating_sub(max_messages);
93 self.messages[start..]
94 .iter()
95 .map(|m| {
96 let mut msg = serde_json::json!({
97 "role": m.get("role").and_then(|v| v.as_str()).unwrap_or("user"),
98 "content": m.get("content").and_then(|v| v.as_str()).unwrap_or(""),
99 });
100 if let Some(tool_call_id) = m.get("tool_call_id").filter(|v| !v.is_null()) {
101 msg["tool_call_id"] = tool_call_id.clone();
102 }
103 if let Some(tool_calls) = m.get("tool_calls").filter(|v| !v.is_null()) {
104 msg["tool_calls"] = tool_calls.clone();
105 }
106 msg
107 })
108 .collect()
109 }
110
111 pub fn clear(&mut self) {
113 self.messages.clear();
114 self.last_consolidated = 0;
115 self.updated_at = Utc::now();
116 }
117}
118
119impl Default for Session {
120 fn default() -> Self {
121 Self::new("")
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn new_session() {
131 let s = Session::new("telegram:123");
132 assert_eq!(s.key, "telegram:123");
133 assert!(s.messages.is_empty());
134 assert_eq!(s.last_consolidated, 0);
135 }
136
137 #[test]
138 fn add_message_basic() {
139 let mut s = Session::new("test");
140 s.add_message("user", "hello", None);
141 s.add_message("assistant", "hi there", None);
142 assert_eq!(s.messages.len(), 2);
143 assert_eq!(s.messages[0]["role"], "user");
144 assert_eq!(s.messages[1]["content"], "hi there");
145 }
146
147 #[test]
148 fn add_message_with_extras() {
149 let mut s = Session::new("test");
150 let mut extras = HashMap::new();
151 extras.insert("tool_calls".into(), serde_json::json!([{"id": "tc1"}]));
152 s.add_message("assistant", "let me check", Some(extras));
153 assert!(s.messages[0].get("tool_calls").is_some());
154 }
155
156 #[test]
157 fn get_history_all() {
158 let mut s = Session::new("test");
159 s.add_message("user", "one", None);
160 s.add_message("assistant", "two", None);
161 let hist = s.get_history(500);
162 assert_eq!(hist.len(), 2);
163 assert_eq!(hist[0]["role"], "user");
164 assert_eq!(hist[1]["content"], "two");
165 }
166
167 #[test]
168 fn get_history_truncated() {
169 let mut s = Session::new("test");
170 for i in 0..10 {
171 s.add_message("user", &format!("msg {i}"), None);
172 }
173 let hist = s.get_history(3);
174 assert_eq!(hist.len(), 3);
175 assert_eq!(hist[0]["content"], "msg 7");
176 assert_eq!(hist[2]["content"], "msg 9");
177 }
178
179 #[test]
180 fn clear_resets_state() {
181 let mut s = Session::new("test");
182 s.add_message("user", "hello", None);
183 s.last_consolidated = 1;
184 s.clear();
185 assert!(s.messages.is_empty());
186 assert_eq!(s.last_consolidated, 0);
187 }
188
189 #[test]
190 fn serde_roundtrip() {
191 let mut s = Session::new("slack:C123");
192 s.add_message("user", "test", None);
193 s.last_consolidated = 0;
194
195 let json = serde_json::to_string(&s).unwrap();
196 let restored: Session = serde_json::from_str(&json).unwrap();
197 assert_eq!(restored.key, "slack:C123");
198 assert_eq!(restored.messages.len(), 1);
199 }
200
201 #[test]
202 fn default_session() {
203 let s = Session::default();
204 assert_eq!(s.key, "");
205 assert!(s.messages.is_empty());
206 }
207}