1use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Serialize, Deserialize)]
10pub struct StoreMemoryRequest {
11 pub content: String,
12 #[serde(default)]
13 pub memory_type: Option<String>,
14 #[serde(default, alias = "domain")]
15 pub space: Option<String>,
16 #[serde(default)]
17 pub source_agent: Option<String>,
18 #[serde(default)]
19 pub title: Option<String>,
20 #[serde(default)]
21 pub confidence: Option<f32>,
22 #[serde(default)]
23 pub supersedes: Option<String>,
24 #[serde(default)]
26 pub entity: Option<String>,
27 #[serde(default)]
29 pub entity_id: Option<String>,
30 #[serde(default)]
31 pub structured_fields: Option<serde_json::Value>,
32 #[serde(default)]
33 pub retrieval_cue: Option<String>,
34}
35
36#[derive(Debug, Serialize, Deserialize)]
37pub struct SearchMemoryRequest {
38 pub query: String,
39 #[serde(default = "default_limit")]
40 pub limit: usize,
41 #[serde(default)]
42 pub memory_type: Option<String>,
43 #[serde(default, alias = "domain")]
44 pub space: Option<String>,
45 #[serde(default)]
46 pub source_agent: Option<String>,
47 #[serde(default)]
54 pub rerank: bool,
55}
56
57#[derive(Debug, Serialize, Deserialize)]
58pub struct ListMemoriesRequest {
59 #[serde(default)]
60 pub memory_type: Option<String>,
61 #[serde(default, alias = "domain")]
62 pub space: Option<String>,
63 #[serde(default = "default_list_limit")]
64 pub limit: usize,
65 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub confirmed: Option<bool>,
67}
68
69#[derive(Debug, Serialize, Deserialize)]
70pub struct ConfirmRequest {
71 #[serde(default = "default_confirmed")]
72 pub confirmed: bool,
73}
74
75#[derive(Debug, Serialize, Deserialize)]
76pub struct ReclassifyMemoryRequest {
77 pub memory_type: String,
78}
79
80#[derive(Debug, Serialize, Deserialize)]
81pub struct ImportMemoriesRequest {
82 pub source: String,
83 pub content: String,
84 #[serde(default)]
85 pub label: Option<String>,
86}
87
88#[derive(Debug, Serialize, Deserialize)]
91pub struct SearchRequest {
92 pub query: String,
93 #[serde(default = "default_limit")]
94 pub limit: usize,
95 pub source_filter: Option<String>,
96 #[serde(default, alias = "domain")]
97 pub space: Option<String>,
98}
99
100#[doc(hidden)]
101#[derive(Debug, Serialize, Deserialize)]
102pub struct ContextRequest {
103 pub current_file: String,
104 pub cursor_prefix: String,
105 #[serde(default = "default_limit")]
106 pub limit: usize,
107}
108
109#[derive(Debug, Serialize, Deserialize)]
110pub struct ChatContextRequest {
111 #[serde(default)]
112 pub query: Option<String>,
113 #[serde(default)]
114 pub conversation_id: Option<String>,
115 #[serde(default = "default_max_chunks")]
116 pub max_chunks: usize,
117 #[serde(default)]
118 pub relevance_threshold: Option<f64>,
119 #[deprecated(
123 since = "0.3.2",
124 note = "Goal taxonomy folded into Identity by migration 45 (Phase 0). \
125 Daemon ignores this field. Will be removed in 0.4."
126 )]
127 #[serde(default = "default_true")]
128 pub include_goals: bool,
129 #[serde(default, alias = "domain")]
130 pub space: Option<String>,
131}
132
133#[derive(Debug, Serialize, Deserialize)]
136pub struct CreateEntityRequest {
137 pub name: String,
138 pub entity_type: String,
139 #[serde(default, alias = "domain")]
140 pub space: Option<String>,
141 #[serde(default)]
142 pub source_agent: Option<String>,
143 #[serde(default)]
144 pub confidence: Option<f32>,
145}
146
147#[doc(hidden)]
148#[derive(Debug, Serialize, Deserialize)]
149pub struct CreateRelationRequest {
150 pub from_entity: String,
151 pub to_entity: String,
152 pub relation_type: String,
153 #[serde(default)]
154 pub source_agent: Option<String>,
155 #[serde(default)]
156 pub confidence: Option<f64>,
157 #[serde(default)]
158 pub explanation: Option<String>,
159 #[serde(default)]
160 pub source_memory_id: Option<String>,
161}
162
163#[derive(Debug, Serialize, Deserialize)]
164pub struct AddObservationRequest {
165 pub entity_id: String,
166 pub content: String,
167 #[serde(default)]
168 pub source_agent: Option<String>,
169 #[serde(default)]
170 pub confidence: Option<f32>,
171}
172
173#[doc(hidden)]
174#[derive(Debug, Serialize, Deserialize)]
175pub struct LinkEntityRequest {
176 pub source_id: String,
177 pub entity_id: String,
178}
179
180#[derive(Debug, Serialize, Deserialize)]
181pub struct ListEntitiesRequest {
182 #[serde(default)]
183 pub entity_type: Option<String>,
184 #[serde(default, alias = "domain")]
185 pub space: Option<String>,
186}
187
188#[derive(Debug, Serialize, Deserialize)]
189pub struct SearchEntitiesRequest {
190 pub query: String,
191 #[serde(default = "default_entity_search_limit")]
192 pub limit: usize,
193}
194
195#[derive(Debug, Serialize, Deserialize)]
198pub struct UpdateProfileRequest {
199 #[serde(default)]
200 pub name: Option<String>,
201 #[serde(default)]
202 pub display_name: Option<String>,
203 #[serde(default)]
204 pub email: Option<String>,
205 #[serde(default)]
206 pub bio: Option<String>,
207 #[serde(default)]
208 pub avatar_path: Option<String>,
209}
210
211#[derive(Debug, Serialize, Deserialize)]
212pub struct UpdateAgentRequest {
213 #[serde(default)]
214 pub agent_type: Option<String>,
215 #[serde(default)]
216 pub description: Option<String>,
217 #[serde(default)]
218 pub enabled: Option<bool>,
219 #[serde(default)]
220 pub trust_level: Option<String>,
221 #[serde(default)]
223 pub display_name: Option<String>,
224}
225
226#[derive(Debug, Serialize, Deserialize)]
229pub struct CreateSpaceRequest {
230 pub name: String,
231 pub description: Option<String>,
232}
233
234#[derive(Debug, Serialize, Deserialize)]
235pub struct UpdateSpaceRequest {
236 pub new_name: Option<String>,
237 pub description: Option<String>,
238}
239
240#[doc(hidden)]
243#[derive(Debug, Serialize, Deserialize)]
244pub struct CreateConceptRequest {
245 pub title: String,
246 pub content: String,
247 #[serde(default)]
248 pub summary: Option<String>,
249 #[serde(default)]
250 pub entity_id: Option<String>,
251 #[serde(default, alias = "domain")]
252 pub space: Option<String>,
253 #[serde(default)]
254 pub source_memory_ids: Vec<String>,
255 #[serde(default)]
256 pub creation_kind: Option<String>,
257 #[serde(default)]
260 pub workspace: Option<String>,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq)]
269pub struct CreatePageDraftRequest {
270 pub draft_id: String,
272 pub title: String,
273 pub content: String,
274 pub space: Option<String>,
275 space_provided: bool,
276}
277
278impl CreatePageDraftRequest {
279 pub fn new(draft_id: String, title: String, content: String, space: Option<String>) -> Self {
283 Self {
284 draft_id,
285 title,
286 content,
287 space,
288 space_provided: true,
289 }
290 }
291
292 pub fn new_inheriting_header_space(draft_id: String, title: String, content: String) -> Self {
295 Self {
296 draft_id,
297 title,
298 content,
299 space: None,
300 space_provided: false,
301 }
302 }
303
304 pub fn space_was_provided(&self) -> bool {
309 self.space_provided
310 }
311}
312
313impl Serialize for CreatePageDraftRequest {
314 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
315 where
316 S: serde::Serializer,
317 {
318 use serde::ser::SerializeStruct;
319
320 let field_count = if self.space_provided { 4 } else { 3 };
321 let mut state = serializer.serialize_struct("CreatePageDraftRequest", field_count)?;
322 state.serialize_field("draft_id", &self.draft_id)?;
323 state.serialize_field("title", &self.title)?;
324 state.serialize_field("content", &self.content)?;
325 if self.space_provided {
326 state.serialize_field("space", &self.space)?;
327 }
328 state.end()
329 }
330}
331
332impl<'de> Deserialize<'de> for CreatePageDraftRequest {
333 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
334 where
335 D: serde::Deserializer<'de>,
336 {
337 #[derive(Deserialize)]
338 struct Wire {
339 draft_id: String,
340 #[serde(default)]
341 title: String,
342 #[serde(default)]
343 content: String,
344 #[serde(default, deserialize_with = "double_option")]
345 space: Option<Option<String>>,
346 }
347
348 let wire = Wire::deserialize(deserializer)?;
349 let (space_provided, space) = match wire.space {
350 Some(space) => (true, space),
351 None => (false, None),
352 };
353 Ok(Self {
354 draft_id: wire.draft_id,
355 title: wire.title,
356 content: wire.content,
357 space,
358 space_provided,
359 })
360 }
361}
362
363#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
365pub struct UpdatePageDraftRequest {
366 pub expected_version: i64,
367 pub title: String,
368 pub content: String,
369 pub space: Option<String>,
370}
371
372impl<'de> Deserialize<'de> for UpdatePageDraftRequest {
373 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
374 where
375 D: serde::Deserializer<'de>,
376 {
377 #[derive(Deserialize)]
378 struct Wire {
379 expected_version: i64,
380 title: String,
381 content: String,
382 #[serde(default, deserialize_with = "double_option")]
383 space: Option<Option<String>>,
384 }
385
386 let wire = Wire::deserialize(deserializer)?;
387 let space = wire
388 .space
389 .ok_or_else(|| serde::de::Error::missing_field("space"))?;
390 Ok(Self {
391 expected_version: wire.expected_version,
392 title: wire.title,
393 content: wire.content,
394 space,
395 })
396 }
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
401pub struct PageDraftVersionRequest {
402 pub expected_version: i64,
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
406#[serde(tag = "action", rename_all = "snake_case")]
407pub enum AcceptRefinementRequest {
408 Accept {
409 #[serde(default, skip_serializing_if = "Option::is_none")]
410 notes: Option<String>,
411 },
412 PickSpace {
413 space: String,
414 #[serde(default, skip_serializing_if = "Option::is_none")]
415 notes: Option<String>,
416 },
417}
418
419#[derive(Debug, Serialize, Deserialize)]
420pub struct SearchPagesRequest {
421 pub query: String,
422 #[serde(default)]
423 pub limit: Option<usize>,
424 #[serde(default, skip_serializing_if = "Option::is_none")]
425 pub page_type: Option<String>,
426 #[serde(default, skip_serializing_if = "Option::is_none")]
427 pub space: Option<String>,
428}
429
430#[derive(Debug, Serialize, Deserialize)]
433pub struct IngestTextRequest {
434 pub source: String,
435 pub source_id: String,
436 pub title: String,
437 pub content: String,
438 pub url: Option<String>,
439 pub metadata: Option<HashMap<String, String>>,
440}
441
442#[doc(hidden)]
443#[derive(Debug, Serialize, Deserialize)]
444pub struct IngestWebpageRequest {
445 pub url: String,
446 pub title: String,
447 pub content: String,
448 pub metadata: Option<HashMap<String, String>>,
449}
450
451#[derive(Debug, Serialize, Deserialize)]
452pub struct IngestMemoryRequest {
453 pub source: String,
454 pub source_id: String,
455 pub title: String,
456 pub content: String,
457 pub url: Option<String>,
458 pub tags: Option<Vec<String>>,
459 pub metadata: Option<HashMap<String, String>>,
460}
461
462#[doc(hidden)]
465#[derive(Debug, Serialize, Deserialize)]
466pub struct AddSourceRequest {
467 pub source_type: String,
468 pub path: String,
471}
472
473fn double_option<'de, T, D>(de: D) -> Result<Option<Option<T>>, D::Error>
478where
479 T: serde::Deserialize<'de>,
480 D: serde::Deserializer<'de>,
481{
482 serde::Deserialize::deserialize(de).map(Some)
483}
484
485#[derive(Debug, Serialize, Deserialize)]
486pub struct UpdateConfigRequest {
487 #[serde(default)]
488 pub skip_apps: Option<Vec<String>>,
489 #[serde(default)]
490 pub skip_title_patterns: Option<Vec<String>>,
491 #[serde(default)]
492 pub private_browsing_detection: Option<bool>,
493 #[serde(default)]
494 pub setup_completed: Option<bool>,
495 #[serde(default)]
496 pub clipboard_enabled: Option<bool>,
497 #[serde(default)]
498 pub screen_capture_enabled: Option<bool>,
499 #[serde(default)]
500 pub remote_access_enabled: Option<bool>,
501 #[serde(default)]
503 pub routine_model: Option<String>,
504 #[serde(default)]
506 pub synthesis_model: Option<String>,
507 #[serde(default)]
509 pub external_llm_endpoint: Option<String>,
510 #[serde(default)]
512 pub external_llm_model: Option<String>,
513 #[serde(
516 default,
517 deserialize_with = "double_option",
518 skip_serializing_if = "Option::is_none"
519 )]
520 pub external_llm_api_key: Option<Option<String>>,
521 #[serde(default)]
525 pub everyday_source: Option<String>,
526 #[serde(default)]
530 pub synthesis_source: Option<String>,
531 #[serde(default)]
534 pub page_map_auto_suggest: Option<bool>,
535}
536
537#[derive(Debug, Serialize, Deserialize)]
540pub struct DeleteByTimeRangeRequest {
541 pub start: i64,
542 pub end: i64,
543}
544
545#[derive(Debug, Serialize, Deserialize)]
546pub struct BulkDeleteItem {
547 pub source: String,
548 pub source_id: String,
549}
550
551#[derive(Debug, Serialize, Deserialize)]
552pub struct BulkDeleteRequest {
553 pub items: Vec<BulkDeleteItem>,
554}
555
556#[derive(Debug, Serialize, Deserialize)]
557pub struct UpdateChunkRequest {
558 pub content: String,
559}
560
561#[derive(Debug, Serialize, Deserialize)]
564pub struct ConfirmEntityRequest {
565 #[serde(default = "default_confirmed")]
566 pub confirmed: bool,
567}
568
569#[derive(Debug, Serialize, Deserialize)]
570pub struct AddEntityObservationRequest {
571 pub content: String,
572 #[serde(default)]
573 pub source_agent: Option<String>,
574 #[serde(default)]
575 pub confidence: Option<f32>,
576}
577
578#[derive(Debug, Serialize, Deserialize)]
579pub struct UpdateObservationRequest {
580 pub content: String,
581}
582
583#[derive(Debug, Serialize, Deserialize)]
584pub struct ConfirmObservationRequest {
585 #[serde(default = "default_confirmed")]
586 pub confirmed: bool,
587}
588
589#[derive(Debug, Serialize, Deserialize)]
592pub struct ReorderSpaceRequest {
593 pub name: String,
594 pub new_order: i64,
595}
596
597#[derive(Debug, Serialize, Deserialize)]
598pub struct SetDocumentSpaceRequest {
599 pub space_name: String,
600}
601
602#[derive(Debug, Serialize, Deserialize)]
605pub struct SetDocumentTagsRequest {
606 #[serde(default)]
607 pub source: Option<String>,
608 pub tags: Vec<String>,
609}
610
611#[derive(Debug, Serialize, Deserialize)]
614pub struct UpdateMemoryRequest {
615 #[serde(default)]
616 pub content: Option<String>,
617 #[serde(default, alias = "domain")]
618 pub space: Option<String>,
619 #[serde(default)]
620 pub confirmed: Option<bool>,
621 #[serde(default)]
622 pub memory_type: Option<String>,
623}
624
625#[derive(Debug, Serialize, Deserialize)]
626pub struct SetStabilityRequest {
627 pub stability: String,
628}
629
630#[derive(Debug, Serialize, Deserialize)]
631pub struct CorrectMemoryRequest {
632 pub correction_prompt: String,
633}
634
635#[derive(Debug, Serialize, Deserialize)]
638pub struct UpdatePageRequest {
639 pub content: String,
640 #[serde(default)]
644 pub source_memory_ids: Vec<String>,
645 #[serde(default)]
653 pub expected_version: Option<i64>,
654 #[serde(default)]
663 pub caller_id: Option<String>,
664 #[serde(default)]
665 pub operation_id: Option<String>,
666}
667
668#[derive(Debug, Serialize, Deserialize)]
678pub struct RefreshPageRequest {
679 pub content: String,
680 pub source_memory_ids: Vec<String>,
681 #[serde(default)]
682 pub summary: Option<String>,
683}
684
685#[derive(Debug, Deserialize, Serialize)]
689pub struct ExportPagesRequest {
690 pub vault_path: Option<String>,
691}
692
693#[derive(Debug, Deserialize, Serialize)]
694pub struct ExportPageRequest {
695 pub vault_path: String,
696}
697
698#[derive(Debug, Clone, Serialize, Deserialize)]
703pub struct TestLlmRequest {
704 pub endpoint: String,
705 pub model: String,
706 #[serde(default, skip_serializing_if = "Option::is_none")]
708 pub prompt: Option<String>,
709 #[serde(default, skip_serializing_if = "Option::is_none")]
711 pub api_key: Option<String>,
712}
713
714#[derive(Debug, Clone, Serialize, Deserialize)]
715pub struct TestLlmResponse {
716 pub response: String,
717}
718
719#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
723pub struct OnDeviceModelRequest {
724 pub model_id: String,
725}
726
727fn default_limit() -> usize {
730 10
731}
732
733fn default_list_limit() -> usize {
734 100
735}
736
737fn default_confirmed() -> bool {
738 true
739}
740
741fn default_max_chunks() -> usize {
742 5
743}
744
745fn default_true() -> bool {
746 true
747}
748
749fn default_entity_search_limit() -> usize {
750 20
751}
752
753#[cfg(test)]
754mod search_pages_page_type_test {
755 use super::*;
756
757 #[test]
758 fn search_pages_request_accepts_page_type() {
759 let json = r#"{"query":"foo","limit":10,"page_type":"recap"}"#;
760 let parsed: SearchPagesRequest = serde_json::from_str(json).unwrap();
761 assert_eq!(parsed.page_type.as_deref(), Some("recap"));
762 }
763
764 #[test]
765 fn search_pages_request_page_type_optional() {
766 let json = r#"{"query":"foo","limit":10}"#;
767 let parsed: SearchPagesRequest = serde_json::from_str(json).unwrap();
768 assert!(parsed.page_type.is_none());
769 }
770
771 #[test]
772 fn search_pages_request_accepts_optional_space() {
773 let json = r#"{"query":"foo","space":"work"}"#;
774 let parsed: SearchPagesRequest = serde_json::from_str(json).unwrap();
775 assert_eq!(parsed.space.as_deref(), Some("work"));
776
777 let omitted: SearchPagesRequest = serde_json::from_str(r#"{"query":"foo"}"#).unwrap();
778 assert!(omitted.space.is_none());
779 }
780}
781
782#[cfg(test)]
783mod list_memories_confirmed_test {
784 use super::*;
785
786 #[test]
787 fn confirmed_false_serializes_to_json() {
788 let req = ListMemoriesRequest {
789 memory_type: None,
790 space: None,
791 limit: 20,
792 confirmed: Some(false),
793 };
794 let s = serde_json::to_string(&req).unwrap();
795 assert!(s.contains("\"confirmed\":false"), "got: {s}");
796 }
797
798 #[test]
799 fn confirmed_none_skips_serialization() {
800 let req = ListMemoriesRequest {
801 memory_type: None,
802 space: None,
803 limit: 20,
804 confirmed: None,
805 };
806 let s = serde_json::to_string(&req).unwrap();
807 assert!(!s.contains("confirmed"), "got: {s}");
808 }
809}
810
811#[cfg(test)]
812mod set_document_tags_test {
813 use super::*;
814
815 #[test]
816 fn set_document_tags_request_accepts_optional_source() {
817 let req: SetDocumentTagsRequest =
818 serde_json::from_str(r#"{"source":"manual","tags":["rust"]}"#).unwrap();
819
820 assert_eq!(req.source.as_deref(), Some("manual"));
821 assert_eq!(req.tags, vec!["rust"]);
822 }
823
824 #[test]
825 fn set_document_tags_request_defaults_source_for_old_payloads() {
826 let req: SetDocumentTagsRequest = serde_json::from_str(r#"{"tags":["rust"]}"#).unwrap();
827
828 assert!(req.source.is_none());
829 assert_eq!(req.tags, vec!["rust"]);
830 }
831}
832
833#[cfg(test)]
834mod on_device_model_request_test {
835 use super::*;
836
837 #[test]
838 fn on_device_model_request_round_trips_model_id() {
839 let req: OnDeviceModelRequest = serde_json::from_str(r#"{"model_id":"qwen3-4b"}"#).unwrap();
840
841 assert_eq!(req.model_id, "qwen3-4b");
842 assert_eq!(
843 serde_json::to_string(&req).unwrap(),
844 r#"{"model_id":"qwen3-4b"}"#
845 );
846 }
847}
848
849#[cfg(test)]
850mod tests {
851 use super::*;
852
853 #[test]
854 fn update_config_request_external_key_tristate() {
855 let r: UpdateConfigRequest = serde_json::from_str("{}").unwrap();
856 assert_eq!(r.external_llm_api_key, None); let r: UpdateConfigRequest =
858 serde_json::from_str(r#"{"external_llm_api_key":null}"#).unwrap();
859 assert_eq!(r.external_llm_api_key, Some(None)); let r: UpdateConfigRequest =
861 serde_json::from_str(r#"{"external_llm_api_key":"sk-x"}"#).unwrap();
862 assert_eq!(r.external_llm_api_key, Some(Some("sk-x".to_string())));
863 }
864
865 #[test]
866 fn test_llm_request_api_key_optional() {
867 let r: TestLlmRequest =
868 serde_json::from_str(r#"{"endpoint":"http://x","model":"m"}"#).unwrap();
869 assert!(r.api_key.is_none());
870 }
871}