agcodex_persistence/
types.rs

1//! Core types for session persistence
2
3use chrono::DateTime;
4use chrono::Utc;
5// Import and re-export types from core crate
6pub use agcodex_core::models::ContentItem;
7pub use agcodex_core::models::ResponseItem;
8pub use agcodex_core::modes::OperatingMode;
9use serde::Deserialize;
10use serde::Serialize;
11use std::collections::HashMap;
12use std::path::PathBuf;
13use uuid::Uuid;
14
15/// Session metadata stored in bincode format for fast access
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct SessionMetadata {
18    pub id: Uuid,
19    pub title: String,
20    pub created_at: DateTime<Utc>,
21    pub updated_at: DateTime<Utc>,
22    pub last_accessed: DateTime<Utc>,
23    pub message_count: usize,
24    pub turn_count: usize,
25    pub current_mode: OperatingMode,
26    pub model: String,
27    pub tags: Vec<String>,
28    pub is_favorite: bool,
29    pub file_size: u64,
30    pub compression_ratio: f32,
31    pub format_version: u16,
32    pub checkpoints: Vec<CheckpointMetadata>,
33}
34
35/// Metadata for a checkpoint
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct CheckpointMetadata {
38    pub id: Uuid,
39    pub name: String,
40    pub created_at: DateTime<Utc>,
41    pub message_index: usize,
42    pub description: Option<String>,
43}
44
45/// Complete checkpoint data
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct Checkpoint {
48    pub metadata: CheckpointMetadata,
49    pub conversation: ConversationSnapshot,
50    pub state: SessionState,
51}
52
53/// Snapshot of a conversation at a point in time
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct ConversationSnapshot {
56    pub id: Uuid,
57    pub messages: Vec<MessageSnapshot>,
58    pub context: ConversationContext,
59    pub mode_history: Vec<(OperatingMode, DateTime<Utc>)>,
60}
61
62/// Individual message snapshot
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct MessageSnapshot {
65    pub item: ResponseItem,
66    pub timestamp: DateTime<Utc>,
67    pub turn_index: usize,
68    pub metadata: MessageMetadata,
69}
70
71/// Additional metadata for messages
72#[derive(Debug, Clone, Serialize, Deserialize, Default)]
73pub struct MessageMetadata {
74    pub edited: bool,
75    pub edit_history: Vec<(DateTime<Utc>, ResponseItem)>,
76    pub branch_point: bool,
77    pub branch_id: Option<Uuid>,
78    pub parent_message_id: Option<Uuid>,
79    pub message_id: Uuid,
80    pub file_context: Vec<FileContext>,
81    pub tool_calls: Vec<ToolCallMetadata>,
82}
83
84/// File context at the time of message
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct FileContext {
87    pub path: PathBuf,
88    pub line_range: Option<(usize, usize)>,
89    pub snippet: Option<String>,
90    pub language: Option<String>,
91}
92
93/// Tool call metadata
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct ToolCallMetadata {
96    pub tool_name: String,
97    pub execution_time_ms: u64,
98    pub success: bool,
99    pub error: Option<String>,
100}
101
102/// Conversation context for restoration
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct ConversationContext {
105    pub working_directory: PathBuf,
106    pub environment_variables: HashMap<String, String>,
107    pub open_files: Vec<PathBuf>,
108    pub ast_index_state: Option<AstIndexState>,
109    pub embedding_cache: Option<EmbeddingCacheState>,
110}
111
112/// AST index state for context restoration
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct AstIndexState {
115    pub indexed_files: Vec<PathBuf>,
116    pub total_symbols: usize,
117    pub cache_size_bytes: u64,
118    pub last_update: DateTime<Utc>,
119}
120
121/// Embedding cache state
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct EmbeddingCacheState {
124    pub cached_chunks: usize,
125    pub model: String,
126    pub cache_size_bytes: u64,
127}
128
129/// Session state for UI restoration
130#[derive(Debug, Clone, Serialize, Deserialize, Default)]
131pub struct SessionState {
132    pub cursor_position: usize,
133    pub scroll_offset: usize,
134    pub selected_message: Option<Uuid>,
135    pub expanded_messages: Vec<Uuid>,
136    #[serde(default)]
137    pub active_panel: String,
138    #[serde(default)]
139    pub panel_sizes: HashMap<String, f32>,
140    pub search_query: Option<String>,
141    pub filter_settings: FilterSettings,
142}
143
144/// Filter settings for message display
145#[derive(Debug, Clone, Serialize, Deserialize, Default)]
146pub struct FilterSettings {
147    pub show_system: bool,
148    pub show_tool_calls: bool,
149    pub show_reasoning: bool,
150    pub role_filter: Option<String>,
151    pub date_range: Option<(DateTime<Utc>, DateTime<Utc>)>,
152}
153
154/// Index of all sessions for fast lookup
155#[derive(Debug, Clone, Serialize, Deserialize, Default)]
156pub struct SessionIndex {
157    pub sessions: HashMap<Uuid, SessionMetadata>,
158    pub recent_sessions: Vec<Uuid>,
159    pub favorite_sessions: Vec<Uuid>,
160    pub tag_index: HashMap<String, Vec<Uuid>>,
161    pub last_updated: DateTime<Utc>,
162    pub total_size_bytes: u64,
163}
164
165impl SessionIndex {
166    pub fn new() -> Self {
167        Self {
168            sessions: HashMap::new(),
169            recent_sessions: Vec::new(),
170            favorite_sessions: Vec::new(),
171            tag_index: HashMap::new(),
172            last_updated: Utc::now(),
173            total_size_bytes: 0,
174        }
175    }
176
177    pub fn add_session(&mut self, metadata: SessionMetadata) {
178        let id = metadata.id;
179
180        // Update favorites
181        if metadata.is_favorite && !self.favorite_sessions.contains(&id) {
182            self.favorite_sessions.push(id);
183        }
184
185        // Update tag index
186        for tag in &metadata.tags {
187            self.tag_index.entry(tag.clone()).or_default().push(id);
188        }
189
190        // Update recent sessions (keep last 20)
191        self.recent_sessions.retain(|&sid| sid != id);
192        self.recent_sessions.insert(0, id);
193        if self.recent_sessions.len() > 20 {
194            self.recent_sessions.truncate(20);
195        }
196
197        // Update total size
198        self.total_size_bytes += metadata.file_size;
199
200        // Insert metadata
201        self.sessions.insert(id, metadata);
202        self.last_updated = Utc::now();
203    }
204
205    pub fn remove_session(&mut self, id: &Uuid) -> Option<SessionMetadata> {
206        if let Some(metadata) = self.sessions.remove(id) {
207            // Update recent sessions
208            self.recent_sessions.retain(|sid| sid != id);
209
210            // Update favorites
211            self.favorite_sessions.retain(|sid| sid != id);
212
213            // Update tag index
214            for tag in &metadata.tags {
215                if let Some(sessions) = self.tag_index.get_mut(tag) {
216                    sessions.retain(|sid| sid != id);
217                    if sessions.is_empty() {
218                        self.tag_index.remove(tag);
219                    }
220                }
221            }
222
223            // Update total size
224            self.total_size_bytes -= metadata.file_size;
225            self.last_updated = Utc::now();
226
227            Some(metadata)
228        } else {
229            None
230        }
231    }
232
233    pub fn search(&self, query: &str) -> Vec<&SessionMetadata> {
234        let query_lower = query.to_lowercase();
235        self.sessions
236            .values()
237            .filter(|metadata| {
238                metadata.title.to_lowercase().contains(&query_lower)
239                    || metadata
240                        .tags
241                        .iter()
242                        .any(|tag| tag.to_lowercase().contains(&query_lower))
243            })
244            .collect()
245    }
246}