Skip to main content

oxios_kernel/
persona_store.rs

1//! In-memory store for persona registry.
2//!
3//! PersonaStore manages CRUD operations for personas in memory.
4//! Persists personas to the state store on save.
5
6use anyhow::{anyhow, Result};
7use parking_lot::RwLock;
8use std::collections::HashMap;
9use std::sync::Arc;
10
11use super::persona::Persona;
12
13/// Thread-safe in-memory persona registry.
14#[derive(Debug, Default)]
15pub struct PersonaStore {
16    personas: RwLock<HashMap<String, Persona>>,
17}
18
19impl PersonaStore {
20    /// Creates a new empty persona store.
21    pub fn new() -> Self {
22        Self {
23            personas: RwLock::new(HashMap::new()),
24        }
25    }
26
27    /// Registers a new persona, replacing any existing one with the same ID.
28    pub fn register(&self, persona: Persona) {
29        let mut personas = self.personas.write();
30        personas.insert(persona.id.clone(), persona);
31    }
32
33    /// Gets a persona by ID.
34    pub fn get(&self, id: &str) -> Option<Persona> {
35        let personas = self.personas.read();
36        personas.get(id).cloned()
37    }
38
39    /// Returns all enabled personas.
40    pub fn list_enabled(&self) -> Vec<Persona> {
41        let personas = self.personas.read();
42        personas.values().filter(|p| p.enabled).cloned().collect()
43    }
44
45    /// Returns all personas (enabled and disabled).
46    pub fn list_all(&self) -> Vec<Persona> {
47        let personas = self.personas.read();
48        personas.values().cloned().collect()
49    }
50
51    /// Sets the enabled state of a persona.
52    pub fn set_enabled(&self, id: &str, enabled: bool) -> Result<()> {
53        let mut personas = self.personas.write();
54        match personas.get_mut(id) {
55            Some(p) => {
56                p.enabled = enabled;
57                Ok(())
58            }
59            None => Err(anyhow!("Persona '{}' not found", id)),
60        }
61    }
62
63    /// Deletes a persona by ID.
64    pub fn delete(&self, id: &str) -> Result<()> {
65        let mut personas = self.personas.write();
66        if personas.remove(id).is_some() {
67            Ok(())
68        } else {
69            Err(anyhow!("Persona '{}' not found", id))
70        }
71    }
72
73    /// Updates an existing persona.
74    pub fn update(&self, id: &str, updated: Persona) -> Result<()> {
75        let mut personas = self.personas.write();
76        if personas.contains_key(id) {
77            personas.insert(id.to_string(), updated);
78            Ok(())
79        } else {
80            Err(anyhow!("Persona '{}' not found", id))
81        }
82    }
83
84    /// Returns the count of registered personas.
85    pub fn len(&self) -> usize {
86        let personas = self.personas.read();
87        personas.len()
88    }
89
90    /// Returns true if there are no registered personas.
91    pub fn is_empty(&self) -> bool {
92        let personas = self.personas.read();
93        personas.is_empty()
94    }
95
96    /// Loads personas from a serializable slice.
97    pub fn load_from_slice(&self, personas: &[Persona]) {
98        let mut store = self.personas.write();
99        for p in personas {
100            store.insert(p.id.clone(), p.clone());
101        }
102    }
103}
104
105/// A handle to the persona store for sharing across components.
106#[derive(Clone)]
107pub struct PersonaStoreHandle {
108    store: Arc<PersonaStore>,
109}
110
111impl PersonaStoreHandle {
112    /// Creates a new handle wrapping the given store.
113    pub fn from_store(store: Arc<PersonaStore>) -> Self {
114        Self { store }
115    }
116
117    /// Returns a clone of the inner store.
118    pub fn inner(&self) -> Arc<PersonaStore> {
119        Arc::clone(&self.store)
120    }
121}
122
123impl Default for PersonaStoreHandle {
124    fn default() -> Self {
125        Self {
126            store: Arc::new(PersonaStore::new()),
127        }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn test_register_and_get() {
137        let store = PersonaStore::new();
138        let persona = Persona::new("Test", "assistant", "A test persona", "You are a test.");
139        store.register(persona.clone());
140        assert_eq!(store.get(&persona.id).unwrap().name, "Test");
141    }
142
143    #[test]
144    fn test_list_enabled() {
145        let store = PersonaStore::new();
146        let mut p1 = Persona::new("A", "dev", "Desc A", "Prompt A");
147        p1.enabled = true;
148        let mut p2 = Persona::new("B", "dev", "Desc B", "Prompt B");
149        p2.enabled = false;
150        store.register(p1);
151        store.register(p2);
152
153        let enabled = store.list_enabled();
154        assert_eq!(enabled.len(), 1);
155        assert_eq!(enabled[0].name, "A");
156    }
157
158    #[test]
159    fn test_set_enabled() {
160        let store = PersonaStore::new();
161        let persona = Persona::new("Test", "dev", "Desc", "Prompt");
162        store.register(persona.clone());
163        store.set_enabled(&persona.id, false).unwrap();
164        assert!(store.list_enabled().is_empty());
165
166        store.set_enabled(&persona.id, true).unwrap();
167        assert_eq!(store.list_enabled().len(), 1);
168    }
169
170    #[test]
171    fn test_delete() {
172        let store = PersonaStore::new();
173        let persona = Persona::new("Test", "dev", "Desc", "Prompt");
174        let id = persona.id.clone();
175        store.register(persona);
176        store.delete(&id).unwrap();
177        assert!(store.get(&id).is_none());
178    }
179}