crabtalk_core/runtime/
session.rs1use crate::model::Message;
4use serde::{Deserialize, Serialize};
5use std::{
6 fs::{self, OpenOptions},
7 io::Write,
8 path::{Path, PathBuf},
9 time::{Instant, SystemTime},
10};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct SessionMeta {
15 pub agent: String,
16 pub created_by: String,
17 pub created_at: String,
18}
19
20#[derive(Debug, Clone)]
26pub struct Session {
27 pub id: u64,
29 pub agent: String,
31 pub history: Vec<Message>,
33 pub created_by: String,
35 pub created_at: Instant,
37 pub file_path: Option<PathBuf>,
39}
40
41impl Session {
42 pub fn new(id: u64, agent: impl Into<String>, created_by: impl Into<String>) -> Self {
44 Self {
45 id,
46 agent: agent.into(),
47 history: Vec::new(),
48 created_by: created_by.into(),
49 created_at: Instant::now(),
50 file_path: None,
51 }
52 }
53
54 pub fn init_file(&mut self, sessions_dir: &Path) {
58 let _ = fs::create_dir_all(sessions_dir);
59 let ts = SystemTime::now()
60 .duration_since(SystemTime::UNIX_EPOCH)
61 .unwrap_or_default()
62 .as_secs();
63 let filename = format!("{}_{ts}_{}.jsonl", self.agent, self.id);
64 let path = sessions_dir.join(filename);
65
66 let meta = SessionMeta {
67 agent: self.agent.clone(),
68 created_by: self.created_by.clone(),
69 created_at: chrono::Utc::now().to_rfc3339(),
70 };
71
72 match OpenOptions::new()
73 .create(true)
74 .write(true)
75 .truncate(true)
76 .open(&path)
77 {
78 Ok(mut f) => {
79 if let Ok(json) = serde_json::to_string(&meta) {
80 let _ = writeln!(f, "{json}");
81 }
82 self.file_path = Some(path);
83 }
84 Err(e) => tracing::warn!("failed to create session file: {e}"),
85 }
86 }
87
88 pub fn persist(&self) {
93 let Some(ref path) = self.file_path else {
94 return;
95 };
96
97 let meta = SessionMeta {
98 agent: self.agent.clone(),
99 created_by: self.created_by.clone(),
100 created_at: chrono::Utc::now().to_rfc3339(),
101 };
102
103 let mut file = match OpenOptions::new()
104 .create(true)
105 .write(true)
106 .truncate(true)
107 .open(path)
108 {
109 Ok(f) => f,
110 Err(e) => {
111 tracing::warn!("failed to persist session {}: {e}", self.id);
112 return;
113 }
114 };
115
116 if let Ok(json) = serde_json::to_string(&meta) {
117 let _ = writeln!(file, "{json}");
118 }
119
120 for msg in &self.history {
121 if msg.auto_injected {
122 continue;
123 }
124 if let Ok(json) = serde_json::to_string(msg) {
125 let _ = writeln!(file, "{json}");
126 }
127 }
128 }
129}