1use std::collections::HashMap;
7use std::sync::{Arc, Mutex};
8
9use regex::RegexSet;
10use tokio::sync::RwLock;
11use uuid::Uuid;
12
13use crate::audio::{AudioData, TTSError, TTSService};
14use crate::config::AgentConfig;
15use crate::inference::InferenceEngine;
16use crate::memory::{Memory, MemoryCategory, MemorySystem};
17use crate::oxyde_game::behavior::{Behavior, BehaviorResult};
18use crate::oxyde_game::emotion::EmotionalState;
19use crate::oxyde_game::intent::Intent;
20use crate::Result;
21
22pub use crate::AgentContext;
24
25pub type AgentCallback = Box<dyn Fn(&Agent, &str) + Send + Sync>;
27
28pub struct CallbackWrapper(AgentCallback);
30
31impl std::fmt::Debug for CallbackWrapper {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 f.write_str("<AgentCallback>")
34 }
35}
36
37impl CallbackWrapper {
38 pub fn new(callback: AgentCallback) -> Self {
40 Self(callback)
41 }
42
43 pub fn call(&self, agent: &Agent, data: &str) {
45 (self.0)(agent, data);
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum AgentState {
52 Initializing,
54 Idle,
56 Processing,
58 Generating,
60 Executing,
62 Paused,
64 Stopped,
66 Error,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
72pub enum AgentEvent {
73 Start,
75 Stop,
77 Action,
79 Response,
81 StateChange,
83 Error,
85}
86
87impl AgentEvent {
88 pub fn as_str(&self) -> &'static str {
90 match self {
91 Self::Start => "start",
92 Self::Stop => "stop",
93 Self::Action => "action",
94 Self::Response => "response",
95 Self::StateChange => "state_change",
96 Self::Error => "error",
97 }
98 }
99
100 pub fn from_str(s: &str) -> Option<Self> {
102 match s.to_lowercase().as_str() {
103 "start" => Some(Self::Start),
104 "stop" => Some(Self::Stop),
105 "action" => Some(Self::Action),
106 "response" => Some(Self::Response),
107 "state_change" | "statechange" => Some(Self::StateChange),
108 "error" => Some(Self::Error),
109 _ => None,
110 }
111 }
112}
113
114impl std::fmt::Display for AgentEvent {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 write!(f, "{}", self.as_str())
117 }
118}
119
120pub struct Agent {
122 id: Uuid,
124
125 name: String,
127
128 config: AgentConfig,
130
131 state: RwLock<AgentState>,
133
134 inference: Arc<InferenceEngine>,
136
137 memory: Arc<MemorySystem>,
139
140 context: RwLock<AgentContext>,
142
143 behaviors: RwLock<Vec<Box<dyn Behavior>>>,
145
146 tts_service: Option<Arc<TTSService>>,
148
149 callbacks: Mutex<HashMap<String, Vec<CallbackWrapper>>>,
151
152 emotional_state: RwLock<EmotionalState>,
154
155 moderation_patterns: Option<RegexSet>,
157}
158
159impl Agent {
160 pub fn new(config: AgentConfig) -> Self {
170 let inference = Arc::new(InferenceEngine::new(&config.inference));
171 let memory = Arc::new(MemorySystem::new(config.memory.clone()));
172
173 let moderation_patterns = if config.moderation.enabled {
175 crate::utils::load_moderation_patterns("assets/badwords_regex.txt").ok()
176 } else {
177 None
178 };
179
180 Self {
181 id: Uuid::new_v4(),
182 name: config.agent.name.clone(),
183 config,
184 state: RwLock::new(AgentState::Initializing),
185 inference,
186 memory,
187 tts_service: None, context: RwLock::new(HashMap::new()),
189 behaviors: RwLock::new(Vec::new()),
190 callbacks: Mutex::new(HashMap::new()),
191 emotional_state: RwLock::new(EmotionalState::new()),
192 moderation_patterns,
193 }
194 }
195
196 pub fn new_with_tts(config: AgentConfig) -> Self {
198 let inference = Arc::new(InferenceEngine::new(&config.inference));
199 let memory = Arc::new(MemorySystem::new(config.memory.clone()));
200
201 let moderation_patterns = if config.moderation.enabled {
202 crate::utils::load_moderation_patterns("assets/badwords_regex.txt").ok()
203 } else {
204 None
205 };
206
207 let tts_service = config.tts.as_ref().map(|tts_config| {
209 Arc::new(TTSService::new(
210 tts_config.default_provider.clone(),
211 tts_config.clone(),
212 ))
213 });
214
215 Self {
216 id: Uuid::new_v4(),
217 name: config.agent.name.clone(),
218 config,
219 state: RwLock::new(AgentState::Initializing),
220 inference,
221 memory,
222 tts_service, context: RwLock::new(HashMap::new()),
224 behaviors: RwLock::new(Vec::new()),
225 callbacks: Mutex::new(HashMap::new()),
226 emotional_state: RwLock::new(EmotionalState::new()),
227 moderation_patterns,
228 }
229 }
230
231 pub async fn speak(
233 &self,
234 text: &str,
235 emotions: &EmotionalState,
236 urgency: f32,
237 ) -> Result<AudioData> {
238 if let Some(tts) = &self.tts_service {
239 tts.synthesize_npc_speech(&self.name, text, emotions, urgency)
240 .await
241 .map_err(|e| {
242 crate::OxydeError::AudioError(TTSError::AudioProcessingError(e.to_string()))
243 })
244 } else {
245 Err(crate::OxydeError::ConfigurationError(
246 "TTS not configured".to_string(),
247 ))
248 }
249 }
250
251 pub fn id(&self) -> Uuid {
253 self.id
254 }
255
256 pub fn name(&self) -> &str {
258 &self.name
259 }
260
261 pub async fn state(&self) -> AgentState {
263 *self.state.read().await
264 }
265
266 pub async fn emotional_state(&self) -> EmotionalState {
268 self.emotional_state.read().await.clone()
269 }
270
271 pub async fn emotion_vector(&self) -> [f32; 8] {
273 let emotion_state = self.emotional_state.read().await;
274 emotion_state.as_vector()
275 }
276
277 pub async fn update_emotion(&self, emotion: &str, delta: f32) {
284 let mut state = self.emotional_state.write().await;
285 state.update_emotion(emotion, delta);
286 }
287
288 pub async fn decay_emotions(&self) {
293 let mut state = self.emotional_state.write().await;
294 state.decay();
295 }
296
297 pub async fn emotional_valence(&self) -> f32 {
301 self.emotional_state.read().await.valence()
302 }
303
304 pub async fn emotional_arousal(&self) -> f32 {
308 self.emotional_state.read().await.arousal()
309 }
310
311 pub async fn add_behavior<B: Behavior + 'static>(&self, behavior: B) {
317 let mut behaviors = self.behaviors.write().await;
318 behaviors.push(Box::new(behavior));
319 }
320
321 pub async fn add_boxed_behavior(&self, behavior: Box<dyn Behavior>) {
327 let mut behaviors = self.behaviors.write().await;
328 behaviors.push(behavior);
329 }
330
331 pub async fn update_context(&self, context: AgentContext) {
337 let mut current_context = self.context.write().await;
338 for (key, value) in context {
339 current_context.insert(key, value);
340 }
341 }
342
343 pub async fn start(&self) -> Result<()> {
347 let mut state = self.state.write().await;
348 *state = AgentState::Idle;
349 log::info!("Agent {} started", self.name);
350
351 self.memory
353 .add(Memory::new(
354 MemoryCategory::Semantic,
355 &serde_json::to_string(&self.config.agent.backstory)?,
356 f64::INFINITY,
357 None,
358 ))
359 .await?;
360
361 self.trigger_event(AgentEvent::Start, "Agent started").await;
362
363 Ok(())
364 }
365
366 pub async fn stop(&self) -> Result<()> {
368 let mut state = self.state.write().await;
369 *state = AgentState::Stopped;
370 log::info!("Agent {} stopped", self.name);
371
372 self.trigger_event(AgentEvent::Stop, "Agent stopped").await;
373
374 Ok(())
375 }
376
377 async fn check_moderation(&self, input: &str) -> Option<String> {
387 if !self.config.moderation.enabled {
388 return None;
389 }
390
391 let regex_flagged = if let Some(ref patterns) = self.moderation_patterns {
393 patterns.is_match(&input.to_lowercase())
394 } else {
395 false
396 };
397
398 if regex_flagged {
400 log::warn!("Agent {} moderated inappropriate content (regex): {}", self.name, input);
401 return Some(self.config.moderation.response_message.clone());
402 }
403
404 if self.config.moderation.use_cloud_moderation {
406 let api_key = self.config.moderation.cloud_moderation_api_key.clone()
407 .or_else(|| self.config.inference.api_key.clone())
408 .or_else(|| std::env::var("OPENAI_API_KEY").ok());
409
410 if let Some(key) = api_key {
411 match crate::utils::check_cloud_moderation(input, &key).await {
412 Ok(true) => {
413 log::warn!("Agent {} moderated inappropriate content (cloud): {}", self.name, input);
414 return Some(self.config.moderation.response_message.clone());
415 },
416 Ok(false) => {
417 },
419 Err(e) => {
420 log::warn!("Cloud moderation failed, continuing without it: {}", e);
421 }
422 }
423 }
424 }
425
426 None
427 }
428
429 pub async fn process_input(&self, input: &str) -> Result<String> {
439 {
440 let mut state = self.state.write().await;
441 *state = AgentState::Processing;
442 }
443
444 log::debug!("Agent {} processing input: {}", self.name, input);
445
446 if let Some(moderation_response) = self.check_moderation(input).await {
448 {
449 let mut state = self.state.write().await;
450 *state = AgentState::Idle;
451 }
452 self.trigger_callback("response", &moderation_response).await;
453 return Ok(moderation_response);
454 }
455
456 let intent = Intent::analyze(input).await?;
458
459 let emotional_state = self.emotional_state.read().await;
461 self.memory.add(Memory::new_emotional(
462 MemoryCategory::Episodic,
463 input,
464 1.0,
465 emotional_state.valence() as f64,
466 emotional_state.arousal() as f64,
467 None
468 )).await?;
469
470 let behaviors = self.behaviors.read().await;
472 let mut response = String::new();
473
474 {
475 let mut state = self.state.write().await;
476 *state = AgentState::Executing;
477 }
478
479 let current_emotional_state = self.emotional_state.read().await.clone();
481
482 let mut candidate_behaviors: Vec<_> = behaviors
484 .iter()
485 .filter(|b| {
486 if let Some(trigger) = b.emotion_trigger() {
488 trigger.matches(¤t_emotional_state)
489 } else {
490 true
491 }
492 })
493 .collect();
494
495 candidate_behaviors.sort_by(|a, b| {
497 let a_priority = a.priority() as i32 + a.emotional_priority_modifier(¤t_emotional_state);
498 let b_priority = b.priority() as i32 + b.emotional_priority_modifier(¤t_emotional_state);
499 b_priority.cmp(&a_priority) });
501
502 for behavior in candidate_behaviors {
504 if behavior.matches_intent(&intent).await {
505 let context = self.context.read().await.clone();
506 let behavior_result = behavior.execute(&intent, &context).await?;
507
508 let influences = behavior.emotion_influences();
510 if !influences.is_empty() {
511 let mut emotional_state = self.emotional_state.write().await;
512 for influence in influences {
513 emotional_state.update_emotion(&influence.emotion, influence.delta);
514 }
515 }
516
517 match behavior_result {
518 BehaviorResult::Response(text) => {
519 response = text;
520 break;
521 }
522 BehaviorResult::Action(action) => {
523 self.trigger_event(AgentEvent::Action, &action).await;
525 },
526 BehaviorResult::None => {
527 }
529 }
530 }
531 }
532
533 if response.is_empty() {
535 {
536 let mut state = self.state.write().await;
537 *state = AgentState::Generating;
538 }
539
540 let memories = self.memory.retrieve_relevant(input, 5, None).await?;
542
543 let context = self.context.read().await.clone();
545 response = self
546 .inference
547 .generate_response(input, &memories, &context)
548 .await?;
549
550 let emotional_state = self.emotional_state.read().await;
552 self.memory.add(Memory::new_emotional(
553 MemoryCategory::Semantic,
554 &response,
555 1.0,
556 emotional_state.valence() as f64,
557 emotional_state.arousal() as f64,
558 None
559 )).await?;
560 }
561
562 {
563 let mut state = self.state.write().await;
564 *state = AgentState::Idle;
565 }
566
567
568 self.trigger_event(AgentEvent::Response, &response).await;
570
571 Ok(response)
572 }
573
574 pub fn on_event<F>(&self, event: AgentEvent, callback: F)
589 where
590 F: Fn(&Agent, &str) + Send + Sync + 'static,
591 {
592 self.register_callback(event.as_str(), callback);
593 }
594
595 #[deprecated(since = "0.1.5", note = "Use on_event() with AgentEvent enum instead")]
602 pub fn register_callback<F>(&self, event: &str, callback: F)
603 where
604 F: Fn(&Agent, &str) + Send + Sync + 'static,
605 {
606 let mut callbacks = self.callbacks.lock().unwrap_or_else(|poisoned| {
608 log::warn!("Callback mutex was poisoned, recovering");
609 poisoned.into_inner()
610 });
611 let event_callbacks = callbacks.entry(event.to_string()).or_insert(Vec::new());
612 event_callbacks.push(CallbackWrapper::new(Box::new(callback)));
613 }
614
615 async fn trigger_event(&self, event: AgentEvent, data: &str) {
622 self.trigger_callback(event.as_str(), data).await;
623 }
624
625 async fn trigger_callback(&self, event: &str, data: &str) {
632 let callbacks = self.callbacks.lock().unwrap_or_else(|poisoned| {
634 log::warn!("Callback mutex was poisoned during trigger, recovering");
635 poisoned.into_inner()
636 });
637 if let Some(event_callbacks) = callbacks.get(event) {
638 for callback in event_callbacks {
639 callback.call(self, data);
640 }
641 }
642 }
643
644
645 pub fn clone_for_binding(&self) -> Self {
651 Self::new(self.config.clone())
652 }
653
654 pub async fn add_memory(
659 &self,
660 category: MemoryCategory,
661 content: &str,
662 importance: f64,
663 tags: Option<Vec<String>>,
664 ) -> Result<()> {
665 self.memory.add(Memory::new(category, content, importance, tags)).await
666 }
667
668 pub async fn add_emotional_memory(
670 &self,
671 category: MemoryCategory,
672 content: &str,
673 importance: f64,
674 valence: f64,
675 intensity: f64,
676 tags: Option<Vec<String>>,
677 ) -> Result<()> {
678 self.memory.add(Memory::new_emotional(
679 category,
680 content,
681 importance,
682 valence,
683 intensity,
684 tags
685 )).await
686 }
687
688 pub async fn memory_count(&self) -> usize {
690 self.memory.count().await
691 }
692
693 pub async fn clear_memories(&self) -> usize {
695 self.memory.clear().await
696 }
697
698 pub async fn get_memories_by_category(&self, category: MemoryCategory) -> Vec<Memory> {
700 self.memory.get_by_category(category).await
701 }
702
703 pub async fn retrieve_relevant_memories(&self, query: &str, limit: usize) -> Result<Vec<Memory>> {
705 self.memory.retrieve_relevant(query, limit, None).await
706 }
707
708 pub async fn forget_memory(&self, memory_id: &str) -> Result<()> {
710 self.memory.forget(memory_id).await
711 }
712
713 pub async fn forget_memories_by_category(&self, category: MemoryCategory) -> usize {
715 self.memory.forget_by_category(category).await
716 }
717
718 pub async fn has_memory(&self, memory_id: &str) -> bool {
720 self.memory.get(memory_id).await.is_some()
721 }
722
723 pub async fn get_memory(&self, memory_id: &str) -> Option<Memory> {
725 self.memory.get(memory_id).await
726 }
727}
728
729impl std::fmt::Debug for Agent {
730 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
731 let callbacks_count = self.callbacks.lock()
732 .map(|cb| cb.len())
733 .unwrap_or(0);
734
735 f.debug_struct("Agent")
736 .field("id", &self.id)
737 .field("name", &self.name)
738 .field("config", &self.config)
739 .field("behaviors_count", &format!("<{} behaviors>", self.behaviors.try_read().map(|b| b.len()).unwrap_or(0)))
741 .field("callbacks_count", &format!("<{} callback types>", callbacks_count))
742 .finish()
743 }
744}
745
746#[derive(Default)]
748pub struct AgentBuilder {
749 config: Option<AgentConfig>,
750 behaviors: Vec<Box<dyn Behavior>>,
751}
752
753impl AgentBuilder {
754 pub fn new() -> Self {
756 Self::default()
757 }
758
759 pub fn with_config(mut self, config: AgentConfig) -> Self {
761 self.config = Some(config);
762 self
763 }
764
765 pub fn with_behavior<B: Behavior + 'static>(mut self, behavior: B) -> Self {
767 self.behaviors.push(Box::new(behavior));
768 self
769 }
770
771 pub async fn build(self) -> Result<Agent> {
773 let config = self.config.ok_or_else(|| {
774 crate::OxydeError::ConfigurationError("Agent configuration is required".to_string())
775 })?;
776
777 config.validate()?;
779
780 let agent = Agent::new(config);
781
782 for behavior in self.behaviors {
784 agent.add_boxed_behavior(behavior).await;
785 }
786
787 Ok(agent)
788 }
789}
790
791#[cfg(test)]
792mod tests {
793 use super::*;
794 use crate::config::{AgentPersonality, InferenceConfig, MemoryConfig};
795
796 #[tokio::test]
797 async fn test_agent_creation() {
798 let config = AgentConfig {
799 agent: AgentPersonality {
800 name: "Test Agent".to_string(),
801 role: "Tester".to_string(),
802 backstory: vec!["A test agent".to_string()],
803 knowledge: vec!["Testing knowledge".to_string()],
804 },
805 memory: MemoryConfig::default(),
806 inference: InferenceConfig::default(),
807 behavior: HashMap::new(),
808 tts: None, moderation: crate::config::ModerationConfig::default(),
810 };
811
812 let agent = Agent::new(config);
813 assert_eq!(agent.name(), "Test Agent");
814
815 agent.start().await.unwrap();
816 assert_eq!(agent.state().await, AgentState::Idle);
817
818 agent.stop().await.unwrap();
819 assert_eq!(agent.state().await, AgentState::Stopped);
820 }
821
822
823 #[tokio::test]
824
825 async fn test_agent_builder_with_behaviors() {
826 use crate::oxyde_game::behavior::GreetingBehavior;
827
828 let config = AgentConfig {
829 agent: AgentPersonality {
830 name: "Builder Test".to_string(),
831 role: "Tester".to_string(),
832 backstory: vec!["Built with builder".to_string()],
833 knowledge: vec![],
834 },
835 memory: MemoryConfig::default(),
836 inference: InferenceConfig::default(),
837 behavior: HashMap::new(),
838 moderation: crate::config::ModerationConfig::default(),
839 tts: None, };
841
842 let greeting1 = GreetingBehavior::new("Hello!");
844 let greeting2 = GreetingBehavior::new("Greetings!");
845
846 let agent = AgentBuilder::new()
847 .with_config(config)
848 .with_behavior(greeting1)
849 .with_behavior(greeting2)
850 .build()
851 .await
852 .unwrap();
853
854 assert_eq!(agent.name(), "Builder Test");
855
856 let behaviors = agent.behaviors.read().await;
858 assert_eq!(behaviors.len(), 2, "Builder should add all provided behaviors");
859 }
860
861 #[tokio::test]
862 async fn test_agent_builder_without_config_fails() {
863 use crate::oxyde_game::behavior::GreetingBehavior;
864
865 let greeting = GreetingBehavior::new("Hello!");
866
867 let result = AgentBuilder::new()
869 .with_behavior(greeting)
870 .build()
871 .await;
872
873 assert!(result.is_err(), "Building without config should fail");
874 if let Err(crate::OxydeError::ConfigurationError(msg)) = result {
875 assert!(msg.contains("required"), "Error should mention config is required");
876 } else {
877 panic!("Expected ConfigurationError");
878 }
879 }
880
881 #[tokio::test]
882 async fn test_content_moderation() {
883 let config = AgentConfig {
884 agent: AgentPersonality {
885 name: "Test Agent".to_string(),
886 role: "Tester".to_string(),
887 backstory: vec!["A test agent".to_string()],
888 knowledge: vec!["Testing knowledge".to_string()],
889 },
890 memory: MemoryConfig::default(),
891 inference: InferenceConfig::default(),
892 behavior: HashMap::new(),
893 moderation: crate::config::ModerationConfig {
894 enabled: true,
895 response_message: "Sorry, I can't respond to that.".to_string(),
896 use_cloud_moderation: false,
897 cloud_moderation_api_key: None,
898 },
899 tts: None, };
901
902 let agent = Agent::new(config);
903 agent.start().await.unwrap();
904
905 let response = agent.process_input("Fuck you").await.unwrap();
907 assert_eq!(response, "Sorry, I can't respond to that.");
908 }
909}