Skip to main content

origin_types/
requests.rs

1// SPDX-License-Identifier: Apache-2.0
2//! API request types for all HTTP endpoints.
3
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7// ===== Memory CRUD =====
8
9#[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    /// Entity name for resolution (e.g. "Alice", "PostgreSQL")
25    #[serde(default)]
26    pub entity: Option<String>,
27    /// Direct entity ID (bypasses name resolution)
28    #[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}
48
49#[derive(Debug, Serialize, Deserialize)]
50pub struct ListMemoriesRequest {
51    #[serde(default)]
52    pub memory_type: Option<String>,
53    #[serde(default, alias = "domain")]
54    pub space: Option<String>,
55    #[serde(default = "default_list_limit")]
56    pub limit: usize,
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub confirmed: Option<bool>,
59}
60
61#[derive(Debug, Serialize, Deserialize)]
62pub struct ConfirmRequest {
63    #[serde(default = "default_confirmed")]
64    pub confirmed: bool,
65}
66
67#[derive(Debug, Serialize, Deserialize)]
68pub struct ReclassifyMemoryRequest {
69    pub memory_type: String,
70}
71
72#[derive(Debug, Serialize, Deserialize)]
73pub struct ImportMemoriesRequest {
74    pub source: String,
75    pub content: String,
76    #[serde(default)]
77    pub label: Option<String>,
78}
79
80// ===== General search/context =====
81
82#[derive(Debug, Serialize, Deserialize)]
83pub struct SearchRequest {
84    pub query: String,
85    #[serde(default = "default_limit")]
86    pub limit: usize,
87    pub source_filter: Option<String>,
88    #[serde(default, alias = "domain")]
89    pub space: Option<String>,
90}
91
92#[doc(hidden)]
93#[derive(Debug, Serialize, Deserialize)]
94pub struct ContextRequest {
95    pub current_file: String,
96    pub cursor_prefix: String,
97    #[serde(default = "default_limit")]
98    pub limit: usize,
99}
100
101#[derive(Debug, Serialize, Deserialize)]
102pub struct ChatContextRequest {
103    #[serde(default)]
104    pub query: Option<String>,
105    #[serde(default)]
106    pub conversation_id: Option<String>,
107    #[serde(default = "default_max_chunks")]
108    pub max_chunks: usize,
109    #[serde(default)]
110    pub relevance_threshold: Option<f64>,
111    /// Deprecated: goal-typed memories were folded into identity by migration 45
112    /// (Phase 0). Daemon ignores this field — no goal-load path exists anymore.
113    /// Field stays for wire backward compat; will be removed in 0.4.
114    #[deprecated(
115        since = "0.3.2",
116        note = "Goal taxonomy folded into Identity by migration 45 (Phase 0). \
117                Daemon ignores this field. Will be removed in 0.4."
118    )]
119    #[serde(default = "default_true")]
120    pub include_goals: bool,
121    #[serde(default, alias = "domain")]
122    pub space: Option<String>,
123}
124
125// ===== Knowledge graph =====
126
127#[derive(Debug, Serialize, Deserialize)]
128pub struct CreateEntityRequest {
129    pub name: String,
130    pub entity_type: String,
131    #[serde(default, alias = "domain")]
132    pub space: Option<String>,
133    #[serde(default)]
134    pub source_agent: Option<String>,
135    #[serde(default)]
136    pub confidence: Option<f32>,
137}
138
139#[doc(hidden)]
140#[derive(Debug, Serialize, Deserialize)]
141pub struct CreateRelationRequest {
142    pub from_entity: String,
143    pub to_entity: String,
144    pub relation_type: String,
145    #[serde(default)]
146    pub source_agent: Option<String>,
147    #[serde(default)]
148    pub confidence: Option<f64>,
149    #[serde(default)]
150    pub explanation: Option<String>,
151    #[serde(default)]
152    pub source_memory_id: Option<String>,
153}
154
155#[derive(Debug, Serialize, Deserialize)]
156pub struct AddObservationRequest {
157    pub entity_id: String,
158    pub content: String,
159    #[serde(default)]
160    pub source_agent: Option<String>,
161    #[serde(default)]
162    pub confidence: Option<f32>,
163}
164
165#[doc(hidden)]
166#[derive(Debug, Serialize, Deserialize)]
167pub struct LinkEntityRequest {
168    pub source_id: String,
169    pub entity_id: String,
170}
171
172#[derive(Debug, Serialize, Deserialize)]
173pub struct ListEntitiesRequest {
174    #[serde(default)]
175    pub entity_type: Option<String>,
176    #[serde(default, alias = "domain")]
177    pub space: Option<String>,
178}
179
180#[derive(Debug, Serialize, Deserialize)]
181pub struct SearchEntitiesRequest {
182    pub query: String,
183    #[serde(default = "default_entity_search_limit")]
184    pub limit: usize,
185}
186
187// ===== Profile & Agents =====
188
189#[derive(Debug, Serialize, Deserialize)]
190pub struct UpdateProfileRequest {
191    #[serde(default)]
192    pub name: Option<String>,
193    #[serde(default)]
194    pub display_name: Option<String>,
195    #[serde(default)]
196    pub email: Option<String>,
197    #[serde(default)]
198    pub bio: Option<String>,
199    #[serde(default)]
200    pub avatar_path: Option<String>,
201}
202
203#[derive(Debug, Serialize, Deserialize)]
204pub struct UpdateAgentRequest {
205    #[serde(default)]
206    pub agent_type: Option<String>,
207    #[serde(default)]
208    pub description: Option<String>,
209    #[serde(default)]
210    pub enabled: Option<bool>,
211    #[serde(default)]
212    pub trust_level: Option<String>,
213    /// Empty string clears the field; None leaves it unchanged.
214    #[serde(default)]
215    pub display_name: Option<String>,
216}
217
218// ===== Spaces =====
219
220#[derive(Debug, Serialize, Deserialize)]
221pub struct CreateSpaceRequest {
222    pub name: String,
223    pub description: Option<String>,
224}
225
226#[derive(Debug, Serialize, Deserialize)]
227pub struct UpdateSpaceRequest {
228    pub new_name: Option<String>,
229    pub description: Option<String>,
230}
231
232// ===== Concepts =====
233
234#[doc(hidden)]
235#[derive(Debug, Serialize, Deserialize)]
236pub struct CreateConceptRequest {
237    pub title: String,
238    pub content: String,
239    #[serde(default)]
240    pub summary: Option<String>,
241    #[serde(default)]
242    pub entity_id: Option<String>,
243    #[serde(default, alias = "domain")]
244    pub space: Option<String>,
245    #[serde(default)]
246    pub source_memory_ids: Vec<String>,
247}
248
249#[derive(Debug, Serialize, Deserialize)]
250pub struct SearchPagesRequest {
251    pub query: String,
252    #[serde(default)]
253    pub limit: Option<usize>,
254    #[serde(default, skip_serializing_if = "Option::is_none")]
255    pub page_type: Option<String>,
256}
257
258// ===== Ingest =====
259
260#[derive(Debug, Serialize, Deserialize)]
261pub struct IngestTextRequest {
262    pub source: String,
263    pub source_id: String,
264    pub title: String,
265    pub content: String,
266    pub url: Option<String>,
267    pub metadata: Option<HashMap<String, String>>,
268}
269
270#[doc(hidden)]
271#[derive(Debug, Serialize, Deserialize)]
272pub struct IngestWebpageRequest {
273    pub url: String,
274    pub title: String,
275    pub content: String,
276    pub metadata: Option<HashMap<String, String>>,
277}
278
279#[derive(Debug, Serialize, Deserialize)]
280pub struct IngestMemoryRequest {
281    pub source: String,
282    pub source_id: String,
283    pub title: String,
284    pub content: String,
285    pub url: Option<String>,
286    pub tags: Option<Vec<String>>,
287    pub metadata: Option<HashMap<String, String>>,
288}
289
290// ===== Sources =====
291
292#[doc(hidden)]
293#[derive(Debug, Serialize, Deserialize)]
294pub struct AddSourceRequest {
295    pub source_type: String,
296    /// Filesystem path as a string. Kept as `String` (not `PathBuf`) because
297    /// this is an HTTP wire format — the handler converts it to `PathBuf`.
298    pub path: String,
299}
300
301// ===== Config =====
302
303#[derive(Debug, Serialize, Deserialize)]
304pub struct UpdateConfigRequest {
305    #[serde(default)]
306    pub skip_apps: Option<Vec<String>>,
307    #[serde(default)]
308    pub skip_title_patterns: Option<Vec<String>>,
309    #[serde(default)]
310    pub private_browsing_detection: Option<bool>,
311    #[serde(default)]
312    pub setup_completed: Option<bool>,
313    #[serde(default)]
314    pub clipboard_enabled: Option<bool>,
315    #[serde(default)]
316    pub screen_capture_enabled: Option<bool>,
317    #[serde(default)]
318    pub remote_access_enabled: Option<bool>,
319    /// Anthropic model used for fast/routine tasks (e.g. classification, tagging).
320    #[serde(default)]
321    pub routine_model: Option<String>,
322    /// Anthropic model used for synthesis tasks (e.g. distillation, narrative).
323    #[serde(default)]
324    pub synthesis_model: Option<String>,
325    /// Base URL for an OpenAI-compatible external LLM endpoint.
326    #[serde(default)]
327    pub external_llm_endpoint: Option<String>,
328    /// Model identifier to use with the external LLM endpoint.
329    #[serde(default)]
330    pub external_llm_model: Option<String>,
331}
332
333// ===== Chunks / indexed files =====
334
335#[derive(Debug, Serialize, Deserialize)]
336pub struct DeleteByTimeRangeRequest {
337    pub start: i64,
338    pub end: i64,
339}
340
341#[derive(Debug, Serialize, Deserialize)]
342pub struct BulkDeleteItem {
343    pub source: String,
344    pub source_id: String,
345}
346
347#[derive(Debug, Serialize, Deserialize)]
348pub struct BulkDeleteRequest {
349    pub items: Vec<BulkDeleteItem>,
350}
351
352#[derive(Debug, Serialize, Deserialize)]
353pub struct UpdateChunkRequest {
354    pub content: String,
355}
356
357// ===== Entity / Observation CRUD =====
358
359#[derive(Debug, Serialize, Deserialize)]
360pub struct ConfirmEntityRequest {
361    #[serde(default = "default_confirmed")]
362    pub confirmed: bool,
363}
364
365#[derive(Debug, Serialize, Deserialize)]
366pub struct AddEntityObservationRequest {
367    pub content: String,
368    #[serde(default)]
369    pub source_agent: Option<String>,
370    #[serde(default)]
371    pub confidence: Option<f32>,
372}
373
374#[derive(Debug, Serialize, Deserialize)]
375pub struct UpdateObservationRequest {
376    pub content: String,
377}
378
379#[derive(Debug, Serialize, Deserialize)]
380pub struct ConfirmObservationRequest {
381    #[serde(default = "default_confirmed")]
382    pub confirmed: bool,
383}
384
385// ===== Spaces extended =====
386
387#[derive(Debug, Serialize, Deserialize)]
388pub struct ReorderSpaceRequest {
389    pub name: String,
390    pub new_order: i64,
391}
392
393#[derive(Debug, Serialize, Deserialize)]
394pub struct SetDocumentSpaceRequest {
395    pub space_name: String,
396}
397
398// ===== Tags =====
399
400#[derive(Debug, Serialize, Deserialize)]
401pub struct SetDocumentTagsRequest {
402    pub tags: Vec<String>,
403}
404
405// ===== Memory update =====
406
407#[derive(Debug, Serialize, Deserialize)]
408pub struct UpdateMemoryRequest {
409    #[serde(default)]
410    pub content: Option<String>,
411    #[serde(default, alias = "domain")]
412    pub space: Option<String>,
413    #[serde(default)]
414    pub confirmed: Option<bool>,
415    #[serde(default)]
416    pub memory_type: Option<String>,
417}
418
419#[derive(Debug, Serialize, Deserialize)]
420pub struct SetStabilityRequest {
421    pub stability: String,
422}
423
424#[derive(Debug, Serialize, Deserialize)]
425pub struct CorrectMemoryRequest {
426    pub correction_prompt: String,
427}
428
429// ===== Concepts update =====
430
431#[derive(Debug, Serialize, Deserialize)]
432pub struct UpdatePageRequest {
433    pub content: String,
434    /// Source memory IDs to associate with this page version.
435    /// Omitted or empty by HTTP callers that preserve existing sources;
436    /// always populated by `post_write::update_page`.
437    #[serde(default)]
438    pub source_memory_ids: Vec<String>,
439}
440
441/// Body for `PUT /api/pages/{id}` — agent-side refresh of a stale page.
442///
443/// Distinct from `UpdatePageRequest` (manual content edit via POST) because:
444///  - `source_memory_ids` is replaced, not preserved.
445///  - `summary` is optionally updated.
446///  - The handler clears `stale_reason` in the same transaction.
447///
448/// v1 deliberately excludes title / entity_id / space changes — slug rename
449/// has its own concurrent-read failure mode and lands as a separate route.
450#[derive(Debug, Serialize, Deserialize)]
451pub struct RefreshPageRequest {
452    pub content: String,
453    pub source_memory_ids: Vec<String>,
454    #[serde(default)]
455    pub summary: Option<String>,
456}
457
458// ===== Concept Export =====
459
460/// Request body for `POST /api/pages/export` (bulk export all pages to an Obsidian vault).
461#[derive(Debug, Deserialize, Serialize)]
462pub struct ExportPagesRequest {
463    pub vault_path: Option<String>,
464}
465
466#[derive(Debug, Deserialize, Serialize)]
467pub struct ExportPageRequest {
468    pub vault_path: String,
469}
470
471// ===== LLM test =====
472
473/// `POST /api/llm/test` — probe an OpenAI-compatible LLM endpoint with a 1-shot prompt.
474/// Used by the app settings UI to validate a custom endpoint before saving.
475#[derive(Debug, Clone, Serialize, Deserialize)]
476pub struct TestLlmRequest {
477    pub endpoint: String,
478    pub model: String,
479    /// Optional override prompt. Defaults to "Say 'hello' and nothing else." server-side.
480    #[serde(default, skip_serializing_if = "Option::is_none")]
481    pub prompt: Option<String>,
482}
483
484#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct TestLlmResponse {
486    pub response: String,
487}
488
489// ===== Default value functions =====
490
491fn default_limit() -> usize {
492    10
493}
494
495fn default_list_limit() -> usize {
496    100
497}
498
499fn default_confirmed() -> bool {
500    true
501}
502
503fn default_max_chunks() -> usize {
504    5
505}
506
507fn default_true() -> bool {
508    true
509}
510
511fn default_entity_search_limit() -> usize {
512    20
513}
514
515#[cfg(test)]
516mod search_pages_page_type_test {
517    use super::*;
518
519    #[test]
520    fn search_pages_request_accepts_page_type() {
521        let json = r#"{"query":"foo","limit":10,"page_type":"recap"}"#;
522        let parsed: SearchPagesRequest = serde_json::from_str(json).unwrap();
523        assert_eq!(parsed.page_type.as_deref(), Some("recap"));
524    }
525
526    #[test]
527    fn search_pages_request_page_type_optional() {
528        let json = r#"{"query":"foo","limit":10}"#;
529        let parsed: SearchPagesRequest = serde_json::from_str(json).unwrap();
530        assert!(parsed.page_type.is_none());
531    }
532}
533
534#[cfg(test)]
535mod list_memories_confirmed_test {
536    use super::*;
537
538    #[test]
539    fn confirmed_false_serializes_to_json() {
540        let req = ListMemoriesRequest {
541            memory_type: None,
542            space: None,
543            limit: 20,
544            confirmed: Some(false),
545        };
546        let s = serde_json::to_string(&req).unwrap();
547        assert!(s.contains("\"confirmed\":false"), "got: {s}");
548    }
549
550    #[test]
551    fn confirmed_none_skips_serialization() {
552        let req = ListMemoriesRequest {
553            memory_type: None,
554            space: None,
555            limit: 20,
556            confirmed: None,
557        };
558        let s = serde_json::to_string(&req).unwrap();
559        assert!(!s.contains("confirmed"), "got: {s}");
560    }
561}