Skip to main content

vtcode_core/session/
mod.rs

1use 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/// Session identifier for conversation persistence.
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, StringNewtype)]
12pub struct SessionId(String);
13
14impl SessionId {
15    /// Generate a new random session identifier.
16    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/// Session state that can be persisted and resumed.
28#[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    /// Save session to disk.
57    #[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    /// Load session from disk.
63    #[must_use = "session load failure goes undetected"]
64    pub fn load(path: &Path) -> Result<Self> {
65        read_json_file_sync(path)
66    }
67}
68
69/// Resolve the default session persistence path for a workspace.
70pub 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}