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