1use crate::entities::{Entity, EntitySearchResult};
5use crate::memory::{IndexedFileInfo, MemoryItem, MemoryStats, SearchResult};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Serialize, Deserialize)]
12pub struct StoreMemoryResponse {
13 pub source_id: String,
14 pub chunks_created: usize,
15 pub memory_type: String,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub entity_id: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub quality: Option<String>,
20 #[serde(default, skip_serializing_if = "Vec::is_empty")]
22 pub warnings: Vec<String>,
23 #[serde(default = "default_extraction_method")]
25 pub extraction_method: String,
26}
27
28fn default_extraction_method() -> String {
29 "unknown".to_string()
30}
31
32#[derive(Debug, Serialize, Deserialize)]
33pub struct SearchMemoryResponse {
34 pub results: Vec<SearchResult>,
35 pub took_ms: f64,
36}
37
38#[derive(Debug, Serialize, Deserialize)]
39pub struct ListMemoriesResponse {
40 pub memories: Vec<IndexedFileInfo>,
41}
42
43#[derive(Debug, Serialize, Deserialize)]
49pub struct DeleteResponse {
50 pub deleted: bool,
51}
52
53#[derive(Debug, Serialize, Deserialize)]
54pub struct ConfirmResponse {
55 pub confirmed: bool,
56}
57
58#[derive(Debug, Serialize, Deserialize)]
59pub struct ReclassifyMemoryResponse {
60 pub source_id: String,
61 pub memory_type: String,
62}
63
64#[derive(Debug, Serialize, Deserialize)]
65pub struct MemoryStatsResponse {
66 pub stats: MemoryStats,
67}
68
69#[derive(Debug, Serialize, Deserialize)]
70pub struct NurtureCardsResponse {
71 pub cards: Vec<MemoryItem>,
72}
73
74#[derive(Debug, Serialize, Deserialize)]
77pub struct HealthResponse {
78 pub status: String,
79 pub db_initialized: bool,
80 pub version: String,
81}
82
83#[derive(Debug, Serialize, Deserialize)]
84pub struct StatusResponse {
85 pub is_running: bool,
86 pub files_indexed: u64,
87 pub files_total: u64,
88 pub sources_connected: Vec<String>,
89}
90
91#[derive(Debug, Serialize, Deserialize)]
92pub struct SearchResponse {
93 pub results: Vec<SearchResult>,
94 pub took_ms: f64,
95}
96
97#[doc(hidden)]
98#[derive(Debug, Serialize, Deserialize)]
99pub struct ContextSuggestion {
100 pub content: String,
101 pub score: f32,
102 pub source: String,
103}
104
105#[doc(hidden)]
106#[derive(Debug, Serialize, Deserialize)]
107pub struct ContextResponse {
108 pub suggestions: Vec<ContextSuggestion>,
109 pub took_ms: f64,
110}
111
112#[derive(Debug, Default, Serialize, Deserialize)]
113pub struct TierTokenEstimates {
114 pub tier1_identity: usize,
115 pub tier2_project: usize,
116 pub tier3_relevant: usize,
117 pub total: usize,
118}
119
120#[derive(Debug, Serialize, Deserialize)]
121pub struct ProfileContext {
122 pub narrative: String,
123 pub identity: Vec<String>,
124 pub preferences: Vec<String>,
125 pub goals: Vec<String>,
126}
127
128#[derive(Debug, Serialize, Deserialize)]
129pub struct KnowledgeContext {
130 #[serde(default, skip_serializing_if = "Vec::is_empty")]
131 pub concepts: Vec<String>,
132 #[serde(default, skip_serializing_if = "Vec::is_empty")]
133 pub decisions: Vec<String>,
134 #[serde(default)]
135 pub relevant_memories: Vec<SearchResult>,
136 #[serde(default, skip_serializing_if = "Vec::is_empty")]
137 pub graph_context: Vec<String>,
138}
139
140#[derive(Debug, Serialize, Deserialize)]
141pub struct ChatContextResponse {
142 pub context: String,
143 pub profile: ProfileContext,
144 pub knowledge: KnowledgeContext,
145 pub took_ms: f64,
146 pub token_estimates: TierTokenEstimates,
147}
148
149#[derive(Debug, Serialize, Deserialize)]
152pub struct ProfileResponse {
153 pub id: String,
154 pub name: String,
155 pub display_name: Option<String>,
156 pub email: Option<String>,
157 pub bio: Option<String>,
158 pub avatar_path: Option<String>,
159 pub created_at: i64,
160 pub updated_at: i64,
161}
162
163#[derive(Debug, Serialize, Deserialize)]
164pub struct AgentResponse {
165 pub id: String,
166 pub name: String,
167 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub display_name: Option<String>,
169 pub agent_type: String,
170 pub description: Option<String>,
171 pub enabled: bool,
172 pub trust_level: String,
173 pub last_seen_at: Option<i64>,
174 pub memory_count: i64,
175 pub created_at: i64,
176 pub updated_at: i64,
177}
178
179#[derive(Debug, Serialize, Deserialize)]
182pub struct CreateEntityResponse {
183 pub id: String,
184}
185
186#[doc(hidden)]
187#[derive(Debug, Serialize, Deserialize)]
188pub struct CreateRelationResponse {
189 pub id: String,
190}
191
192#[derive(Debug, Serialize, Deserialize)]
193pub struct AddObservationResponse {
194 pub id: String,
195}
196
197#[derive(Debug, Serialize, Deserialize)]
198pub struct ListEntitiesResponse {
199 pub entities: Vec<Entity>,
200}
201
202#[derive(Debug, Serialize, Deserialize)]
203pub struct SearchEntitiesResponse {
204 pub results: Vec<EntitySearchResult>,
205}
206
207#[derive(Debug, Serialize, Deserialize)]
210pub struct ImportMemoriesResponse {
211 pub imported: usize,
212 pub skipped: usize,
213 pub breakdown: HashMap<String, usize>,
214 pub entities_created: usize,
215 pub observations_added: usize,
216 pub relations_created: usize,
217 pub batch_id: String,
218}
219
220#[doc(hidden)]
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
225pub enum Nudge {
226 Silent,
227 Ambient,
228 Notable,
229 Wow,
230}
231
232#[doc(hidden)]
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct PhaseResult {
236 pub name: String,
237 pub duration_ms: u64,
238 pub items_processed: usize,
239 pub error: Option<String>,
240 pub nudge: Nudge,
241 pub headline: Option<String>,
242}
243
244#[doc(hidden)]
245#[derive(Debug, Serialize, Deserialize)]
246pub struct SteepResponse {
247 pub memories_decayed: u64,
248 pub recaps_generated: u32,
249 pub distilled: u32,
250 pub pending_remaining: u32,
251 pub phases: Vec<PhaseResult>,
252}
253
254#[derive(Debug, Serialize, Deserialize)]
257pub struct ConfigResponse {
258 pub skip_apps: Vec<String>,
259 pub skip_title_patterns: Vec<String>,
260 pub private_browsing_detection: bool,
261 pub setup_completed: bool,
262 pub clipboard_enabled: bool,
263 pub screen_capture_enabled: bool,
264 pub selection_capture_enabled: bool,
265 pub remote_access_enabled: bool,
266}
267
268#[derive(Debug, Serialize, Deserialize)]
271pub struct IndexedFilesResponse {
272 pub files: Vec<IndexedFileInfo>,
273}
274
275#[derive(Debug, Serialize, Deserialize)]
276pub struct DeleteCountResponse {
277 pub deleted: usize,
278}
279
280#[derive(Debug, Serialize, Deserialize)]
283pub struct SuccessResponse {
284 pub ok: bool,
285}
286
287#[derive(Debug, Serialize, Deserialize)]
290pub struct MemoryDetailResponse {
291 pub memory: Option<MemoryItem>,
292}
293
294#[derive(Debug, Serialize, Deserialize)]
295pub struct VersionChainResponse {
296 pub versions: Vec<crate::memory::MemoryVersionItem>,
297}
298
299#[derive(Debug, Serialize, Deserialize)]
302pub struct TagsResponse {
303 pub tags: Vec<String>,
304}
305
306#[derive(Debug, Serialize, Deserialize)]
309pub struct ActivityResponse {
310 pub activities: Vec<crate::memory::AgentActivityRow>,
311}
312
313#[derive(Debug, Serialize, Deserialize)]
316pub struct DecisionsResponse {
317 pub decisions: Vec<MemoryItem>,
318}
319
320#[derive(Debug, Serialize, Deserialize)]
321pub struct DecisionDomainsResponse {
322 pub domains: Vec<String>,
323}
324
325#[derive(Debug, Serialize, Deserialize)]
328pub struct PinnedMemoriesResponse {
329 pub memories: Vec<MemoryItem>,
330}
331
332#[derive(Debug, Serialize, Deserialize)]
335pub struct IngestResponse {
336 pub chunks_created: usize,
337 pub document_id: String,
338}
339
340#[derive(Debug, Deserialize, Serialize)]
346pub struct ExportConceptResponse {
347 pub path: String,
348}
349
350#[derive(Debug, Deserialize, Serialize)]
353pub struct KnowledgePathResponse {
354 pub path: String,
355}
356
357#[derive(Debug, Deserialize, Serialize)]
358pub struct KnowledgeCountResponse {
359 pub count: u64,
360}
361
362#[doc(hidden)]
365#[derive(Debug, Clone, Serialize, Deserialize)]
366pub struct SyncStatsResponse {
367 pub files_found: usize,
368 pub ingested: usize,
369 pub skipped: usize,
370 pub errors: usize,
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn store_memory_response_deserializes_without_extraction_method() {
379 let json = r#"{
381 "source_id": "mem_abc",
382 "chunks_created": 3,
383 "memory_type": "fact"
384 }"#;
385 let parsed: StoreMemoryResponse = serde_json::from_str(json).unwrap();
386 assert_eq!(parsed.source_id, "mem_abc");
387 assert_eq!(parsed.chunks_created, 3);
388 assert_eq!(parsed.memory_type, "fact");
389 assert_eq!(parsed.extraction_method, "unknown");
390 assert!(parsed.warnings.is_empty());
391 }
392
393 #[test]
394 fn store_memory_response_deserializes_with_all_fields() {
395 let json = r#"{
396 "source_id": "mem_abc",
397 "chunks_created": 3,
398 "memory_type": "fact",
399 "warnings": ["decision memory missing claim"],
400 "extraction_method": "llm"
401 }"#;
402 let parsed: StoreMemoryResponse = serde_json::from_str(json).unwrap();
403 assert_eq!(parsed.warnings.len(), 1);
404 assert_eq!(parsed.extraction_method, "llm");
405 }
406
407 #[test]
408 fn chat_context_response_roundtrips_with_empty_knowledge_sections() {
409 let response = ChatContextResponse {
410 context: "context".into(),
411 profile: ProfileContext {
412 narrative: "n".into(),
413 identity: vec![],
414 preferences: vec![],
415 goals: vec![],
416 },
417 knowledge: KnowledgeContext {
418 concepts: vec![],
419 decisions: vec![],
420 relevant_memories: vec![],
421 graph_context: vec![],
422 },
423 took_ms: 1.0,
424 token_estimates: TierTokenEstimates {
425 tier1_identity: 1,
426 tier2_project: 2,
427 tier3_relevant: 3,
428 total: 6,
429 },
430 };
431
432 let json = serde_json::to_string(&response).unwrap();
433 let parsed: ChatContextResponse = serde_json::from_str(&json).unwrap();
434 assert!(parsed.knowledge.concepts.is_empty());
435 assert!(parsed.knowledge.decisions.is_empty());
436 assert!(parsed.knowledge.relevant_memories.is_empty());
437 assert!(parsed.knowledge.graph_context.is_empty());
438 }
439}