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}
199
200fn default_enrichment_status() -> String {
201 "raw".to_string()
202}
203
204fn default_supersede_mode() -> String {
205 "hide".to_string()
206}
207
208impl Default for RawDocument {
209 fn default() -> Self {
210 Self {
211 source: String::new(),
212 source_id: String::new(),
213 title: String::new(),
214 summary: None,
215 content: String::new(),
216 url: None,
217 last_modified: 0,
218 metadata: HashMap::new(),
219 memory_type: None,
220 space: None,
221 source_agent: None,
222 confidence: None,
223 confirmed: None,
224 stability: None,
225 supersedes: None,
226 pending_revision: false,
227 entity_id: None,
228 quality: None,
229 importance: None,
230 is_recap: false,
231 enrichment_status: "raw".to_string(),
232 supersede_mode: "hide".to_string(),
233 structured_fields: None,
234 retrieval_cue: None,
235 source_text: None,
236 }
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
242#[serde(rename_all = "lowercase")]
243pub enum SourceType {
244 Obsidian,
245 Directory,
246}
247
248impl SourceType {
249 pub fn as_str(&self) -> &'static str {
250 match self {
251 Self::Obsidian => "obsidian",
252 Self::Directory => "directory",
253 }
254 }
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
259pub enum SyncStatus {
260 Active,
261 Paused,
262 Error(String),
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct SourceStatus {
268 pub name: String,
269 pub connected: bool,
270 pub requires_auth: bool,
271 pub last_sync: Option<i64>,
272 pub document_count: u64,
273 pub error: Option<String>,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct Source {
279 pub id: String,
280 pub source_type: SourceType,
281 pub path: std::path::PathBuf,
282 #[serde(default = "default_sync_status")]
283 pub status: SyncStatus,
284 pub last_sync: Option<i64>,
285 #[serde(default)]
286 pub file_count: u64,
287 #[serde(default)]
288 pub memory_count: u64,
289 #[serde(default)]
291 pub last_sync_errors: u64,
292 #[serde(default)]
295 pub last_sync_error_detail: Option<String>,
296}
297
298fn default_sync_status() -> SyncStatus {
299 SyncStatus::Active
300}