1use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "lowercase")]
21pub enum MemoryType {
22 Identity,
23 Preference,
24 Decision,
25 Lesson,
26 Gotcha,
27 Fact,
28}
29
30impl MemoryType {
31 pub fn all_values() -> &'static [&'static str] {
33 &[
34 "identity",
35 "preference",
36 "decision",
37 "lesson",
38 "gotcha",
39 "fact",
40 ]
41 }
42
43 pub fn is_profile_alias(s: &str) -> bool {
46 s.eq_ignore_ascii_case("profile")
47 }
48
49 pub fn is_knowledge_alias(s: &str) -> bool {
52 s.eq_ignore_ascii_case("knowledge")
53 }
54}
55
56impl std::fmt::Display for MemoryType {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 let s = match self {
59 Self::Identity => "identity",
60 Self::Preference => "preference",
61 Self::Decision => "decision",
62 Self::Lesson => "lesson",
63 Self::Gotcha => "gotcha",
64 Self::Fact => "fact",
65 };
66 f.write_str(s)
67 }
68}
69
70impl std::str::FromStr for MemoryType {
71 type Err = String;
72 fn from_str(s: &str) -> Result<Self, Self::Err> {
73 match s.to_lowercase().as_str() {
74 "identity" => Ok(Self::Identity),
75 "preference" => Ok(Self::Preference),
76 "decision" => Ok(Self::Decision),
77 "lesson" => Ok(Self::Lesson),
78 "gotcha" => Ok(Self::Gotcha),
79 "fact" => Ok(Self::Fact),
80 "goal" => Ok(Self::Identity),
82 "profile" => Err(
84 "profile requires sub-classification into identity or preference -- use classify_memory_type".to_string()
85 ),
86 "knowledge" => Err(
88 "knowledge requires sub-classification into fact, lesson, or gotcha -- use classify_memory_type".to_string()
89 ),
90 "correction" | "custom" | "recap" => Ok(Self::Fact),
92 _ => Err(format!(
93 "invalid memory_type '{}', valid values: {}",
94 s,
95 Self::all_values().join(", ")
96 )),
97 }
98 }
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum StabilityTier {
104 Protected,
106 Standard,
108 Ephemeral,
110}
111
112pub fn stability_tier(memory_type: Option<&str>) -> StabilityTier {
114 match memory_type {
115 Some("identity") | Some("preference") => StabilityTier::Protected,
116 Some("fact") | Some("decision") | Some("lesson") | Some("gotcha") => {
117 StabilityTier::Standard
118 }
119 Some("goal") => StabilityTier::Protected,
121 _ => StabilityTier::Ephemeral,
122 }
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct RawDocument {
128 pub source: String,
130 pub source_id: String,
132 pub title: String,
134 pub summary: Option<String>,
136 pub content: String,
138 pub url: Option<String>,
140 pub last_modified: i64,
142 pub metadata: HashMap<String, String>,
144
145 #[serde(default, skip_serializing_if = "Option::is_none")]
148 pub memory_type: Option<String>,
149 #[serde(default, alias = "domain", skip_serializing_if = "Option::is_none")]
151 pub space: Option<String>,
152 #[serde(default, skip_serializing_if = "Option::is_none")]
154 pub source_agent: Option<String>,
155 #[serde(default, skip_serializing_if = "Option::is_none")]
157 pub confidence: Option<f32>,
158 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub confirmed: Option<bool>,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub stability: Option<String>,
164 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub supersedes: Option<String>,
167 #[serde(default)]
169 pub pending_revision: bool,
170 #[serde(default, skip_serializing_if = "Option::is_none")]
172 pub entity_id: Option<String>,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
175 pub quality: Option<String>,
176 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub importance: Option<u8>,
179 #[serde(default)]
181 pub is_recap: bool,
182 #[serde(default = "default_enrichment_status")]
185 pub enrichment_status: String,
186 #[serde(default = "default_supersede_mode")]
188 pub supersede_mode: String,
189 #[serde(default, skip_serializing_if = "Option::is_none")]
191 pub structured_fields: Option<String>,
192 #[serde(default, skip_serializing_if = "Option::is_none")]
194 pub retrieval_cue: Option<String>,
195 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub source_text: Option<String>,
198 #[serde(default, skip_serializing_if = "Option::is_none")]
201 pub content_hash: Option<String>,
202}
203
204fn default_enrichment_status() -> String {
205 "raw".to_string()
206}
207
208fn default_supersede_mode() -> String {
209 "hide".to_string()
210}
211
212impl Default for RawDocument {
213 fn default() -> Self {
214 Self {
215 source: String::new(),
216 source_id: String::new(),
217 title: String::new(),
218 summary: None,
219 content: String::new(),
220 url: None,
221 last_modified: 0,
222 metadata: HashMap::new(),
223 memory_type: None,
224 space: None,
225 source_agent: None,
226 confidence: None,
227 confirmed: None,
228 stability: None,
229 supersedes: None,
230 pending_revision: false,
231 entity_id: None,
232 quality: None,
233 importance: None,
234 is_recap: false,
235 enrichment_status: "raw".to_string(),
236 supersede_mode: "hide".to_string(),
237 structured_fields: None,
238 retrieval_cue: None,
239 source_text: None,
240 content_hash: None,
241 }
242 }
243}
244
245#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
247#[serde(rename_all = "lowercase")]
248pub enum SourceType {
249 Obsidian,
250 Directory,
251}
252
253impl SourceType {
254 pub fn as_str(&self) -> &'static str {
255 match self {
256 Self::Obsidian => "obsidian",
257 Self::Directory => "directory",
258 }
259 }
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize)]
264pub enum SyncStatus {
265 Active,
266 Paused,
267 Error(String),
268 Unavailable(String),
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct SourceStatus {
279 pub name: String,
280 pub connected: bool,
281 pub requires_auth: bool,
282 pub last_sync: Option<i64>,
283 pub document_count: u64,
284 pub error: Option<String>,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct Source {
290 pub id: String,
291 pub source_type: SourceType,
292 pub path: std::path::PathBuf,
293 #[serde(default = "default_sync_status")]
294 pub status: SyncStatus,
295 pub last_sync: Option<i64>,
296 #[serde(default)]
297 pub file_count: u64,
298 #[serde(default)]
299 pub memory_count: u64,
300 #[serde(default)]
302 pub last_sync_errors: u64,
303 #[serde(default)]
306 pub last_sync_error_detail: Option<String>,
307}
308
309fn default_sync_status() -> SyncStatus {
310 SyncStatus::Active
311}