vtcode_core/session/
mod.rs1use anyhow::Result;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use std::path::{Path, PathBuf};
5use vtcode_macros::StringNewtype;
6
7use crate::llm::provider::Message;
8use crate::utils::file_utils::{read_json_file_sync, write_json_file_sync};
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, StringNewtype)]
12pub struct SessionId(String);
13
14impl SessionId {
15 pub fn generate() -> Self {
17 Self(uuid::Uuid::new_v4().to_string())
18 }
19}
20
21impl Default for SessionId {
22 fn default() -> Self {
23 Self::generate()
24 }
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct SessionState {
30 pub id: SessionId,
31 pub created_at: DateTime<Utc>,
32 pub last_updated: DateTime<Utc>,
33 pub history: Vec<Message>,
34 pub active_skills: Vec<String>,
35 pub working_dir: PathBuf,
36}
37
38impl SessionState {
39 pub fn new(
40 id: SessionId,
41 created_at: DateTime<Utc>,
42 history: Vec<Message>,
43 active_skills: Vec<String>,
44 working_dir: PathBuf,
45 ) -> Self {
46 Self {
47 id,
48 created_at,
49 last_updated: created_at,
50 history,
51 active_skills,
52 working_dir,
53 }
54 }
55
56 #[must_use = "session save silently drops data"]
58 pub fn save(&self, path: &Path) -> Result<()> {
59 write_json_file_sync(path, self)
60 }
61
62 #[must_use = "session load failure goes undetected"]
64 pub fn load(path: &Path) -> Result<Self> {
65 read_json_file_sync(path)
66 }
67}
68
69pub fn session_path(workspace_root: &Path, id: &SessionId) -> PathBuf {
71 workspace_root
72 .join(".vtcode")
73 .join("sessions")
74 .join(format!("{}.json", id.as_str()))
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use tempfile::TempDir;
81
82 #[test]
83 fn session_state_round_trip() {
84 let tmp = TempDir::new().expect("temp dir");
85 let id = SessionId::new("session-1");
86 let created_at = Utc::now();
87 let history = vec![Message::user("hello".to_string())];
88 let state = SessionState::new(
89 id.clone(),
90 created_at,
91 history.clone(),
92 vec!["skill-a".to_string()],
93 tmp.path().to_path_buf(),
94 );
95
96 let path = session_path(tmp.path(), &id);
97 state.save(&path).expect("save");
98 let loaded = SessionState::load(&path).expect("load");
99
100 assert_eq!(loaded.id, id);
101 assert_eq!(loaded.history, history);
102 }
103}