kiromi_ai_memory/
graph.rs1use std::collections::BTreeSet;
12
13use serde::{Deserialize, Serialize};
14
15use crate::memory::MemoryRef;
16use crate::partition::PartitionPath;
17use crate::summary::{SummaryRef, SummarySubject};
18
19#[non_exhaustive]
25#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
26#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
27pub enum NodeRef {
28 Memory(MemoryRef),
30 Summary(SummaryRef),
32 Partition(PartitionPath),
35}
36
37#[non_exhaustive]
40#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
41#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
42pub enum EdgeKind {
43 Link,
45 SummaryInput {
49 from_subject: SummarySubject,
51 },
52 ParentPartition,
54 PartitionContains,
56}
57
58impl EdgeKind {
59 #[must_use]
61 pub fn tag(&self) -> EdgeKindTag {
62 match self {
63 EdgeKind::Link => EdgeKindTag::Link,
64 EdgeKind::SummaryInput { .. } => EdgeKindTag::SummaryInput,
65 EdgeKind::ParentPartition => EdgeKindTag::ParentPartition,
66 EdgeKind::PartitionContains => EdgeKindTag::PartitionContains,
67 }
68 }
69}
70
71#[non_exhaustive]
73#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
74#[serde(rename_all = "snake_case")]
75pub enum EdgeKindTag {
76 Link,
78 SummaryInput,
80 ParentPartition,
82 PartitionContains,
84}
85
86impl EdgeKindTag {
87 #[must_use]
89 pub fn all() -> BTreeSet<EdgeKindTag> {
90 let mut s = BTreeSet::new();
91 s.insert(EdgeKindTag::Link);
92 s.insert(EdgeKindTag::SummaryInput);
93 s.insert(EdgeKindTag::ParentPartition);
94 s.insert(EdgeKindTag::PartitionContains);
95 s
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101pub struct GraphEdge {
102 pub from: NodeRef,
104 pub to: NodeRef,
106 pub kind: EdgeKind,
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
113pub struct GraphNode {
114 pub r#ref: NodeRef,
116}
117
118#[derive(Debug, Clone, Default, Serialize, Deserialize)]
121pub struct Graph {
122 pub nodes: Vec<GraphNode>,
124 pub edges: Vec<GraphEdge>,
126}
127
128#[non_exhaustive]
130#[derive(Debug, Clone, Serialize, Deserialize)]
131#[serde(default)]
132pub struct TraverseOpts {
133 pub include_kinds: BTreeSet<EdgeKindTag>,
136 pub max_nodes: u32,
138 pub include_partition_edges: bool,
142}
143
144impl Default for TraverseOpts {
145 fn default() -> Self {
146 Self {
147 include_kinds: EdgeKindTag::all(),
148 max_nodes: 256,
149 include_partition_edges: true,
150 }
151 }
152}
153
154impl TraverseOpts {
155 #[must_use]
157 pub fn permits(&self, tag: EdgeKindTag) -> bool {
158 if !self.include_kinds.contains(&tag) {
159 return false;
160 }
161 if !self.include_partition_edges
162 && matches!(
163 tag,
164 EdgeKindTag::ParentPartition | EdgeKindTag::PartitionContains
165 )
166 {
167 return false;
168 }
169 true
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn default_opts_permit_everything() {
179 let o = TraverseOpts::default();
180 assert!(o.permits(EdgeKindTag::Link));
181 assert!(o.permits(EdgeKindTag::SummaryInput));
182 assert!(o.permits(EdgeKindTag::ParentPartition));
183 assert!(o.permits(EdgeKindTag::PartitionContains));
184 }
185
186 #[test]
187 fn include_partition_edges_off_blocks_partition_tags() {
188 let o = TraverseOpts {
189 include_partition_edges: false,
190 ..TraverseOpts::default()
191 };
192 assert!(o.permits(EdgeKindTag::Link));
193 assert!(!o.permits(EdgeKindTag::ParentPartition));
194 assert!(!o.permits(EdgeKindTag::PartitionContains));
195 }
196
197 #[test]
198 fn graph_serdes() {
199 let g = Graph::default();
200 let s = serde_json::to_string(&g).unwrap();
201 let _: Graph = serde_json::from_str(&s).unwrap();
202 }
203}