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, skip_serializing_if = "Option::is_none")]
151 pub domain: 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)]
178 pub is_recap: bool,
179 #[serde(default = "default_enrichment_status")]
182 pub enrichment_status: String,
183 #[serde(default = "default_supersede_mode")]
185 pub supersede_mode: String,
186 #[serde(default, skip_serializing_if = "Option::is_none")]
188 pub structured_fields: Option<String>,
189 #[serde(default, skip_serializing_if = "Option::is_none")]
191 pub retrieval_cue: Option<String>,
192 #[serde(default, skip_serializing_if = "Option::is_none")]
194 pub source_text: Option<String>,
195}
196
197fn default_enrichment_status() -> String {
198 "raw".to_string()
199}
200
201fn default_supersede_mode() -> String {
202 "hide".to_string()
203}
204
205impl Default for RawDocument {
206 fn default() -> Self {
207 Self {
208 source: String::new(),
209 source_id: String::new(),
210 title: String::new(),
211 summary: None,
212 content: String::new(),
213 url: None,
214 last_modified: 0,
215 metadata: HashMap::new(),
216 memory_type: None,
217 domain: None,
218 source_agent: None,
219 confidence: None,
220 confirmed: None,
221 stability: None,
222 supersedes: None,
223 pending_revision: false,
224 entity_id: None,
225 quality: None,
226 is_recap: false,
227 enrichment_status: "raw".to_string(),
228 supersede_mode: "hide".to_string(),
229 structured_fields: None,
230 retrieval_cue: None,
231 source_text: None,
232 }
233 }
234}
235
236#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
238#[serde(rename_all = "lowercase")]
239pub enum SourceType {
240 Obsidian,
241 Directory,
242}
243
244impl SourceType {
245 pub fn as_str(&self) -> &'static str {
246 match self {
247 Self::Obsidian => "obsidian",
248 Self::Directory => "directory",
249 }
250 }
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
255pub enum SyncStatus {
256 Active,
257 Paused,
258 Error(String),
259}