1use std::fmt;
6use uuid::Uuid;
7
8use crate::constants::{CORE_MEMORY_BLOCK_LABEL_BYTES_MAX, CORE_MEMORY_BLOCK_SIZE_BYTES_MAX};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub struct MemoryBlockId(Uuid);
13
14impl MemoryBlockId {
15 #[must_use]
17 pub fn new() -> Self {
18 Self(Uuid::new_v4())
19 }
20
21 #[must_use]
23 pub fn from_uuid(uuid: Uuid) -> Self {
24 Self(uuid)
25 }
26
27 #[must_use]
29 pub fn as_uuid(&self) -> &Uuid {
30 &self.0
31 }
32}
33
34impl Default for MemoryBlockId {
35 fn default() -> Self {
36 Self::new()
37 }
38}
39
40impl fmt::Display for MemoryBlockId {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 write!(f, "{}", self.0)
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
59pub enum MemoryBlockType {
60 System,
62 Persona,
64 Human,
66 Facts,
68 Goals,
70 Scratch,
72}
73
74impl MemoryBlockType {
75 #[must_use]
77 pub fn as_str(&self) -> &'static str {
78 match self {
79 Self::System => "system",
80 Self::Persona => "persona",
81 Self::Human => "human",
82 Self::Facts => "facts",
83 Self::Goals => "goals",
84 Self::Scratch => "scratch",
85 }
86 }
87
88 #[must_use]
92 pub fn priority(&self) -> u8 {
93 match self {
94 Self::System => 0,
95 Self::Persona => 1,
96 Self::Human => 2,
97 Self::Facts => 3,
98 Self::Goals => 4,
99 Self::Scratch => 5,
100 }
101 }
102
103 #[must_use]
105 pub fn all_ordered() -> &'static [MemoryBlockType] {
106 &[
107 Self::System,
108 Self::Persona,
109 Self::Human,
110 Self::Facts,
111 Self::Goals,
112 Self::Scratch,
113 ]
114 }
115}
116
117impl fmt::Display for MemoryBlockType {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 write!(f, "{}", self.as_str())
120 }
121}
122
123#[derive(Debug, Clone)]
127pub struct MemoryBlock {
128 id: MemoryBlockId,
130 block_type: MemoryBlockType,
132 label: Option<String>,
134 content: String,
136 size_bytes: usize,
138 created_at_ms: u64,
140 modified_at_ms: u64,
142}
143
144impl MemoryBlock {
145 #[must_use]
151 pub fn new(block_type: MemoryBlockType, content: impl Into<String>, now_ms: u64) -> Self {
152 let content = content.into();
153
154 assert!(
156 content.len() <= CORE_MEMORY_BLOCK_SIZE_BYTES_MAX,
157 "block content {} bytes exceeds max {}",
158 content.len(),
159 CORE_MEMORY_BLOCK_SIZE_BYTES_MAX
160 );
161
162 let size_bytes = content.len();
163 let id = MemoryBlockId::new();
164
165 let result = Self {
167 id,
168 block_type,
169 label: None,
170 content,
171 size_bytes,
172 created_at_ms: now_ms,
173 modified_at_ms: now_ms,
174 };
175
176 assert_eq!(
177 result.size_bytes,
178 result.content.len(),
179 "size must match content"
180 );
181
182 result
183 }
184
185 #[must_use]
191 pub fn with_label(
192 block_type: MemoryBlockType,
193 label: impl Into<String>,
194 content: impl Into<String>,
195 now_ms: u64,
196 ) -> Self {
197 let label = label.into();
198 let content = content.into();
199
200 assert!(
202 label.len() <= CORE_MEMORY_BLOCK_LABEL_BYTES_MAX,
203 "block label {} bytes exceeds max {}",
204 label.len(),
205 CORE_MEMORY_BLOCK_LABEL_BYTES_MAX
206 );
207 assert!(
208 content.len() <= CORE_MEMORY_BLOCK_SIZE_BYTES_MAX,
209 "block content {} bytes exceeds max {}",
210 content.len(),
211 CORE_MEMORY_BLOCK_SIZE_BYTES_MAX
212 );
213
214 let size_bytes = content.len();
215 let id = MemoryBlockId::new();
216
217 Self {
218 id,
219 block_type,
220 label: Some(label),
221 content,
222 size_bytes,
223 created_at_ms: now_ms,
224 modified_at_ms: now_ms,
225 }
226 }
227
228 #[must_use]
230 pub fn id(&self) -> MemoryBlockId {
231 self.id
232 }
233
234 #[must_use]
236 pub fn block_type(&self) -> MemoryBlockType {
237 self.block_type
238 }
239
240 #[must_use]
242 pub fn label(&self) -> Option<&str> {
243 self.label.as_deref()
244 }
245
246 #[must_use]
248 pub fn content(&self) -> &str {
249 &self.content
250 }
251
252 #[must_use]
254 pub fn size_bytes(&self) -> usize {
255 self.size_bytes
256 }
257
258 #[must_use]
260 pub fn created_at_ms(&self) -> u64 {
261 self.created_at_ms
262 }
263
264 #[must_use]
266 pub fn modified_at_ms(&self) -> u64 {
267 self.modified_at_ms
268 }
269
270 pub fn set_content(&mut self, content: impl Into<String>, now_ms: u64) {
275 let content = content.into();
276
277 assert!(
279 content.len() <= CORE_MEMORY_BLOCK_SIZE_BYTES_MAX,
280 "block content {} bytes exceeds max {}",
281 content.len(),
282 CORE_MEMORY_BLOCK_SIZE_BYTES_MAX
283 );
284
285 self.size_bytes = content.len();
286 self.content = content;
287 self.modified_at_ms = now_ms;
288
289 assert_eq!(
291 self.size_bytes,
292 self.content.len(),
293 "size must match content"
294 );
295 }
296
297 #[must_use]
301 pub fn render(&self) -> String {
302 let type_attr = self.block_type.as_str();
303 match &self.label {
304 Some(label) => {
305 format!(
306 "<block type=\"{}\" label=\"{}\">\n{}\n</block>",
307 type_attr, label, self.content
308 )
309 }
310 None => {
311 format!("<block type=\"{}\">\n{}\n</block>", type_attr, self.content)
312 }
313 }
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320
321 #[test]
322 fn test_block_type_as_str() {
323 assert_eq!(MemoryBlockType::System.as_str(), "system");
324 assert_eq!(MemoryBlockType::Persona.as_str(), "persona");
325 assert_eq!(MemoryBlockType::Human.as_str(), "human");
326 assert_eq!(MemoryBlockType::Facts.as_str(), "facts");
327 assert_eq!(MemoryBlockType::Goals.as_str(), "goals");
328 assert_eq!(MemoryBlockType::Scratch.as_str(), "scratch");
329 }
330
331 #[test]
332 fn test_block_type_priority() {
333 assert_eq!(MemoryBlockType::System.priority(), 0);
334 assert_eq!(MemoryBlockType::Scratch.priority(), 5);
335 }
336
337 #[test]
338 fn test_block_type_all_ordered() {
339 let types = MemoryBlockType::all_ordered();
340 assert_eq!(types.len(), 6);
341 assert_eq!(types[0], MemoryBlockType::System);
342 assert_eq!(types[5], MemoryBlockType::Scratch);
343 }
344
345 #[test]
346 fn test_memory_block_new() {
347 let block = MemoryBlock::new(MemoryBlockType::System, "Hello world", 1000);
348
349 assert_eq!(block.block_type(), MemoryBlockType::System);
350 assert_eq!(block.content(), "Hello world");
351 assert_eq!(block.size_bytes(), 11);
352 assert!(block.label().is_none());
353 assert_eq!(block.created_at_ms(), 1000);
354 assert_eq!(block.modified_at_ms(), 1000);
355 }
356
357 #[test]
358 fn test_memory_block_with_label() {
359 let block = MemoryBlock::with_label(
360 MemoryBlockType::Facts,
361 "user_preferences",
362 "Likes cats",
363 2000,
364 );
365
366 assert_eq!(block.block_type(), MemoryBlockType::Facts);
367 assert_eq!(block.label(), Some("user_preferences"));
368 assert_eq!(block.content(), "Likes cats");
369 assert_eq!(block.size_bytes(), 10);
370 }
371
372 #[test]
373 fn test_memory_block_set_content() {
374 let mut block = MemoryBlock::new(MemoryBlockType::Scratch, "initial", 1000);
375 assert_eq!(block.size_bytes(), 7);
376
377 block.set_content("updated content here", 2000);
378
379 assert_eq!(block.content(), "updated content here");
380 assert_eq!(block.size_bytes(), 20);
381 assert_eq!(block.created_at_ms(), 1000);
382 assert_eq!(block.modified_at_ms(), 2000);
383 }
384
385 #[test]
386 fn test_memory_block_render() {
387 let block = MemoryBlock::new(MemoryBlockType::System, "You are helpful.", 1000);
388 let rendered = block.render();
389
390 assert!(rendered.contains("<block type=\"system\">"));
391 assert!(rendered.contains("You are helpful."));
392 assert!(rendered.contains("</block>"));
393 }
394
395 #[test]
396 fn test_memory_block_render_with_label() {
397 let block = MemoryBlock::with_label(MemoryBlockType::Human, "profile", "Name: Alice", 1000);
398 let rendered = block.render();
399
400 assert!(rendered.contains("type=\"human\""));
401 assert!(rendered.contains("label=\"profile\""));
402 assert!(rendered.contains("Name: Alice"));
403 }
404
405 #[test]
406 fn test_memory_block_id_unique() {
407 let id1 = MemoryBlockId::new();
408 let id2 = MemoryBlockId::new();
409 assert_ne!(id1, id2);
410 }
411
412 #[test]
413 #[should_panic(expected = "block content")]
414 fn test_memory_block_content_too_large() {
415 let large_content = "x".repeat(CORE_MEMORY_BLOCK_SIZE_BYTES_MAX + 1);
416 let _ = MemoryBlock::new(MemoryBlockType::System, large_content, 1000);
417 }
418
419 #[test]
420 #[should_panic(expected = "block label")]
421 fn test_memory_block_label_too_large() {
422 let large_label = "x".repeat(CORE_MEMORY_BLOCK_LABEL_BYTES_MAX + 1);
423 let _ = MemoryBlock::with_label(MemoryBlockType::System, large_label, "content", 1000);
424 }
425}