1#![allow(missing_docs)]
14
15use serde::{Deserialize, Serialize};
16
17#[allow(clippy::trivially_copy_pass_by_ref)]
20fn is_false(b: &bool) -> bool {
21 !*b
22}
23
24#[derive(Clone, Debug, Default, Serialize)]
28pub struct EncodeInput {
29 pub text: String,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub session: Option<u64>,
32 #[serde(skip_serializing_if = "Option::is_none")]
33 pub occurred_at: Option<u64>,
34}
35
36#[derive(Clone, Debug, Deserialize)]
38pub struct EncodeResult {
39 pub memory_id: String,
40 pub was_deduplicated: bool,
41 pub salience: f32,
42 pub kind: u8,
43 pub created_at_unix_nanos: u64,
44 pub auto_edges_added: u32,
45}
46
47#[derive(Clone, Debug, Default, Serialize)]
51pub struct RecallInput {
52 pub query: String,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub max_results: Option<u32>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub subject: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
63 pub include_trace: Option<bool>,
64}
65
66#[derive(Clone, Debug, Deserialize)]
68pub struct MemoryHit {
69 pub memory_id: String,
70 pub text: String,
71 pub similarity_score: f32,
72 pub confidence: f32,
73 pub salience: f32,
74 pub kind: u8,
75 pub created_at_unix_nanos: u64,
76}
77
78#[derive(Clone, Debug, Deserialize)]
80pub struct RecallResult {
81 pub answer_kind: String,
82 pub memories: Vec<MemoryHit>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub trace: Option<String>,
89}
90
91#[derive(Clone, Debug, Serialize)]
95pub struct ForgetInput {
96 pub memory_id: String,
97 #[serde(skip_serializing_if = "is_false")]
98 pub hard: bool,
99}
100
101#[derive(Clone, Debug, Deserialize)]
103pub struct ForgetResult {
104 pub memory_id: String,
105 pub was_already_forgotten: bool,
106 pub edges_removed: u32,
107}
108
109#[derive(Clone, Debug, Serialize)]
113pub struct LinkInput {
114 pub source: String,
115 pub target: String,
116 pub kind: String,
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub weight: Option<f32>,
119}
120
121#[derive(Clone, Debug, Deserialize)]
123pub struct LinkResult {
124 pub source: String,
125 pub target: String,
126 pub kind: String,
127 pub weight: f32,
128 pub created_at_unix_nanos: u64,
129 pub already_existed: bool,
130}
131
132#[derive(Clone, Debug, Serialize)]
134pub struct UnlinkInput {
135 pub source: String,
136 pub target: String,
137 pub kind: String,
138}
139
140#[derive(Clone, Debug, Deserialize)]
142pub struct UnlinkResult {
143 pub source: String,
144 pub target: String,
145 pub kind: String,
146 pub removed: bool,
147}
148
149#[derive(Clone, Debug, Default, Serialize)]
153pub struct Endpoint {
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub text: Option<String>,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub memory_id: Option<String>,
158}
159
160impl Endpoint {
161 #[must_use]
163 pub fn text(text: impl Into<String>) -> Self {
164 Self {
165 text: Some(text.into()),
166 memory_id: None,
167 }
168 }
169
170 #[must_use]
172 pub fn memory(memory_id: impl Into<String>) -> Self {
173 Self {
174 text: None,
175 memory_id: Some(memory_id.into()),
176 }
177 }
178}
179
180#[derive(Clone, Debug, Default, Serialize)]
182pub struct PlanInput {
183 pub start: Endpoint,
184 pub goal: Endpoint,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub max_steps: Option<u32>,
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub max_wall_time_ms: Option<u32>,
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub max_branches: Option<u32>,
191 #[serde(skip_serializing_if = "Option::is_none")]
192 pub strategy: Option<String>,
193}
194
195#[derive(Clone, Debug, Deserialize)]
197pub struct PlanStep {
198 pub step_index: u32,
199 pub memory_id: String,
200 pub text: String,
201 pub transition_kind: String,
202 pub confidence: f32,
203 pub estimated_distance_to_goal: f32,
204}
205
206#[derive(Clone, Debug, Deserialize)]
208pub struct PlanResult {
209 pub steps: Vec<PlanStep>,
210}
211
212#[derive(Clone, Debug, Default, Serialize)]
214pub struct ReasonInput {
215 pub observation: Endpoint,
216 #[serde(skip_serializing_if = "Option::is_none")]
217 pub depth: Option<u32>,
218 #[serde(skip_serializing_if = "Option::is_none")]
219 pub confidence_threshold: Option<f32>,
220 #[serde(skip_serializing_if = "Option::is_none")]
221 pub max_inferences: Option<u32>,
222 #[serde(skip_serializing_if = "Option::is_none")]
223 pub budget_wall_time_ms: Option<u32>,
224}
225
226#[derive(Clone, Debug, Deserialize)]
228pub struct InferenceStep {
229 pub step_index: u32,
230 pub claim: String,
231 pub supporting_memories: Vec<String>,
232 pub contradicting_memories: Vec<String>,
233 pub confidence: f32,
234 pub inference_kind: String,
235}
236
237#[derive(Clone, Debug, Deserialize)]
239pub struct ReasonResult {
240 pub inferences: Vec<InferenceStep>,
241}
242
243#[derive(Clone, Debug, Deserialize)]
247pub struct Permissions {
248 pub can_encode: bool,
249 pub can_recall: bool,
250 pub can_plan: bool,
251 pub can_reason: bool,
252 pub can_forget: bool,
253 pub can_admin: bool,
254}
255
256#[derive(Clone, Debug, Deserialize)]
258pub struct Whoami {
259 pub namespace: String,
260 pub space_id: String,
261 pub permissions: Permissions,
262}
263
264#[derive(Clone, Debug, Deserialize)]
266pub struct Capabilities {
267 pub rerank: bool,
268 pub llm_extractor: bool,
269 pub classifier_extractor: bool,
270 pub pattern_extractor: bool,
271 pub schema_namespaces: Vec<String>,
272 pub vector_dim: u16,
273}
274
275#[derive(Clone, Debug, Default, Serialize)]
288pub struct MemoryListQuery {
289 #[serde(skip_serializing_if = "Option::is_none")]
290 pub limit: Option<u32>,
291 #[serde(skip_serializing_if = "Option::is_none")]
292 pub cursor: Option<String>,
293 #[serde(skip_serializing_if = "Option::is_none")]
294 pub dir: Option<String>,
295 #[serde(skip_serializing_if = "Option::is_none")]
296 pub include_tombstoned: Option<bool>,
297}
298
299#[derive(Clone, Debug, Deserialize)]
301pub struct MemoryListItem {
302 pub memory_id: String,
303 pub text: String,
304 pub kind: u8,
305 pub state: u8,
306 pub created_at_unix_nanos: u64,
307 pub occurred_at_unix_nanos: u64,
308 pub last_accessed_at_unix_nanos: u64,
309 pub salience: f32,
310 pub access_count: u32,
311 pub statement_count: u32,
312 pub entity_count: u32,
313 pub relation_count: u32,
314}
315
316#[derive(Clone, Debug, Deserialize)]
318pub struct MemoryListPage {
319 pub items: Vec<MemoryListItem>,
320 pub next_cursor: Option<String>,
321}
322
323#[derive(Clone, Debug, Deserialize)]
325pub struct StageRecord {
326 pub memory_id: String,
327 pub kind: u8,
328 pub salience: f32,
329 pub created_at_unix_nanos: u64,
330 pub occurred_at_unix_nanos: u64,
331 pub vector_dim: u32,
332 pub text_len: u32,
333 pub lsn: u64,
334}
335
336#[derive(Clone, Debug, Deserialize)]
338pub struct StageKeywordField {
339 pub field: String,
340 pub terms: Vec<String>,
341}
342
343#[derive(Clone, Debug, Deserialize)]
345pub struct StageGraphNode {
346 pub id: String,
347 pub name: String,
348 pub kind: String,
349 pub type_qname: String,
350}
351
352#[derive(Clone, Debug, Deserialize)]
354pub struct StageGraphEdge {
355 pub source: String,
356 pub target: String,
357 pub predicate: String,
358 pub kind: String,
359 pub confidence: f32,
360 pub event_at_unix_nanos: Option<u64>,
361}
362
363#[derive(Clone, Debug, Deserialize)]
365pub struct StageGraph {
366 pub nodes: Vec<StageGraphNode>,
367 pub edges: Vec<StageGraphEdge>,
368}
369
370#[derive(Clone, Debug, Deserialize)]
372pub struct StageArtifact {
373 pub vector: Vec<f32>,
374 pub record: Option<StageRecord>,
375 pub hype_questions: Vec<String>,
376 pub keyword_fields: Vec<StageKeywordField>,
377 pub graph: Option<StageGraph>,
378}
379
380#[derive(Clone, Debug, Deserialize)]
382pub struct MemoryInspect {
383 pub found: bool,
384 pub memory_id: String,
385 pub text: String,
386 pub artifact: StageArtifact,
387}
388
389#[derive(Clone, Debug, Default, Serialize)]
393pub struct ListEntitiesQuery {
394 #[serde(skip_serializing_if = "Option::is_none")]
395 pub type_id: Option<u32>,
396 #[serde(skip_serializing_if = "Option::is_none")]
397 pub prefix: Option<String>,
398 #[serde(skip_serializing_if = "Option::is_none")]
399 pub mention_count_min: Option<u32>,
400 #[serde(skip_serializing_if = "Option::is_none")]
401 pub include_tombstoned: Option<bool>,
402 #[serde(skip_serializing_if = "Option::is_none")]
403 pub include_merged: Option<bool>,
404 #[serde(skip_serializing_if = "Option::is_none")]
405 pub limit: Option<u32>,
406 #[serde(skip_serializing_if = "Option::is_none")]
409 pub cursor: Option<String>,
410}
411
412#[derive(Clone, Debug, Deserialize)]
414pub struct EntityDetail {
415 pub entity_id: String,
416 pub entity_type_id: u32,
417 pub canonical_name: String,
418 pub aliases: Vec<String>,
419 pub mention_count: u32,
420 pub created_at_unix_nanos: u64,
421 pub updated_at_unix_nanos: u64,
422 pub merged_into: Option<String>,
424}
425
426#[derive(Clone, Debug, Deserialize)]
428pub struct ListEntitiesResult {
429 pub entities: Vec<EntityDetail>,
430 pub count: usize,
431 #[serde(default, skip_serializing_if = "Option::is_none")]
435 pub next_cursor: Option<String>,
436}
437
438#[derive(Clone, Debug, Default, Serialize)]
440pub struct CreateEntityInput {
441 pub entity_type_id: u32,
442 pub canonical_name: String,
443 #[serde(skip_serializing_if = "Vec::is_empty")]
444 pub aliases: Vec<String>,
445}
446
447#[derive(Clone, Debug, Deserialize)]
449pub struct CreateEntityResult {
450 pub entity_id: String,
451}
452
453#[derive(Clone, Debug, Default, Serialize)]
455pub struct ResolveEntityInput {
456 pub candidate_name: String,
457 #[serde(skip_serializing_if = "String::is_empty")]
458 pub resolution_context: String,
459 #[serde(skip_serializing_if = "Option::is_none")]
461 pub type_hint: Option<u32>,
462 #[serde(skip_serializing_if = "is_false")]
463 pub allow_create: bool,
464}
465
466#[derive(Clone, Debug, Deserialize)]
469pub struct ResolveEntityResult {
470 pub outcome: String,
471 pub tier: u8,
472 pub confidence: f32,
473 pub entity_id: Option<String>,
474 pub candidate_ids: Vec<String>,
475}
476
477#[derive(Clone, Debug, Default, Serialize)]
479pub struct TraverseInput {
480 #[serde(skip_serializing_if = "String::is_empty")]
481 pub direction: String,
482 #[serde(skip_serializing_if = "Vec::is_empty")]
483 pub relation_types: Vec<String>,
484 #[serde(skip_serializing_if = "Option::is_none")]
485 pub max_depth: Option<u32>,
486 #[serde(skip_serializing_if = "Option::is_none")]
487 pub max_nodes: Option<u32>,
488 #[serde(skip_serializing_if = "is_false")]
489 pub include_superseded: bool,
490}
491
492#[derive(Clone, Debug, Deserialize)]
494pub struct TraversalStep {
495 pub relation_id: String,
496 pub from: String,
497 pub to: String,
498 pub relation_type: String,
499 pub depth: u32,
500 #[serde(default, skip_serializing_if = "Option::is_none")]
505 pub from_name: Option<String>,
506 #[serde(default, skip_serializing_if = "Option::is_none")]
508 pub to_name: Option<String>,
509}
510
511#[derive(Clone, Debug, Deserialize)]
513pub struct TraversalPath {
514 pub steps: Vec<TraversalStep>,
515}
516
517#[derive(Clone, Debug, Deserialize)]
519pub struct TraverseResult {
520 pub paths: Vec<TraversalPath>,
521 pub total_paths: u32,
522 pub truncated: bool,
523}
524
525#[derive(Clone, Debug, Default, Serialize)]
529pub struct ListRelationsQuery {
530 #[serde(skip_serializing_if = "Option::is_none")]
531 pub direction: Option<String>,
532 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
535 pub relation_type: Option<String>,
536 #[serde(skip_serializing_if = "Option::is_none")]
537 pub include_superseded: Option<bool>,
538 #[serde(skip_serializing_if = "Option::is_none")]
539 pub include_tombstoned: Option<bool>,
540 #[serde(skip_serializing_if = "Option::is_none")]
541 pub limit: Option<u32>,
542 #[serde(skip_serializing_if = "Option::is_none")]
545 pub cursor: Option<String>,
546}
547
548#[derive(Clone, Debug, Deserialize)]
550pub struct RelationDetail {
551 pub relation_id: String,
552 pub relation_type: String,
553 pub from_entity: String,
554 pub to_entity: String,
555 pub confidence: f32,
556 pub valid_from_unix_nanos: u64,
557 pub valid_to_unix_nanos: u64,
558 pub is_symmetric: bool,
559 pub tombstoned: bool,
560 #[serde(default, skip_serializing_if = "Option::is_none")]
565 pub from_name: Option<String>,
566 #[serde(default, skip_serializing_if = "Option::is_none")]
568 pub to_name: Option<String>,
569}
570
571#[derive(Clone, Debug, Deserialize)]
573pub struct ListRelationsResult {
574 pub relations: Vec<RelationDetail>,
575 pub count: usize,
576 #[serde(default, skip_serializing_if = "Option::is_none")]
580 pub next_cursor: Option<String>,
581}
582
583#[derive(Clone, Debug, Default, Serialize)]
587pub struct GetRelationQuery {
588 #[serde(skip_serializing_if = "Option::is_none")]
589 pub follow_supersession: Option<bool>,
590}
591
592#[derive(Clone, Debug, Default, Serialize)]
597pub struct ListStatementsQuery {
598 #[serde(skip_serializing_if = "Option::is_none")]
599 pub subject: Option<String>,
600 #[serde(skip_serializing_if = "Option::is_none")]
601 pub predicate: Option<String>,
602 #[serde(skip_serializing_if = "Option::is_none")]
603 pub kind: Option<String>,
604 #[serde(skip_serializing_if = "Option::is_none")]
605 pub min_confidence: Option<f32>,
606 #[serde(skip_serializing_if = "Option::is_none")]
607 pub only_current: Option<bool>,
608 #[serde(skip_serializing_if = "Option::is_none")]
609 pub include_tombstoned: Option<bool>,
610 #[serde(skip_serializing_if = "Option::is_none")]
611 pub limit: Option<u32>,
612 #[serde(skip_serializing_if = "Option::is_none")]
615 pub cursor: Option<String>,
616}
617
618fn de_f64<'de, D>(deserializer: D) -> Result<f64, D::Error>
628where
629 D: serde::Deserializer<'de>,
630{
631 let number = serde_json::Number::deserialize(deserializer)?;
632 number
633 .as_f64()
634 .ok_or_else(|| serde::de::Error::custom(format!("{number} is not an f64")))
635}
636
637#[derive(Clone, Debug, Deserialize)]
640#[serde(tag = "type", rename_all = "snake_case")]
641pub enum StatementValue {
642 Text {
643 value: String,
644 },
645 Integer {
646 value: i64,
647 },
648 Float {
649 #[serde(deserialize_with = "de_f64")]
650 value: f64,
651 },
652 Bool {
653 value: bool,
654 },
655 UnixNanos {
656 value: u64,
657 },
658 Blob {
659 value: Vec<u8>,
660 },
661}
662
663#[derive(Clone, Debug, Deserialize)]
666#[serde(tag = "kind", rename_all = "snake_case")]
667pub enum StatementObject {
668 Entity { id: String },
669 Value { value: StatementValue },
670 Memory { id: String },
671 Statement { id: String },
672}
673
674#[derive(Clone, Debug, Deserialize)]
676pub struct StatementDetail {
677 pub statement_id: String,
678 pub kind: String,
681 pub subject: String,
682 pub predicate: String,
683 pub object: StatementObject,
684 pub confidence: f32,
685 pub event_at_unix_nanos: u64,
686 pub valid_from_unix_nanos: u64,
687 pub valid_to_unix_nanos: u64,
688 pub tombstoned: bool,
689 #[serde(default, skip_serializing_if = "Option::is_none")]
694 pub subject_name: Option<String>,
695 #[serde(default, skip_serializing_if = "Option::is_none")]
697 pub object_name: Option<String>,
698}
699
700#[derive(Clone, Debug, Deserialize)]
702pub struct ListStatementsResult {
703 pub statements: Vec<StatementDetail>,
704 pub count: usize,
705 #[serde(default, skip_serializing_if = "Option::is_none")]
709 pub next_cursor: Option<String>,
710}
711
712#[derive(Clone, Debug, Default, Serialize)]
715pub struct GetStatementQuery {
716 #[serde(skip_serializing_if = "Option::is_none")]
717 pub follow_supersession: Option<bool>,
718}
719
720#[derive(Clone, Debug, Default, Serialize)]
724pub struct GraphFetchQuery {
725 #[serde(skip_serializing_if = "Option::is_none")]
726 pub limit: Option<u32>,
727 #[serde(skip_serializing_if = "Option::is_none")]
728 pub cursor: Option<String>,
729 #[serde(skip_serializing_if = "Option::is_none")]
730 pub include_statements: Option<bool>,
731 #[serde(skip_serializing_if = "Option::is_none")]
732 pub include_memories: Option<bool>,
733 #[serde(skip_serializing_if = "Option::is_none")]
734 pub include_memory_edges: Option<bool>,
735 #[serde(skip_serializing_if = "Option::is_none")]
736 pub include_tombstoned: Option<bool>,
737}
738
739#[derive(Clone, Debug, Deserialize)]
741pub struct GraphNode {
742 pub id: String,
743 pub kind: String,
744 pub label: String,
745 pub type_qname: String,
746}
747
748#[derive(Clone, Debug, Deserialize)]
750pub struct GraphEdge {
751 pub from_id: String,
752 pub to_id: String,
753 pub kind: String,
754 pub label: String,
755}
756
757#[derive(Clone, Debug, Deserialize)]
759pub struct GraphPage {
760 pub nodes: Vec<GraphNode>,
761 pub edges: Vec<GraphEdge>,
762 pub next_cursor: Option<String>,
763}
764
765#[derive(Clone, Debug, Default, Serialize)]
770pub struct SchemaGetQuery {
771 #[serde(skip_serializing_if = "Option::is_none")]
772 pub namespace: Option<String>,
773 #[serde(skip_serializing_if = "Option::is_none")]
774 pub version: Option<u32>,
775}
776
777#[derive(Clone, Debug, Deserialize)]
779pub struct Schema {
780 pub namespace: String,
781 pub schema_version: u32,
782 pub schema_document: String,
783 pub uploaded_at_unix_nanos: u64,
784 pub validator_version: u32,
785}
786
787#[derive(Clone, Debug, Deserialize)]
789pub struct SchemaError {
790 pub code: String,
791 pub message: String,
792 pub line: u32,
793 pub column: u32,
794 pub length: u32,
795 pub severity: u8,
796}
797
798#[derive(Clone, Debug, Default, Serialize)]
800pub struct SchemaUploadInput {
801 pub schema_document: String,
802 #[serde(skip_serializing_if = "is_false")]
803 pub dry_run: bool,
804 #[serde(skip_serializing_if = "is_false")]
805 pub allow_breaking: bool,
806}
807
808#[derive(Clone, Debug, Deserialize)]
810pub struct SchemaUploadResult {
811 pub namespace: String,
812 pub schema_version: u32,
813 pub backward_compatible: bool,
814 pub validation_errors: Vec<SchemaError>,
815}
816
817#[derive(Clone, Debug, Default, Serialize)]
819pub struct SchemaValidateInput {
820 pub schema_document: String,
821}
822
823#[derive(Clone, Debug, Deserialize)]
825pub struct SchemaValidateResult {
826 pub namespace: String,
827 pub would_be_version: u32,
828 pub validation_errors: Vec<SchemaError>,
829}
830
831#[derive(Clone, Debug, Serialize)]
838pub struct SchemaReplaceInput {
839 pub schema_document: String,
840 pub force_drop_existing: bool,
842}
843
844#[derive(Clone, Debug, Deserialize)]
847pub struct SchemaReplaceResult {
848 pub namespace: String,
849 pub schema_version: u32,
850 pub dropped_count: u32,
851 pub validation_errors: Vec<SchemaError>,
852}
853
854#[cfg(test)]
855mod recall_trace_tests {
856 use super::{RecallInput, RecallResult};
857
858 #[test]
862 fn include_trace_is_omitted_unless_set() {
863 let body = serde_json::to_string(&RecallInput {
864 query: "x".into(),
865 max_results: Some(5),
866 ..Default::default()
867 })
868 .expect("serialize");
869 assert!(!body.contains("include_trace"), "{body}");
870 }
871
872 #[test]
873 fn include_trace_is_sent_when_set() {
874 let body = serde_json::to_string(&RecallInput {
875 query: "x".into(),
876 include_trace: Some(true),
877 ..Default::default()
878 })
879 .expect("serialize");
880 assert!(body.contains(r#""include_trace":true"#), "{body}");
881 }
882
883 #[test]
888 fn trace_round_trips_as_an_opaque_string() {
889 let r: RecallResult = serde_json::from_str(
890 r#"{"answer_kind":"single","memories":[],"trace":"{\"stages\":[]}"}"#,
891 )
892 .expect("deserialize");
893 assert_eq!(r.trace.as_deref(), Some(r#"{"stages":[]}"#));
894 }
895
896 #[test]
899 fn a_missing_trace_is_none_not_an_error() {
900 let r: RecallResult =
901 serde_json::from_str(r#"{"answer_kind":"none","memories":[]}"#).expect("deserialize");
902 assert!(r.trace.is_none());
903 }
904}