1use std::time::SystemTime;
4
5use bytes::Bytes;
6use serde::{Deserialize, Serialize};
7use serde_json::{Map, Value};
8use uuid::Uuid;
9
10use crate::embeddings::EmbeddingVector;
11use crate::{MemoryError, MemoryResult};
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub enum MemoryChannel {
17 Input,
19 Output,
21 Tool,
23 System,
25 Custom(String),
27}
28
29impl MemoryChannel {
30 pub fn custom(label: impl Into<String>) -> MemoryResult<Self> {
36 let value = label.into();
37 if value.trim().is_empty() {
38 return Err(MemoryError::InvalidRecord(
39 "custom memory channel label must not be empty",
40 ));
41 }
42 Ok(Self::Custom(value))
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct MemoryRecord {
49 id: Uuid,
50 timestamp: SystemTime,
51 channel: MemoryChannel,
52 payload: Bytes,
53 #[serde(default)]
54 tags: Vec<String>,
55 #[serde(default)]
56 metadata: Map<String, Value>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 embedding: Option<EmbeddingVector>,
59}
60
61impl MemoryRecord {
62 #[must_use]
64 pub fn builder(channel: MemoryChannel, payload: Bytes) -> MemoryRecordBuilder {
65 MemoryRecordBuilder {
66 id: Uuid::new_v4(),
67 timestamp: SystemTime::now(),
68 channel,
69 payload,
70 tags: Vec::new(),
71 metadata: Map::new(),
72 embedding: None,
73 }
74 }
75
76 #[must_use]
78 pub fn id(&self) -> Uuid {
79 self.id
80 }
81
82 #[must_use]
84 pub fn timestamp(&self) -> SystemTime {
85 self.timestamp
86 }
87
88 #[must_use]
90 pub fn channel(&self) -> &MemoryChannel {
91 &self.channel
92 }
93
94 #[must_use]
96 pub fn payload(&self) -> &Bytes {
97 &self.payload
98 }
99
100 #[must_use]
102 pub fn tags(&self) -> &[String] {
103 &self.tags
104 }
105
106 #[must_use]
108 pub fn metadata(&self) -> &Map<String, Value> {
109 &self.metadata
110 }
111
112 #[must_use]
114 pub fn embedding(&self) -> Option<&EmbeddingVector> {
115 self.embedding.as_ref()
116 }
117}
118
119#[derive(Debug)]
121pub struct MemoryRecordBuilder {
122 id: Uuid,
123 timestamp: SystemTime,
124 channel: MemoryChannel,
125 payload: Bytes,
126 tags: Vec<String>,
127 metadata: Map<String, Value>,
128 embedding: Option<EmbeddingVector>,
129}
130
131impl MemoryRecordBuilder {
132 #[must_use]
134 pub fn id(mut self, id: Uuid) -> Self {
135 self.id = id;
136 self
137 }
138
139 #[must_use]
141 pub fn timestamp(mut self, timestamp: SystemTime) -> Self {
142 self.timestamp = timestamp;
143 self
144 }
145
146 pub fn tag(mut self, tag: impl Into<String>) -> MemoryResult<Self> {
152 let value = tag.into();
153 if value.trim().is_empty() {
154 return Err(MemoryError::InvalidRecord("memory tags must not be empty"));
155 }
156 self.tags.push(value);
157 Ok(self)
158 }
159
160 pub fn tags<I, S>(mut self, tags: I) -> MemoryResult<Self>
166 where
167 I: IntoIterator<Item = S>,
168 S: Into<String>,
169 {
170 for tag in tags {
171 self = self.tag(tag)?;
172 }
173 Ok(self)
174 }
175
176 #[must_use]
178 pub fn metadata(mut self, key: impl Into<String>, value: Value) -> Self {
179 self.metadata.insert(key.into(), value);
180 self
181 }
182
183 #[must_use]
185 pub fn merge_metadata(mut self, map: Map<String, Value>) -> Self {
186 self.metadata.extend(map);
187 self
188 }
189
190 #[must_use]
192 pub fn embedding(mut self, embedding: EmbeddingVector) -> Self {
193 self.embedding = Some(embedding);
194 self
195 }
196
197 pub fn build(self) -> MemoryResult<MemoryRecord> {
203 Ok(MemoryRecord {
204 id: self.id,
205 timestamp: self.timestamp,
206 channel: self.channel,
207 payload: self.payload,
208 tags: self.tags,
209 metadata: self.metadata,
210 embedding: self.embedding,
211 })
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn builder_rejects_empty_tags() {
221 let payload = Bytes::from_static(b"payload");
222 let err = MemoryRecord::builder(MemoryChannel::Input, payload.clone())
223 .tag("")
224 .expect_err("empty tag should fail");
225 assert!(matches!(err, MemoryError::InvalidRecord(_)));
226
227 let err = MemoryRecord::builder(MemoryChannel::Input, payload)
228 .tags(vec!["ok", " "])
229 .expect_err("whitespace tag should fail");
230 assert!(matches!(err, MemoryError::InvalidRecord(_)));
231 }
232
233 #[test]
234 fn builder_constructs_record() {
235 let payload = Bytes::from_static(b"payload");
236 let record = MemoryRecord::builder(MemoryChannel::Output, payload.clone())
237 .tag("mxp")
238 .unwrap()
239 .metadata("key", Value::from("value"))
240 .build()
241 .unwrap();
242
243 assert_eq!(record.payload(), &payload);
244 assert_eq!(record.tags(), ["mxp"]);
245 assert_eq!(record.metadata().get("key").unwrap(), "value");
246 }
247}