oxyde/
agent.rs

1//! Agent module for the Oxyde SDK
2//!
3//! This module provides the core Agent type, which represents an AI-driven NPC
4//! in a game environment. Agents have behaviors, memory, and can interact with players.
5
6use 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
22// Re-export AgentContext from oxyde-core so it's available as agent::AgentContext
23pub use crate::AgentContext;
24
25/// Callback for agent events
26pub type AgentCallback = Box<dyn Fn(&Agent, &str) + Send + Sync>;
27
28/// Wrapper for agent callbacks to make them Debug-able
29pub 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    /// Create a new callback wrapper
39    pub fn new(callback: AgentCallback) -> Self {
40        Self(callback)
41    }
42
43    /// Call the underlying callback
44    pub fn call(&self, agent: &Agent, data: &str) {
45        (self.0)(agent, data);
46    }
47}
48
49/// Agent state
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum AgentState {
52    /// Agent is initializing
53    Initializing,
54    /// Agent is idle
55    Idle,
56    /// Agent is processing input
57    Processing,
58    /// Agent is generating a response
59    Generating,
60    /// Agent is executing a behavior
61    Executing,
62    /// Agent is paused
63    Paused,
64    /// Agent is stopped
65    Stopped,
66    /// Agent has encountered an error
67    Error,
68}
69
70/// Agent event types for callbacks
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
72pub enum AgentEvent {
73    /// Agent has started
74    Start,
75    /// Agent has stopped
76    Stop,
77    /// Agent is performing an action
78    Action,
79    /// Agent has generated a response
80    Response,
81    /// Agent state has changed
82    StateChange,
83    /// Agent encountered an error
84    Error,
85}
86
87impl AgentEvent {
88    /// Convert to string representation
89    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    /// Convert from string representation
101    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
120/// Agent represents an AI-powered NPC in a game
121pub struct Agent {
122    /// Unique identifier for the agent
123    id: Uuid,
124
125    /// Agent name
126    name: String,
127
128    /// Agent configuration
129    config: AgentConfig,
130
131    /// Current state of the agent
132    state: RwLock<AgentState>,
133
134    /// Inference engine for generating responses
135    inference: Arc<InferenceEngine>,
136
137    /// Memory system for storing and retrieving context
138    memory: Arc<MemorySystem>,
139
140    /// Context data (current environment state)
141    context: RwLock<AgentContext>,
142
143    /// Behaviors available to the agent
144    behaviors: RwLock<Vec<Box<dyn Behavior>>>,
145
146    /// TTS service for generating speech
147    tts_service: Option<Arc<TTSService>>,
148
149    /// Callbacks for agent events
150    callbacks: Mutex<HashMap<String, Vec<CallbackWrapper>>>,
151
152    /// Emotional state of the agent
153    emotional_state: RwLock<EmotionalState>,
154
155    /// Moderation patterns for content filtering
156    moderation_patterns: Option<RegexSet>,
157}
158
159impl Agent {
160    /// Create a new agent with the given configuration
161    ///
162    /// # Arguments
163    ///
164    /// * `config` - Agent configuration
165    ///
166    /// # Returns
167    ///
168    /// A new Agent instance
169    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        // Load moderation patterns if enabled
174        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, // TTS service is optional ..... REMOVE IF TTS WILL ALWAYS BE REQUIRED
188            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    /// Create a new agent with TTS service
197    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        // Initialize TTS if configured
208        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, // Add TTS service field
223            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    /// Generate speech for agent response
232    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    /// Get the agent's unique ID
252    pub fn id(&self) -> Uuid {
253        self.id
254    }
255
256    /// Get the agent's name
257    pub fn name(&self) -> &str {
258        &self.name
259    }
260
261    /// Get the agent's current state
262    pub async fn state(&self) -> AgentState {
263        *self.state.read().await
264    }
265
266    /// Get a copy of the agent's current emotional state
267    pub async fn emotional_state(&self) -> EmotionalState {
268        self.emotional_state.read().await.clone()
269    }
270
271    /// Get the agent's emotion vector as a float array
272    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    /// Update a specific emotion by a delta value
278    ///
279    /// # Arguments
280    ///
281    /// * `emotion` - Name of the emotion to update (e.g., "joy", "fear")
282    /// * `delta` - Amount to change the emotion by (-1.0 to 1.0)
283    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    /// Apply emotional decay to all emotions
289    ///
290    /// This should be called periodically (e.g., every frame or tick)
291    /// to allow emotions to naturally fade over time
292    pub async fn decay_emotions(&self) {
293        let mut state = self.emotional_state.write().await;
294        state.decay();
295    }
296
297    /// Get the current emotional valence (-1.0 to 1.0)
298    ///
299    /// Valence represents how positive or negative the agent feels
300    pub async fn emotional_valence(&self) -> f32 {
301        self.emotional_state.read().await.valence()
302    }
303
304    /// Get the current emotional arousal (0.0 to 1.0)
305    ///
306    /// Arousal represents how intensely the agent is feeling
307    pub async fn emotional_arousal(&self) -> f32 {
308        self.emotional_state.read().await.arousal()
309    }
310
311    /// Add a behavior to the agent
312    ///
313    /// # Arguments
314    ///
315    /// * `behavior` - A behavior to add to the agent
316    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    /// Add a boxed behavior to the agent
322    ///
323    /// # Arguments
324    ///
325    /// * `behavior` - A boxed behavior to add to the agent
326    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    /// Update the agent's context with new data
332    ///
333    /// # Arguments
334    ///
335    /// * `context` - New context data to merge with existing context
336    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    /// Start the agent
344    ///
345    /// This initializes the agent and prepares it for operation
346    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        // Initialize memory with agent's backstory and knowledge
352        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    /// Stop the agent
367    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    /// Check if content should be moderated
378    ///
379    /// # Arguments
380    ///
381    /// * `input` - Content to check for inappropriate material
382    ///
383    /// # Returns
384    ///
385    /// `Some(response_message)` if content should be moderated, `None` if content is acceptable
386    async fn check_moderation(&self, input: &str) -> Option<String> {
387        if !self.config.moderation.enabled {
388            return None;
389        }
390
391        // Quick regex check first (instant)
392        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 already flagged it, no need for cloud check - return immediately
399        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        // Only do cloud check if regex didn't catch it and cloud moderation is enabled
405        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                        // Content is clean, continue processing
418                    },
419                    Err(e) => {
420                        log::warn!("Cloud moderation failed, continuing without it: {}", e);
421                    }
422                }
423            }
424        }
425
426        None
427    }
428
429    /// Process player input and generate a response
430    ///
431    /// # Arguments
432    ///
433    /// * `input` - Player input to process
434    ///
435    /// # Returns
436    ///
437    /// A result containing the agent's response
438    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        // Check for inappropriate content if moderation is enabled
447        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        // Analyze player intent
457        let intent = Intent::analyze(input).await?;
458
459        // Update memory with player input, capturing current emotional state
460        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        // Find behaviors that match the intent
471        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        // Get current emotional state for behavior filtering and prioritization
480        let current_emotional_state = self.emotional_state.read().await.clone();
481
482        // Filter and sort behaviors by priority (considering emotional modifiers)
483        let mut candidate_behaviors: Vec<_> = behaviors
484            .iter()
485            .filter(|b| {
486                // Check if behavior's emotion trigger is satisfied
487                if let Some(trigger) = b.emotion_trigger() {
488                    trigger.matches(&current_emotional_state)
489                } else {
490                    true
491                }
492            })
493            .collect();
494
495        // Sort by priority (base + emotional modifier), highest first
496        candidate_behaviors.sort_by(|a, b| {
497            let a_priority = a.priority() as i32 + a.emotional_priority_modifier(&current_emotional_state);
498            let b_priority = b.priority() as i32 + b.emotional_priority_modifier(&current_emotional_state);
499            b_priority.cmp(&a_priority) // Descending order
500        });
501
502        // Execute matching behaviors in priority order
503        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                // Apply emotional influences from the behavior
509                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                        // Trigger action callback
524                        self.trigger_event(AgentEvent::Action, &action).await;
525                    },
526                    BehaviorResult::None => {
527                        // Continue to next behavior
528                    }
529                }
530            }
531        }
532
533        // If no behavior provided a response, generate one with inference
534        if response.is_empty() {
535            {
536                let mut state = self.state.write().await;
537                *state = AgentState::Generating;
538            }
539
540            // Get relevant memories
541            let memories = self.memory.retrieve_relevant(input, 5, None).await?;
542
543            // Generate response using inference engine
544            let context = self.context.read().await.clone();
545            response = self
546                .inference
547                .generate_response(input, &memories, &context)
548                .await?;
549
550            // Store the response in memory with current emotional state
551            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        // Trigger response callback
569        self.trigger_event(AgentEvent::Response, &response).await;
570
571        Ok(response)
572    }
573
574    /// Register a callback for agent events using typed events
575    ///
576    /// # Arguments
577    ///
578    /// * `event` - Event type to trigger the callback
579    /// * `callback` - Callback function
580    ///
581    /// # Example
582    ///
583    /// ```ignore
584    /// agent.on_event(AgentEvent::Start, |agent, data| {
585    ///     println!("Agent {} started: {}", agent.name(), data);
586    /// });
587    /// ```
588    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    /// Register a callback for agent events (deprecated, use on_event)
596    ///
597    /// # Arguments
598    ///
599    /// * `event` - Event name to trigger the callback
600    /// * `callback` - Callback function
601    #[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        // Lock the callbacks mutex, recovering from poison if necessary
607        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    /// Trigger a callback for a typed event
616    ///
617    /// # Arguments
618    ///
619    /// * `event` - Event type
620    /// * `data` - Event data
621    async fn trigger_event(&self, event: AgentEvent, data: &str) {
622        self.trigger_callback(event.as_str(), data).await;
623    }
624
625    /// Trigger a callback for an event
626    ///
627    /// # Arguments
628    ///
629    /// * `event` - Event name
630    /// * `data` - Event data
631    async fn trigger_callback(&self, event: &str, data: &str) {
632        // Lock the callbacks mutex, recovering from poison if necessary
633        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    /// Create a new agent with the same configuration but new state
646    ///
647    /// This is a simplified clone method that creates a new agent with the same
648    /// configuration but with fresh state. This is useful for creating copies
649    /// of agents for engine bindings.
650    pub fn clone_for_binding(&self) -> Self {
651        Self::new(self.config.clone())
652    }
653
654    // ==================== Memory System Wrapper Methods ====================
655    // These methods provide direct access to the memory system for FFI bindings
656
657    /// Add a memory to the agent's memory system
658    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    /// Add a memory with emotional context to the agent's memory system
669    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    /// Get the total number of memories stored
689    pub async fn memory_count(&self) -> usize {
690        self.memory.count().await
691    }
692
693    /// Clear all non-permanent memories
694    pub async fn clear_memories(&self) -> usize {
695        self.memory.clear().await
696    }
697
698    /// Get all memories of a specific category
699    pub async fn get_memories_by_category(&self, category: MemoryCategory) -> Vec<Memory> {
700        self.memory.get_by_category(category).await
701    }
702
703    /// Retrieve memories relevant to a query
704    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    /// Forget a specific memory by ID
709    pub async fn forget_memory(&self, memory_id: &str) -> Result<()> {
710        self.memory.forget(memory_id).await
711    }
712
713    /// Forget all memories of a specific category
714    pub async fn forget_memories_by_category(&self, category: MemoryCategory) -> usize {
715        self.memory.forget_by_category(category).await
716    }
717
718    /// Check if a memory exists by ID
719    pub async fn has_memory(&self, memory_id: &str) -> bool {
720        self.memory.get(memory_id).await.is_some()
721    }
722
723    /// Get a specific memory by ID
724    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            // Don't debug the behaviors or callbacks directly as they don't implement Debug
740            .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/// AgentBuilder for fluent construction of Agents
747#[derive(Default)]
748pub struct AgentBuilder {
749    config: Option<AgentConfig>,
750    behaviors: Vec<Box<dyn Behavior>>,
751}
752
753impl AgentBuilder {
754    /// Create a new AgentBuilder
755    pub fn new() -> Self {
756        Self::default()
757    }
758
759    /// Set the agent configuration
760    pub fn with_config(mut self, config: AgentConfig) -> Self {
761        self.config = Some(config);
762        self
763    }
764
765    /// Add a behavior to the agent
766    pub fn with_behavior<B: Behavior + 'static>(mut self, behavior: B) -> Self {
767        self.behaviors.push(Box::new(behavior));
768        self
769    }
770
771    /// Build the agent
772    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        // Validate the configuration before building
778        config.validate()?;
779
780        let agent = Agent::new(config);
781
782        // Add all behaviors provided via the builder
783        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, // No TTS for this test
809            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, // No TTS for this test
840        };
841
842        // Create agent with builder and add behaviors
843        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        // Verify behaviors were added (check the count)
857        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        // Attempt to build without config should fail
868        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, // No TTS for this test
900        };
901
902        let agent = Agent::new(config);
903        agent.start().await.unwrap();
904
905        // Test that bad words trigger moderation response
906        let response = agent.process_input("Fuck you").await.unwrap();
907        assert_eq!(response, "Sorry, I can't respond to that.");
908    }
909}