1#![allow(dead_code, unused_imports, improper_ctypes_definitions)]
2pub mod hot_reload;
13pub mod skill;
14pub mod tool;
15pub mod tools;
16pub mod tts;
17pub mod wasm_runtime;
18
19pub use mofa_kernel::{
20 AgentPlugin, PluginConfig, PluginContext, PluginEvent, PluginMetadata, PluginResult,
21 PluginState, PluginType,
22};
23use serde::{Deserialize, Serialize};
24use std::any::Any;
25use std::collections::HashMap;
26use std::sync::Arc;
27use tokio::sync::RwLock;
28use tracing::{debug, error, info, warn};
29#[async_trait::async_trait]
35pub trait LLMClient: Send + Sync {
36 async fn generate(&self, prompt: &str) -> PluginResult<String>;
38
39 async fn generate_stream(
41 &self,
42 prompt: &str,
43 callback: Box<dyn Fn(String) + Send + Sync>,
44 ) -> PluginResult<String>;
45
46 async fn chat(&self, messages: Vec<ChatMessage>) -> PluginResult<String>;
48
49 async fn embedding(&self, text: &str) -> PluginResult<Vec<f32>>;
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct ChatMessage {
56 pub role: String,
57 pub content: String,
58}
59
60impl ChatMessage {
61 pub fn system(content: &str) -> Self {
62 Self {
63 role: "system".to_string(),
64 content: content.to_string(),
65 }
66 }
67
68 pub fn user(content: &str) -> Self {
69 Self {
70 role: "user".to_string(),
71 content: content.to_string(),
72 }
73 }
74
75 pub fn assistant(content: &str) -> Self {
76 Self {
77 role: "assistant".to_string(),
78 content: content.to_string(),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct LLMPluginConfig {
86 pub model: String,
88 pub api_key: Option<String>,
90 pub base_url: Option<String>,
92 pub max_tokens: usize,
94 pub temperature: f32,
96 pub timeout_secs: u64,
98}
99
100impl Default for LLMPluginConfig {
101 fn default() -> Self {
102 Self {
103 model: "gpt-3.5-turbo".to_string(),
104 api_key: None,
105 base_url: None,
106 max_tokens: 2048,
107 temperature: 0.7,
108 timeout_secs: 30,
109 }
110 }
111}
112
113pub struct OpenAIClient {
115 config: LLMPluginConfig,
116}
117
118impl OpenAIClient {
119 pub fn new(config: LLMPluginConfig) -> Self {
120 Self { config }
121 }
122}
123
124#[async_trait::async_trait]
125impl LLMClient for OpenAIClient {
126 async fn generate(&self, prompt: &str) -> PluginResult<String> {
127 debug!(
129 "OpenAI generating response for prompt: {}...",
130 &prompt[..prompt.len().min(50)]
131 );
132 Ok(format!(
133 "[{}] Generated response to: {}",
134 self.config.model, prompt
135 ))
136 }
137
138 async fn generate_stream(
139 &self,
140 prompt: &str,
141 callback: Box<dyn Fn(String) + Send + Sync>,
142 ) -> PluginResult<String> {
143 let response = format!("[{}] Stream response to: {}", self.config.model, prompt);
145 for chunk in response.as_str().split_whitespace() {
146 callback(chunk.to_string());
147 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
148 }
149 Ok(response)
150 }
151
152 async fn chat(&self, messages: Vec<ChatMessage>) -> PluginResult<String> {
153 let last_message = messages.last().map(|m| m.content.as_str()).unwrap_or("");
154 debug!("OpenAI chat with {} messages", messages.len());
155 Ok(format!(
156 "[{}] Chat response to: {}",
157 self.config.model, last_message
158 ))
159 }
160
161 async fn embedding(&self, text: &str) -> PluginResult<Vec<f32>> {
162 debug!(
164 "OpenAI generating embedding for text: {}...",
165 &text[..text.len().min(50)]
166 );
167 Ok(vec![0.1, 0.2, 0.3, 0.4, 0.5])
168 }
169}
170
171pub struct LLMPlugin {
173 metadata: PluginMetadata,
174 state: PluginState,
175 config: LLMPluginConfig,
176 client: Option<Arc<dyn LLMClient>>,
177 call_count: u64,
178 total_tokens: u64,
179}
180
181impl LLMPlugin {
182 pub fn new(plugin_id: &str) -> Self {
183 let metadata = PluginMetadata::new(plugin_id, "LLM Plugin", PluginType::LLM)
184 .with_description("Large Language Model integration plugin")
185 .with_capability("text_generation")
186 .with_capability("chat")
187 .with_capability("embedding");
188
189 Self {
190 metadata,
191 state: PluginState::Unloaded,
192 config: LLMPluginConfig::default(),
193 client: None,
194 call_count: 0,
195 total_tokens: 0,
196 }
197 }
198
199 pub fn with_config(mut self, config: LLMPluginConfig) -> Self {
200 self.config = config;
201 self
202 }
203
204 pub fn with_client<C: LLMClient + 'static>(mut self, client: C) -> Self {
205 self.client = Some(Arc::new(client));
206 self
207 }
208
209 pub fn client(&self) -> Option<&Arc<dyn LLMClient>> {
211 self.client.as_ref()
212 }
213
214 pub async fn chat(&mut self, messages: Vec<ChatMessage>) -> PluginResult<String> {
216 let client = self
217 .client
218 .as_ref()
219 .ok_or_else(|| anyhow::anyhow!("LLM client not initialized"))?;
220 self.call_count += 1;
221 client.chat(messages).await
222 }
223
224 pub async fn embedding(&self, text: &str) -> PluginResult<Vec<f32>> {
226 let client = self
227 .client
228 .as_ref()
229 .ok_or_else(|| anyhow::anyhow!("LLM client not initialized"))?;
230 client.embedding(text).await
231 }
232}
233
234#[async_trait::async_trait]
235impl AgentPlugin for LLMPlugin {
236 fn metadata(&self) -> &PluginMetadata {
237 &self.metadata
238 }
239
240 fn state(&self) -> PluginState {
241 self.state.clone()
242 }
243
244 async fn load(&mut self, ctx: &PluginContext) -> PluginResult<()> {
245 self.state = PluginState::Loading;
246 info!("Loading LLM plugin: {}", self.metadata.id);
247
248 if let Some(model) = ctx.config.get_string("model") {
250 self.config.model = model;
251 }
252 if let Some(api_key) = ctx.config.get_string("api_key") {
253 self.config.api_key = Some(api_key);
254 }
255
256 self.state = PluginState::Loaded;
257 Ok(())
258 }
259
260 async fn init_plugin(&mut self) -> PluginResult<()> {
261 info!("Initializing LLM plugin: {}", self.metadata.id);
262
263 if self.client.is_none() {
265 self.client = Some(Arc::new(OpenAIClient::new(self.config.clone())));
266 }
267
268 Ok(())
269 }
270
271 async fn start(&mut self) -> PluginResult<()> {
272 self.state = PluginState::Running;
273 info!("LLM plugin {} started", self.metadata.id);
274 Ok(())
275 }
276
277 async fn stop(&mut self) -> PluginResult<()> {
278 self.state = PluginState::Paused;
279 info!("LLM plugin {} stopped", self.metadata.id);
280 Ok(())
281 }
282
283 async fn unload(&mut self) -> PluginResult<()> {
284 self.client = None;
285 self.state = PluginState::Unloaded;
286 info!("LLM plugin {} unloaded", self.metadata.id);
287 Ok(())
288 }
289
290 async fn execute(&mut self, input: String) -> PluginResult<String> {
291 let client = self
292 .client
293 .as_ref()
294 .ok_or_else(|| anyhow::anyhow!("LLM client not initialized"))?;
295 self.call_count += 1;
296 client.generate(&input).await
297 }
298
299 fn stats(&self) -> HashMap<String, serde_json::Value> {
300 let mut stats = HashMap::new();
301 stats.insert("call_count".to_string(), serde_json::json!(self.call_count));
302 stats.insert(
303 "total_tokens".to_string(),
304 serde_json::json!(self.total_tokens),
305 );
306 stats.insert("model".to_string(), serde_json::json!(self.config.model));
307 stats
308 }
309
310 fn as_any(&self) -> &dyn Any {
311 self
312 }
313
314 fn as_any_mut(&mut self) -> &mut dyn Any {
315 self
316 }
317
318 fn into_any(self: Box<Self>) -> Box<dyn Any> {
319 self
320 }
321}
322
323pub mod rhai_runtime;
328
329pub use rhai_runtime::*;
330pub use tool::*;
331pub use tools::*;
332pub use tts::{
333 AudioPlaybackConfig, MockTTSEngine, TTSCommand, TTSEngine, TTSPlugin, TTSPluginConfig,
334 TextToSpeechTool, VoiceInfo, cache::ModelCache, model_downloader::HFHubClient, play_audio,
335 play_audio_async,
336};
337
338#[cfg(feature = "kokoro")]
340pub use tts::kokoro_wrapper::KokoroTTS;
341
342#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct ToolDefinition {
345 pub name: String,
347 pub description: String,
349 pub parameters: serde_json::Value,
351 pub requires_confirmation: bool,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct ToolCall {
358 pub name: String,
360 pub arguments: serde_json::Value,
362 pub call_id: String,
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct ToolResult {
369 pub call_id: String,
371 pub success: bool,
373 pub result: serde_json::Value,
375 pub error: Option<String>,
377}
378
379#[async_trait::async_trait]
381pub trait ToolExecutor: Send + Sync {
382 fn definition(&self) -> &ToolDefinition;
384
385 async fn execute(&self, arguments: serde_json::Value) -> PluginResult<serde_json::Value>;
387
388 fn validate(&self, arguments: &serde_json::Value) -> PluginResult<()> {
390 let _ = arguments;
391 Ok(())
392 }
393}
394
395pub struct ToolPlugin {
397 metadata: PluginMetadata,
398 state: PluginState,
399 tools: HashMap<String, Box<dyn ToolExecutor>>,
400 call_history: Vec<ToolCall>,
401}
402
403impl ToolPlugin {
404 pub fn new(plugin_id: &str) -> Self {
405 let metadata = PluginMetadata::new(plugin_id, "Tool Plugin", PluginType::Tool)
406 .with_description("Tool calling and execution plugin")
407 .with_capability("tool_call")
408 .with_capability("function_call");
409
410 Self {
411 metadata,
412 state: PluginState::Unloaded,
413 tools: HashMap::new(),
414 call_history: Vec::new(),
415 }
416 }
417
418 pub fn register_tool<T: ToolExecutor + 'static>(&mut self, tool: T) {
420 let name = tool.definition().name.clone();
421 self.tools.insert(name.clone(), Box::new(tool));
422 info!("Registered tool: {}", name);
423 }
424
425 pub fn list_tools(&self) -> Vec<ToolDefinition> {
427 self.tools
428 .values()
429 .map(|t| t.definition().clone())
430 .collect()
431 }
432
433 pub async fn call_tool(&mut self, call: ToolCall) -> PluginResult<ToolResult> {
435 let tool = self
436 .tools
437 .get(&call.name)
438 .ok_or_else(|| anyhow::anyhow!("Tool not found: {}", call.name))?;
439
440 tool.validate(&call.arguments)?;
442
443 self.call_history.push(call.clone());
445
446 match tool.execute(call.arguments).await {
448 Ok(result) => Ok(ToolResult {
449 call_id: call.call_id,
450 success: true,
451 result,
452 error: None,
453 }),
454 Err(e) => Ok(ToolResult {
455 call_id: call.call_id,
456 success: false,
457 result: serde_json::Value::Null,
458 error: Some(e.to_string()),
459 }),
460 }
461 }
462}
463
464#[async_trait::async_trait]
465impl AgentPlugin for ToolPlugin {
466 fn metadata(&self) -> &PluginMetadata {
467 &self.metadata
468 }
469
470 fn state(&self) -> PluginState {
471 self.state.clone()
472 }
473
474 async fn load(&mut self, _ctx: &PluginContext) -> PluginResult<()> {
475 self.state = PluginState::Loading;
476 info!("Loading Tool plugin: {}", self.metadata.id);
477 self.state = PluginState::Loaded;
478 Ok(())
479 }
480
481 async fn init_plugin(&mut self) -> PluginResult<()> {
482 info!("Initializing Tool plugin: {}", self.metadata.id);
483 Ok(())
484 }
485
486 async fn start(&mut self) -> PluginResult<()> {
487 self.state = PluginState::Running;
488 info!(
489 "Tool plugin {} started with {} tools",
490 self.metadata.id,
491 self.tools.len()
492 );
493 Ok(())
494 }
495
496 async fn stop(&mut self) -> PluginResult<()> {
497 self.state = PluginState::Paused;
498 info!("Tool plugin {} stopped", self.metadata.id);
499 Ok(())
500 }
501
502 async fn unload(&mut self) -> PluginResult<()> {
503 self.tools.clear();
504 self.state = PluginState::Unloaded;
505 info!("Tool plugin {} unloaded", self.metadata.id);
506 Ok(())
507 }
508
509 async fn execute(&mut self, input: String) -> PluginResult<String> {
510 let call: ToolCall = serde_json::from_str(&input)
512 .map_err(|e| anyhow::anyhow!("Invalid tool call format: {}", e))?;
513 let result = self.call_tool(call).await?;
514 serde_json::to_string(&result)
515 .map_err(|e| anyhow::anyhow!("Failed to serialize result: {}", e))
516 }
517
518 fn stats(&self) -> HashMap<String, serde_json::Value> {
519 let mut stats = HashMap::new();
520 stats.insert(
521 "tool_count".to_string(),
522 serde_json::json!(self.tools.len()),
523 );
524 stats.insert(
525 "call_count".to_string(),
526 serde_json::json!(self.call_history.len()),
527 );
528 stats
529 }
530
531 fn as_any(&self) -> &dyn Any {
532 self
533 }
534
535 fn as_any_mut(&mut self) -> &mut dyn Any {
536 self
537 }
538
539 fn into_any(self: Box<Self>) -> Box<dyn Any> {
540 self
541 }
542}
543
544#[async_trait::async_trait]
550pub trait StorageBackend: Send + Sync {
551 async fn get(&self, key: &str) -> PluginResult<Option<Vec<u8>>>;
553
554 async fn set(&self, key: &str, value: Vec<u8>) -> PluginResult<()>;
556
557 async fn delete(&self, key: &str) -> PluginResult<bool>;
559
560 async fn exists(&self, key: &str) -> PluginResult<bool>;
562
563 async fn keys(&self, prefix: Option<&str>) -> PluginResult<Vec<String>>;
565}
566
567pub struct MemoryStorage {
569 data: Arc<RwLock<HashMap<String, Vec<u8>>>>,
570}
571
572impl MemoryStorage {
573 pub fn new() -> Self {
574 Self {
575 data: Arc::new(RwLock::new(HashMap::new())),
576 }
577 }
578}
579
580impl Default for MemoryStorage {
581 fn default() -> Self {
582 Self::new()
583 }
584}
585
586#[async_trait::async_trait]
587impl StorageBackend for MemoryStorage {
588 async fn get(&self, key: &str) -> PluginResult<Option<Vec<u8>>> {
589 let data = self.data.read().await;
590 Ok(data.get(key).cloned())
591 }
592
593 async fn set(&self, key: &str, value: Vec<u8>) -> PluginResult<()> {
594 let mut data = self.data.write().await;
595 data.insert(key.to_string(), value);
596 Ok(())
597 }
598
599 async fn delete(&self, key: &str) -> PluginResult<bool> {
600 let mut data = self.data.write().await;
601 Ok(data.remove(key).is_some())
602 }
603
604 async fn exists(&self, key: &str) -> PluginResult<bool> {
605 let data = self.data.read().await;
606 Ok(data.contains_key(key))
607 }
608
609 async fn keys(&self, prefix: Option<&str>) -> PluginResult<Vec<String>> {
610 let data = self.data.read().await;
611 Ok(match prefix {
612 Some(p) => data.keys().filter(|k| k.starts_with(p)).cloned().collect(),
613 None => data.keys().cloned().collect(),
614 })
615 }
616}
617
618pub struct StoragePlugin {
620 metadata: PluginMetadata,
621 state: PluginState,
622 backend: Option<Arc<dyn StorageBackend>>,
623 read_count: u64,
624 write_count: u64,
625}
626
627impl StoragePlugin {
628 pub fn new(plugin_id: &str) -> Self {
629 let metadata = PluginMetadata::new(plugin_id, "Storage Plugin", PluginType::Storage)
630 .with_description("Key-value storage plugin")
631 .with_capability("storage")
632 .with_capability("persistence");
633
634 Self {
635 metadata,
636 state: PluginState::Unloaded,
637 backend: None,
638 read_count: 0,
639 write_count: 0,
640 }
641 }
642
643 pub fn with_backend<B: StorageBackend + 'static>(mut self, backend: B) -> Self {
644 self.backend = Some(Arc::new(backend));
645 self
646 }
647
648 pub async fn get(&mut self, key: &str) -> PluginResult<Option<Vec<u8>>> {
650 let backend = self
651 .backend
652 .as_ref()
653 .ok_or_else(|| anyhow::anyhow!("Storage backend not initialized"))?;
654 self.read_count += 1;
655 backend.get(key).await
656 }
657
658 pub async fn set(&mut self, key: &str, value: Vec<u8>) -> PluginResult<()> {
660 let backend = self
661 .backend
662 .as_ref()
663 .ok_or_else(|| anyhow::anyhow!("Storage backend not initialized"))?;
664 self.write_count += 1;
665 backend.set(key, value).await
666 }
667
668 pub async fn delete(&mut self, key: &str) -> PluginResult<bool> {
670 let backend = self
671 .backend
672 .as_ref()
673 .ok_or_else(|| anyhow::anyhow!("Storage backend not initialized"))?;
674 self.write_count += 1;
675 backend.delete(key).await
676 }
677
678 pub async fn get_string(&mut self, key: &str) -> PluginResult<Option<String>> {
680 let data = self.get(key).await?;
681 Ok(data.map(|d| String::from_utf8_lossy(&d).to_string()))
682 }
683
684 pub async fn set_string(&mut self, key: &str, value: &str) -> PluginResult<()> {
686 self.set(key, value.as_bytes().to_vec()).await
687 }
688}
689
690#[async_trait::async_trait]
691impl AgentPlugin for StoragePlugin {
692 fn metadata(&self) -> &PluginMetadata {
693 &self.metadata
694 }
695
696 fn state(&self) -> PluginState {
697 self.state.clone()
698 }
699
700 async fn load(&mut self, _ctx: &PluginContext) -> PluginResult<()> {
701 self.state = PluginState::Loading;
702 info!("Loading Storage plugin: {}", self.metadata.id);
703 self.state = PluginState::Loaded;
704 Ok(())
705 }
706
707 async fn init_plugin(&mut self) -> PluginResult<()> {
708 info!("Initializing Storage plugin: {}", self.metadata.id);
709 if self.backend.is_none() {
710 self.backend = Some(Arc::new(MemoryStorage::new()));
711 }
712 Ok(())
713 }
714
715 async fn start(&mut self) -> PluginResult<()> {
716 self.state = PluginState::Running;
717 info!("Storage plugin {} started", self.metadata.id);
718 Ok(())
719 }
720
721 async fn stop(&mut self) -> PluginResult<()> {
722 self.state = PluginState::Paused;
723 info!("Storage plugin {} stopped", self.metadata.id);
724 Ok(())
725 }
726
727 async fn unload(&mut self) -> PluginResult<()> {
728 self.backend = None;
729 self.state = PluginState::Unloaded;
730 info!("Storage plugin {} unloaded", self.metadata.id);
731 Ok(())
732 }
733
734 async fn execute(&mut self, input: String) -> PluginResult<String> {
735 let parts: Vec<&str> = input.as_str().splitn(3, ' ').collect();
737 match parts.as_slice() {
738 ["get", key] => {
739 let value = self.get_string(key).await?;
740 Ok(value.unwrap_or_else(|| "null".to_string()))
741 }
742 ["set", key, value] => {
743 self.set_string(key, value).await?;
744 Ok("OK".to_string())
745 }
746 ["delete", key] => {
747 let deleted = self.delete(key).await?;
748 Ok(if deleted { "1" } else { "0" }.to_string())
749 }
750 _ => Err(anyhow::anyhow!(
751 "Invalid command. Use: get <key>, set <key> <value>, delete <key>"
752 )),
753 }
754 }
755
756 fn stats(&self) -> HashMap<String, serde_json::Value> {
757 let mut stats = HashMap::new();
758 stats.insert("read_count".to_string(), serde_json::json!(self.read_count));
759 stats.insert(
760 "write_count".to_string(),
761 serde_json::json!(self.write_count),
762 );
763 stats
764 }
765
766 fn as_any(&self) -> &dyn Any {
767 self
768 }
769
770 fn as_any_mut(&mut self) -> &mut dyn Any {
771 self
772 }
773
774 fn into_any(self: Box<Self>) -> Box<dyn Any> {
775 self
776 }
777}
778
779#[derive(Debug, Clone, Serialize, Deserialize)]
785pub struct MemoryEntry {
786 pub id: String,
788 pub content: String,
790 pub embedding: Option<Vec<f32>>,
792 pub created_at: u64,
794 pub access_count: u32,
796 pub importance: f32,
798 pub metadata: HashMap<String, String>,
800}
801
802pub struct MemoryPlugin {
804 metadata: PluginMetadata,
805 state: PluginState,
806 memories: Vec<MemoryEntry>,
807 max_memories: usize,
808}
809
810impl MemoryPlugin {
811 pub fn new(plugin_id: &str) -> Self {
812 let metadata = PluginMetadata::new(plugin_id, "Memory Plugin", PluginType::Memory)
813 .with_description("Agent memory management plugin")
814 .with_capability("short_term_memory")
815 .with_capability("long_term_memory")
816 .with_capability("memory_retrieval");
817
818 Self {
819 metadata,
820 state: PluginState::Unloaded,
821 memories: Vec::new(),
822 max_memories: 1000,
823 }
824 }
825
826 pub fn with_max_memories(mut self, max: usize) -> Self {
827 self.max_memories = max;
828 self
829 }
830
831 pub fn add_memory(&mut self, content: &str, importance: f32) -> String {
833 let id = uuid::Uuid::now_v7().to_string();
834 let entry = MemoryEntry {
835 id: id.clone(),
836 content: content.to_string(),
837 embedding: None,
838 created_at: std::time::SystemTime::now()
839 .duration_since(std::time::UNIX_EPOCH)
840 .unwrap_or_default()
841 .as_secs(),
842 access_count: 0,
843 importance,
844 metadata: HashMap::new(),
845 };
846 self.memories.push(entry);
847
848 if self.memories.len() > self.max_memories {
850 self.memories
852 .sort_by(|a, b| b.importance.partial_cmp(&a.importance).unwrap());
853 self.memories.truncate(self.max_memories);
855 }
856
857 id
858 }
859
860 pub fn retrieve(&mut self, query: &str, limit: usize) -> Vec<&MemoryEntry> {
862 let mut results: Vec<&mut MemoryEntry> = self
864 .memories
865 .iter_mut()
866 .filter(|m| m.content.contains(query))
867 .collect();
868
869 for entry in &mut results {
871 entry.access_count += 1;
872 }
873
874 results.into_iter().map(|m| &*m).take(limit).collect()
875 }
876
877 pub fn all_memories(&self) -> &[MemoryEntry] {
879 &self.memories
880 }
881
882 pub fn clear(&mut self) {
884 self.memories.clear();
885 }
886}
887
888#[async_trait::async_trait]
889impl AgentPlugin for MemoryPlugin {
890 fn metadata(&self) -> &PluginMetadata {
891 &self.metadata
892 }
893
894 fn state(&self) -> PluginState {
895 self.state.clone()
896 }
897
898 async fn load(&mut self, _ctx: &PluginContext) -> PluginResult<()> {
899 self.state = PluginState::Loading;
900 info!("Loading Memory plugin: {}", self.metadata.id);
901 self.state = PluginState::Loaded;
902 Ok(())
903 }
904
905 async fn init_plugin(&mut self) -> PluginResult<()> {
906 info!("Initializing Memory plugin: {}", self.metadata.id);
907 Ok(())
908 }
909
910 async fn start(&mut self) -> PluginResult<()> {
911 self.state = PluginState::Running;
912 info!("Memory plugin {} started", self.metadata.id);
913 Ok(())
914 }
915
916 async fn stop(&mut self) -> PluginResult<()> {
917 self.state = PluginState::Paused;
918 info!("Memory plugin {} stopped", self.metadata.id);
919 Ok(())
920 }
921
922 async fn unload(&mut self) -> PluginResult<()> {
923 self.memories.clear();
924 self.state = PluginState::Unloaded;
925 info!("Memory plugin {} unloaded", self.metadata.id);
926 Ok(())
927 }
928
929 async fn execute(&mut self, input: String) -> PluginResult<String> {
930 let parts: Vec<&str> = input.as_str().splitn(3, ' ').collect();
931 match parts.as_slice() {
932 ["add", content] => {
933 let id = self.add_memory(content, 0.5);
934 Ok(format!("Added memory: {}", id))
935 }
936 ["add", content, importance] => {
937 let imp: f32 = importance.parse().unwrap_or(0.5);
938 let id = self.add_memory(content, imp);
939 Ok(format!("Added memory: {}", id))
940 }
941 ["search", query] => {
942 let results = self.retrieve(query, 5);
943 let contents: Vec<&str> = results.iter().map(|m| m.content.as_str()).collect();
944 Ok(serde_json::to_string(&contents)?)
945 }
946 ["count"] => Ok(self.memories.len().to_string()),
947 ["clear"] => {
948 self.clear();
949 Ok("Cleared".to_string())
950 }
951 _ => Err(anyhow::anyhow!("Invalid command")),
952 }
953 }
954
955 fn stats(&self) -> HashMap<String, serde_json::Value> {
956 let mut stats = HashMap::new();
957 stats.insert(
958 "memory_count".to_string(),
959 serde_json::json!(self.memories.len()),
960 );
961 stats.insert(
962 "max_memories".to_string(),
963 serde_json::json!(self.max_memories),
964 );
965 stats
966 }
967
968 fn as_any(&self) -> &dyn Any {
969 self
970 }
971
972 fn as_any_mut(&mut self) -> &mut dyn Any {
973 self
974 }
975
976 fn into_any(self: Box<Self>) -> Box<dyn Any> {
977 self
978 }
979}
980
981struct PluginEntry {
987 plugin: Box<dyn AgentPlugin>,
988 config: PluginConfig,
989}
990
991pub struct PluginManager {
993 plugins: Arc<RwLock<HashMap<String, PluginEntry>>>,
995 context: PluginContext,
997 event_rx: Option<tokio::sync::mpsc::Receiver<PluginEvent>>,
999 event_tx: tokio::sync::mpsc::Sender<PluginEvent>,
1001}
1002
1003impl PluginManager {
1004 pub fn new(agent_id: &str) -> Self {
1006 let (event_tx, event_rx) = tokio::sync::mpsc::channel(256);
1007 let context = PluginContext::new(agent_id).with_event_sender(event_tx.clone());
1008
1009 Self {
1010 plugins: Arc::new(RwLock::new(HashMap::new())),
1011 context,
1012 event_rx: Some(event_rx),
1013 event_tx,
1014 }
1015 }
1016
1017 pub fn context(&self) -> &PluginContext {
1019 &self.context
1020 }
1021
1022 pub async fn register<P: AgentPlugin + 'static>(&self, plugin: P) -> PluginResult<()> {
1024 self.register_with_config(plugin, PluginConfig::new()).await
1025 }
1026
1027 pub async fn register_with_config<P: AgentPlugin + 'static>(
1029 &self,
1030 plugin: P,
1031 config: PluginConfig,
1032 ) -> PluginResult<()> {
1033 let plugin_id = plugin.plugin_id().to_string();
1034 let mut plugins = self.plugins.write().await;
1035
1036 if plugins.contains_key(&plugin_id) {
1037 return Err(anyhow::anyhow!("Plugin {} already registered", plugin_id));
1038 }
1039
1040 let entry = PluginEntry {
1041 plugin: Box::new(plugin),
1042 config,
1043 };
1044 plugins.insert(plugin_id.clone(), entry);
1045
1046 info!("Plugin {} registered", plugin_id);
1047 Ok(())
1048 }
1049
1050 pub async fn unregister(&self, plugin_id: &str) -> PluginResult<()> {
1052 let mut plugins = self.plugins.write().await;
1053 if let Some(mut entry) = plugins.remove(plugin_id) {
1054 entry.plugin.unload().await?;
1055 info!("Plugin {} unregistered", plugin_id);
1056 }
1057 Ok(())
1058 }
1059
1060 pub async fn get(
1062 &self,
1063 plugin_id: &str,
1064 ) -> Option<impl std::ops::Deref<Target = Box<dyn AgentPlugin>> + '_> {
1065 let plugins = self.plugins.read().await;
1066 if plugins.contains_key(plugin_id) {
1067 Some(tokio::sync::RwLockReadGuard::map(plugins, |p| {
1068 &p.get(plugin_id).unwrap().plugin
1069 }))
1070 } else {
1071 None
1072 }
1073 }
1074
1075 pub async fn get_mut(
1077 &self,
1078 plugin_id: &str,
1079 ) -> Option<impl std::ops::DerefMut<Target = Box<dyn AgentPlugin>> + '_> {
1080 let plugins = self.plugins.write().await;
1081 if plugins.contains_key(plugin_id) {
1082 Some(tokio::sync::RwLockWriteGuard::map(plugins, |p| {
1083 &mut p.get_mut(plugin_id).unwrap().plugin
1084 }))
1085 } else {
1086 None
1087 }
1088 }
1089
1090 pub async fn get_by_type(&self, plugin_type: PluginType) -> Vec<String> {
1092 let plugins = self.plugins.read().await;
1093 plugins
1094 .iter()
1095 .filter(|(_, entry)| entry.plugin.plugin_type() == plugin_type)
1096 .map(|(id, _)| id.clone())
1097 .collect()
1098 }
1099
1100 pub async fn load_all(&self) -> PluginResult<()> {
1102 let mut plugins = self.plugins.write().await;
1103 for (id, entry) in plugins.iter_mut() {
1104 let ctx = self.context.clone().with_config(entry.config.clone());
1105 if let Err(e) = entry.plugin.load(&ctx).await {
1106 error!("Failed to load plugin {}: {}", id, e);
1107 return Err(e);
1108 }
1109 }
1110 info!("All plugins loaded");
1111 Ok(())
1112 }
1113
1114 pub async fn init_all(&self) -> PluginResult<()> {
1116 let mut plugins = self.plugins.write().await;
1117
1118 let mut sorted: Vec<_> = plugins.iter_mut().collect();
1120 sorted.sort_by(|a, b| {
1121 b.1.plugin
1122 .metadata()
1123 .priority
1124 .cmp(&a.1.plugin.metadata().priority)
1125 });
1126
1127 for (id, entry) in sorted {
1128 if let Err(e) = entry.plugin.init_plugin().await {
1129 error!("Failed to initialize plugin {}: {}", id, e);
1130 return Err(e);
1131 }
1132 }
1133 info!("All plugins initialized");
1134 Ok(())
1135 }
1136
1137 pub async fn start_all(&self) -> PluginResult<()> {
1139 let mut plugins = self.plugins.write().await;
1140 for (id, entry) in plugins.iter_mut() {
1141 if entry.config.auto_start
1142 && entry.config.enabled
1143 && let Err(e) = entry.plugin.start().await
1144 {
1145 error!("Failed to start plugin {}: {}", id, e);
1146 return Err(e);
1147 }
1148 }
1149 info!("All auto-start plugins started");
1150 Ok(())
1151 }
1152
1153 pub async fn stop_all(&self) -> PluginResult<()> {
1155 let mut plugins = self.plugins.write().await;
1156 for (id, entry) in plugins.iter_mut() {
1157 if let Err(e) = entry.plugin.stop().await {
1158 warn!("Failed to stop plugin {}: {}", id, e);
1159 }
1160 }
1161 info!("All plugins stopped");
1162 Ok(())
1163 }
1164
1165 pub async fn unload_all(&self) -> PluginResult<()> {
1167 let mut plugins = self.plugins.write().await;
1168 for (id, entry) in plugins.iter_mut() {
1169 if let Err(e) = entry.plugin.unload().await {
1170 warn!("Failed to unload plugin {}: {}", id, e);
1171 }
1172 }
1173 plugins.clear();
1174 info!("All plugins unloaded");
1175 Ok(())
1176 }
1177
1178 pub async fn execute(&self, plugin_id: &str, input: String) -> PluginResult<String> {
1180 let mut plugins = self.plugins.write().await;
1181 let entry = plugins
1182 .get_mut(plugin_id)
1183 .ok_or_else(|| anyhow::anyhow!("Plugin {} not found", plugin_id))?;
1184 entry.plugin.execute(input).await
1185 }
1186
1187 pub async fn plugin_ids(&self) -> Vec<String> {
1189 let plugins = self.plugins.read().await;
1190 plugins.keys().cloned().collect()
1191 }
1192
1193 pub async fn list_plugins(&self) -> Vec<PluginMetadata> {
1195 let plugins = self.plugins.read().await;
1196 plugins
1197 .values()
1198 .map(|e| e.plugin.metadata().clone())
1199 .collect()
1200 }
1201
1202 pub async fn stats(&self, plugin_id: &str) -> Option<HashMap<String, serde_json::Value>> {
1204 let plugins = self.plugins.read().await;
1205 plugins.get(plugin_id).map(|e| e.plugin.stats())
1206 }
1207
1208 pub async fn health_check_all(&self) -> HashMap<String, bool> {
1210 let plugins = self.plugins.read().await;
1211 let mut results = HashMap::new();
1212 for (id, entry) in plugins.iter() {
1213 let healthy = entry.plugin.health_check().await.unwrap_or(false);
1214 results.insert(id.clone(), healthy);
1215 }
1216 results
1217 }
1218
1219 pub fn take_event_receiver(&mut self) -> Option<tokio::sync::mpsc::Receiver<PluginEvent>> {
1221 self.event_rx.take()
1222 }
1223}
1224
1225#[cfg(test)]
1230mod tests {
1231 use super::*;
1232
1233 #[tokio::test]
1234 async fn test_plugin_manager() {
1235 let manager = PluginManager::new("test_agent");
1236
1237 let llm = LLMPlugin::new("llm_001");
1239 manager.register(llm).await.unwrap();
1240
1241 let storage = StoragePlugin::new("storage_001");
1243 manager.register(storage).await.unwrap();
1244
1245 let memory = MemoryPlugin::new("memory_001");
1247 manager.register(memory).await.unwrap();
1248
1249 let ids = manager.plugin_ids().await;
1251 assert_eq!(ids.len(), 3);
1252
1253 manager.load_all().await.unwrap();
1255 manager.init_all().await.unwrap();
1256 manager.start_all().await.unwrap();
1257
1258 let result = manager
1260 .execute("llm_001", "Hello".to_string())
1261 .await
1262 .unwrap();
1263 assert!(result.contains("Hello"));
1264
1265 manager
1267 .execute("storage_001", "set foo bar".to_string())
1268 .await
1269 .unwrap();
1270 let value = manager
1271 .execute("storage_001", "get foo".to_string())
1272 .await
1273 .unwrap();
1274 assert_eq!(value, "bar");
1275
1276 manager.stop_all().await.unwrap();
1278 manager.unload_all().await.unwrap();
1279
1280 let ids = manager.plugin_ids().await;
1281 assert_eq!(ids.len(), 0);
1282 }
1283
1284 #[tokio::test]
1285 async fn test_llm_plugin() {
1286 let mut llm = LLMPlugin::new("llm_test");
1287 let ctx = PluginContext::new("test_agent");
1288
1289 llm.load(&ctx).await.unwrap();
1290 llm.init_plugin().await.unwrap();
1291 llm.start().await.unwrap();
1292
1293 assert_eq!(llm.state(), PluginState::Running);
1294
1295 let response = llm.execute("Test prompt".to_string()).await.unwrap();
1296 assert!(response.contains("Test prompt"));
1297
1298 llm.stop().await.unwrap();
1299 llm.unload().await.unwrap();
1300
1301 assert_eq!(llm.state(), PluginState::Unloaded);
1302 }
1303
1304 #[tokio::test]
1305 async fn test_storage_plugin() {
1306 let mut storage = StoragePlugin::new("storage_test").with_backend(MemoryStorage::new());
1307 let ctx = PluginContext::new("test_agent");
1308
1309 storage.load(&ctx).await.unwrap();
1310 storage.init_plugin().await.unwrap();
1311 storage.start().await.unwrap();
1312
1313 storage.set_string("key1", "value1").await.unwrap();
1315 let value = storage.get_string("key1").await.unwrap();
1316 assert_eq!(value, Some("value1".to_string()));
1317
1318 let deleted = storage.delete("key1").await.unwrap();
1320 assert!(deleted);
1321
1322 let value = storage.get_string("key1").await.unwrap();
1323 assert!(value.is_none());
1324
1325 storage.stop().await.unwrap();
1326 storage.unload().await.unwrap();
1327 }
1328
1329 #[tokio::test]
1330 async fn test_memory_plugin() {
1331 let mut memory = MemoryPlugin::new("memory_test").with_max_memories(100);
1332 let ctx = PluginContext::new("test_agent");
1333
1334 memory.load(&ctx).await.unwrap();
1335 memory.init_plugin().await.unwrap();
1336 memory.start().await.unwrap();
1337
1338 let id1 = memory.add_memory("Important meeting tomorrow", 0.9);
1340 let id2 = memory.add_memory("Buy groceries", 0.3);
1341
1342 assert!(!id1.is_empty());
1343 assert!(!id2.is_empty());
1344
1345 let results = memory.retrieve("meeting", 10);
1347 assert_eq!(results.len(), 1);
1348 assert!(results[0].content.contains("meeting"));
1349
1350 assert_eq!(memory.all_memories().len(), 2);
1352
1353 memory.stop().await.unwrap();
1354 memory.unload().await.unwrap();
1355 }
1356
1357 #[tokio::test]
1358 async fn test_plugin_context() {
1359 let ctx = PluginContext::new("test_agent");
1360
1361 ctx.set_state("counter", 42i32).await;
1363 let value: Option<i32> = ctx.get_state("counter").await;
1364 assert_eq!(value, Some(42));
1365
1366 let mut config = PluginConfig::new();
1368 config.set("timeout", 30);
1369 config.set("enabled", true);
1370
1371 assert_eq!(config.get_i64("timeout"), Some(30));
1372 assert_eq!(config.get_bool("enabled"), Some(true));
1373 }
1374}