1use crate::configurable::ConfigurableAgent;
15use crate::{AgentConfig, ToonAgents};
16use ares_llm::ProviderRegistry;
17use ares_tools::Tools;
18use ares_types::types::{AgentType, AppError, Result};
19use std::collections::HashMap;
20use std::sync::Arc;
21
22pub struct AgentRegistry {
27 configs: HashMap<String, AgentConfig>,
29 provider_registry: Arc<ProviderRegistry>,
31 tools: Arc<Tools>,
33 dynamic_config: Option<Arc<dyn ToonAgents>>,
35}
36
37fn intersect_agent_tools_with_tenant_allowlist(
38 agent_tools: Option<&[String]>,
39 tenant_allowed_tools: &[String],
40) -> Vec<String> {
41 agent_tools
42 .unwrap_or(&[])
43 .iter()
44 .filter(|tool| tenant_allowed_tools.contains(*tool))
45 .cloned()
46 .collect()
47}
48
49impl AgentRegistry {
50 pub fn new(provider_registry: Arc<ProviderRegistry>, tools: Arc<Tools>) -> Self {
52 Self {
53 configs: HashMap::new(),
54 provider_registry,
55 tools,
56 dynamic_config: None,
57 }
58 }
59
60 pub fn from_config(
62 agents: HashMap<String, AgentConfig>,
63 provider_registry: Arc<ProviderRegistry>,
64 tools: Arc<Tools>,
65 ) -> Self {
66 Self {
67 configs: agents,
68 provider_registry,
69 tools,
70 dynamic_config: None,
71 }
72 }
73
74 pub fn with_dynamic_config(
76 agents: HashMap<String, AgentConfig>,
77 provider_registry: Arc<ProviderRegistry>,
78 tools: Arc<Tools>,
79 dynamic_config: Arc<dyn ToonAgents>,
80 ) -> Self {
81 Self {
82 configs: agents,
83 provider_registry,
84 tools,
85 dynamic_config: Some(dynamic_config),
86 }
87 }
88
89 pub fn set_dynamic_config(&mut self, dynamic_config: Arc<dyn ToonAgents>) {
91 self.dynamic_config = Some(dynamic_config);
92 }
93
94 pub fn register(&mut self, name: &str, config: AgentConfig) {
96 self.configs.insert(name.to_string(), config);
97 }
98
99 pub fn get_config(&self, name: &str) -> Option<&AgentConfig> {
103 self.configs.get(name)
104 }
105
106 pub fn get_config_any(&self, name: &str) -> Option<AgentConfig> {
108 self.configs.get(name).cloned().or_else(|| self.get_toon_config(name))
109 }
110
111 pub fn get_toon_config(&self, name: &str) -> Option<AgentConfig> {
113 self.dynamic_config.as_ref().and_then(|dc| dc.get(name))
114 }
115
116 fn has_toml_agent(&self, name: &str) -> bool {
118 self.configs.contains_key(name)
119 }
120
121 fn has_toon_agent(&self, name: &str) -> bool {
123 self.dynamic_config
124 .as_ref()
125 .map(|dc| dc.get(name).is_some())
126 .unwrap_or(false)
127 }
128
129 pub fn agent_names(&self) -> Vec<String> {
131 let mut names: Vec<String> = self.configs.keys().cloned().collect();
132
133 if let Some(dc) = &self.dynamic_config {
135 for name in dc.names() {
136 if !names.contains(&name) {
137 names.push(name);
138 }
139 }
140 }
141
142 names
143 }
144
145 pub fn has_agent(&self, name: &str) -> bool {
147 self.has_toml_agent(name) || self.has_toon_agent(name)
148 }
149
150 pub async fn create_agent(&self, name: &str) -> Result<ConfigurableAgent> {
159 if let Some(config) = self.get_config(name) {
161 return self.create_agent_from_config(name, config).await;
162 }
163
164 if let Some(config) = self.get_toon_config(name) {
166 return self.create_agent_from_config(name, &config).await;
167 }
168
169 Err(AppError::Configuration(format!(
170 "Agent '{}' not found in TOML or TOON configuration",
171 name
172 )))
173 }
174
175 pub async fn create_agent_from_config(
177 &self,
178 name: &str,
179 config: &AgentConfig,
180 ) -> Result<ConfigurableAgent> {
181 let llm = self
183 .provider_registry
184 .create_client_for_model(&config.model)
185 .await?;
186 let provider_name = self
187 .provider_registry
188 .get_model(&config.model)
189 .map(|model| model.provider.clone())
190 .unwrap_or_else(|| config.model.clone());
191
192 let mut agent = ConfigurableAgent::new_with_provider(
193 name,
194 config,
195 llm,
196 None,
197 provider_name,
198 );
199 agent.set_tools(Arc::clone(&self.tools));
200 Ok(agent)
201 }
202
203 #[cfg(feature = "postgres")]
206 pub async fn create_agent_from_config_with_fallbacks(
207 &self,
208 name: &str,
209 config: &AgentConfig,
210 tenant_id: &str,
211 pool: &sqlx::PgPool,
212 fleet_secrets: &ares_store::FleetSecrets,
213 ) -> Result<ConfigurableAgent> {
214 let chain = self
215 .provider_registry
216 .resolve_with_fallback(&config.model, tenant_id, pool, fleet_secrets)
217 .await?;
218
219 let allowlist_store = ares_store::tenant_allowlist::TenantAllowlistStore::new(pool);
220 for resolved in &chain {
221 if !allowlist_store
222 .is_model_allowed(tenant_id, &resolved.model_name)
223 .await
224 .map_err(|e| AppError::Auth(format!("Failed to check model allowlist: {}", e)))?
225 {
226 return Err(AppError::Auth(format!(
227 "Model '{}' is not allowed for this tenant",
228 resolved.model_name
229 )));
230 }
231 }
232
233 let mut iter = chain.into_iter();
234 let primary = iter.next().ok_or_else(|| {
235 AppError::Configuration(format!(
236 "No provider resolved for model/tier '{}'",
237 config.model
238 ))
239 })?;
240
241 let primary_provider_name = primary.provider_name.clone();
242
243 let llm = self
244 .provider_registry
245 .create_client_for_resolved_provider(&primary)
246 .await
247 .map_err(|e| {
248 AppError::Configuration(format!(
249 "Failed to construct primary provider '{}' for model '{}': {}",
250 primary.provider_name, primary.model_name, e
251 ))
252 })?;
253
254 let mut fallback_llms = Vec::new();
255 for fallback in iter {
256 let fallback_provider_name = fallback.provider_name.clone();
257 let client = self
258 .provider_registry
259 .create_client_for_resolved_provider(&fallback)
260 .await
261 .map_err(|e| {
262 AppError::Configuration(format!(
263 "Failed to construct fallback provider '{}' for model '{}': {}",
264 fallback.provider_name, fallback.model_name, e
265 ))
266 })?;
267 fallback_llms.push((fallback_provider_name, client));
268 }
269
270 let mut agent = ConfigurableAgent::new_with_provider(
271 name,
272 config,
273 llm,
274 None,
275 primary_provider_name,
276 );
277 agent.set_tools(Arc::clone(&self.tools));
278 agent.set_fallback_llms_with_providers(fallback_llms);
279
280 let db_tools = allowlist_store
284 .list_tools(tenant_id)
285 .await
286 .map_err(|e| AppError::Auth(format!("Failed to check tool allowlist: {}", e)))?;
287 let db_tool_names: Vec<String> = db_tools.iter().map(|t| t.tool_name.clone()).collect();
288 let new_allowed =
289 intersect_agent_tools_with_tenant_allowlist(agent.allowed_tools(), &db_tool_names);
290 agent.set_allowed_tools(Some(new_allowed));
291
292 Ok(agent)
293 }
294
295 pub async fn create_agent_by_type(&self, agent_type: AgentType) -> Result<ConfigurableAgent> {
297 let name = Self::type_to_name(&agent_type);
298 self.create_agent(name).await
299 }
300
301 pub fn type_to_name(agent_type: &AgentType) -> &str {
303 agent_type.as_str()
304 }
305
306 pub fn get_agent_model(&self, name: &str) -> Option<String> {
308 if let Some(config) = self.configs.get(name) {
310 return Some(config.model.clone());
311 }
312 self.get_toon_config(name).map(|c| c.model)
314 }
315
316 pub fn get_agent_tools(&self, name: &str) -> Vec<String> {
321 if let Some(config) = self.configs.get(name) {
323 return config
324 .allowed_tools
325 .clone()
326 .unwrap_or_else(|| config.tools.clone());
327 }
328 self.get_toon_config(name)
330 .map(|c| c.allowed_tools.unwrap_or(c.tools))
331 .unwrap_or_default()
332 }
333
334 pub fn get_agent_system_prompt(&self, name: &str) -> Option<String> {
336 if let Some(config) = self.configs.get(name) {
338 return config.system_prompt.clone();
339 }
340 self.get_toon_config(name).and_then(|c| c.system_prompt)
342 }
343}
344
345pub struct AgentRegistryBuilder {
347 configs: HashMap<String, AgentConfig>,
348 provider_registry: Option<Arc<ProviderRegistry>>,
349 tools: Option<Arc<Tools>>,
350 dynamic_config: Option<Arc<dyn ToonAgents>>,
351}
352
353impl AgentRegistryBuilder {
354 pub fn new() -> Self {
356 Self {
357 configs: HashMap::new(),
358 provider_registry: None,
359 tools: None,
360 dynamic_config: None,
361 }
362 }
363
364 pub fn with_provider_registry(mut self, registry: Arc<ProviderRegistry>) -> Self {
366 self.provider_registry = Some(registry);
367 self
368 }
369
370 pub fn with_tools(mut self, tools: Arc<Tools>) -> Self {
372 self.tools = Some(tools);
373 self
374 }
375
376 pub fn with_dynamic_config(mut self, dynamic_config: Arc<dyn ToonAgents>) -> Self {
378 self.dynamic_config = Some(dynamic_config);
379 self
380 }
381
382 pub fn with_agent(mut self, name: &str, config: AgentConfig) -> Self {
384 self.configs.insert(name.to_string(), config);
385 self
386 }
387
388 pub fn from_config(mut self, agents: HashMap<String, AgentConfig>) -> Self {
390 self.configs = agents;
391 self
392 }
393
394 pub fn build(self) -> Result<AgentRegistry> {
396 let provider_registry = self.provider_registry.ok_or_else(|| {
397 AppError::Configuration("ProviderRegistry is required for AgentRegistry".into())
398 })?;
399
400 let tools = self.tools.unwrap_or_else(|| {
401 Arc::new(Tools::from_static(std::iter::empty::<Arc<dyn ares_tools::Tool>>()))
402 });
403
404 Ok(AgentRegistry {
405 configs: self.configs,
406 provider_registry,
407 tools,
408 dynamic_config: self.dynamic_config,
409 })
410 }
411}
412
413impl Default for AgentRegistryBuilder {
414 fn default() -> Self {
415 Self::new()
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422 use ares_llm::ProviderConfig;
423 use ares_tools::Tool;
424 use std::collections::HashMap;
425
426 struct MapToon(HashMap<String, AgentConfig>);
427 impl ToonAgents for MapToon {
428 fn get(&self, name: &str) -> Option<AgentConfig> {
429 self.0.get(name).cloned()
430 }
431 fn names(&self) -> Vec<String> {
432 self.0.keys().cloned().collect()
433 }
434 }
435
436 fn empty_tools() -> Arc<Tools> {
437 Arc::new(Tools::from_static(Vec::<Arc<dyn Tool>>::new()))
438 }
439
440 fn create_test_agent_map() -> HashMap<String, AgentConfig> {
441 HashMap::new()
442 }
443
444 fn create_test_provider_registry() -> Arc<ProviderRegistry> {
445 let mut registry = ProviderRegistry::new();
446 registry.register_provider(
447 "ollama-local",
448 ProviderConfig::OpenAI {
449 api_key_env: "TEST_KEY".to_string(),
450 api_base: "https://test.example.com/v1".to_string(),
451 default_model: "ministral-3:3b".to_string(),
452 },
453 );
454 registry.register_model(
455 "default",
456 ares_llm::ModelConfig {
457 provider: "ollama-local".to_string(),
458 model: "ministral-3:3b".to_string(),
459 temperature: 0.7,
460 max_tokens: 512,
461 },
462 );
463 Arc::new(registry)
464 }
465
466 #[test]
467 fn intersect_agent_tools_defaults_to_deny() {
468 let tenant_tools = vec!["calendar".to_string(), "search".to_string()];
469 let allowed = intersect_agent_tools_with_tenant_allowlist(None, &tenant_tools);
470 assert!(allowed.is_empty());
471 }
472
473 #[test]
474 fn intersect_agent_tools_keeps_only_configured_and_tenant_allowed() {
475 let agent_tools = vec!["calendar".to_string(), "sql".to_string()];
476 let tenant_tools = vec!["calendar".to_string(), "search".to_string()];
477 let allowed =
478 intersect_agent_tools_with_tenant_allowlist(Some(&agent_tools), &tenant_tools);
479 assert_eq!(allowed, vec!["calendar".to_string()]);
480 }
481
482 #[test]
483 fn test_type_to_name() {
484 assert_eq!(AgentRegistry::type_to_name(&AgentType::Router), "router");
485 assert_eq!(AgentRegistry::type_to_name(&AgentType::Product), "product");
486 assert_eq!(AgentRegistry::type_to_name(&AgentType::HR), "hr");
487 assert_eq!(AgentRegistry::type_to_name(&AgentType::Invoice), "invoice");
488 assert_eq!(AgentRegistry::type_to_name(&AgentType::Sales), "sales");
489 assert_eq!(AgentRegistry::type_to_name(&AgentType::Finance), "finance");
490 assert_eq!(
491 AgentRegistry::type_to_name(&AgentType::Orchestrator),
492 "orchestrator"
493 );
494 }
495
496 #[test]
497 fn test_registry_register_and_get() {
498 let provider_registry = create_test_provider_registry();
499 let tools = empty_tools();
500 let mut registry = AgentRegistry::new(provider_registry, tools);
501
502 let config = AgentConfig {
503 model: "default".to_string(),
504 system_prompt: Some("Test prompt".to_string()),
505 tools: vec![],
506 max_tool_iterations: 5,
507 parallel_tools: false,
508 extra: HashMap::new(),
509 allowed_tools: None,
510 compaction_enabled: None,
511 };
512
513 registry.register("test-agent", config);
514
515 assert!(registry.has_agent("test-agent"));
516 assert!(!registry.has_agent("nonexistent"));
517 assert!(registry.get_config("test-agent").is_some());
518 assert!(registry.get_config("nonexistent").is_none());
519 }
520
521 #[test]
522 fn test_registry_agent_names() {
523 let provider_registry = create_test_provider_registry();
524 let tools = empty_tools();
525 let mut registry = AgentRegistry::new(provider_registry, tools);
526
527 registry.register(
528 "agent1",
529 AgentConfig {
530 model: "default".to_string(),
531 system_prompt: None,
532 tools: vec![],
533 max_tool_iterations: 10,
534 parallel_tools: false,
535 extra: HashMap::new(),
536 allowed_tools: None,
537 compaction_enabled: None,
538 },
539 );
540
541 registry.register(
542 "agent2",
543 AgentConfig {
544 model: "default".to_string(),
545 system_prompt: None,
546 tools: vec![],
547 max_tool_iterations: 10,
548 parallel_tools: false,
549 extra: HashMap::new(),
550 allowed_tools: None,
551 compaction_enabled: None,
552 },
553 );
554
555 let names = registry.agent_names();
556 assert_eq!(names.len(), 2);
557 assert!(names.contains(&"agent1".to_string()));
558 assert!(names.contains(&"agent2".to_string()));
559 }
560
561 #[test]
562 fn test_registry_get_agent_model() {
563 let provider_registry = create_test_provider_registry();
564 let tools = empty_tools();
565 let mut registry = AgentRegistry::new(provider_registry, tools);
566
567 registry.register(
568 "test",
569 AgentConfig {
570 model: "default".to_string(),
571 system_prompt: None,
572 tools: vec![],
573 max_tool_iterations: 10,
574 parallel_tools: false,
575 extra: HashMap::new(),
576 allowed_tools: None,
577 compaction_enabled: None,
578 },
579 );
580
581 assert_eq!(
582 registry.get_agent_model("test"),
583 Some("default".to_string())
584 );
585 assert_eq!(registry.get_agent_model("nonexistent"), None);
586 }
587
588 #[test]
589 fn test_registry_get_agent_tools() {
590 let provider_registry = create_test_provider_registry();
591 let tools = empty_tools();
592 let mut registry = AgentRegistry::new(provider_registry, tools);
593
594 registry.register(
595 "with_tools",
596 AgentConfig {
597 model: "default".to_string(),
598 system_prompt: None,
599 tools: vec!["calculator".to_string(), "web_search".to_string()],
600 max_tool_iterations: 10,
601 parallel_tools: false,
602 extra: HashMap::new(),
603 allowed_tools: None,
604 compaction_enabled: None,
605 },
606 );
607
608 registry.register(
609 "no_tools",
610 AgentConfig {
611 model: "default".to_string(),
612 system_prompt: None,
613 tools: vec![],
614 max_tool_iterations: 10,
615 parallel_tools: false,
616 extra: HashMap::new(),
617 allowed_tools: None,
618 compaction_enabled: None,
619 },
620 );
621
622 let tools = registry.get_agent_tools("with_tools");
623 assert_eq!(tools.len(), 2);
624 assert!(tools.contains(&"calculator".to_string()));
625
626 let no_tools = registry.get_agent_tools("no_tools");
627 assert!(no_tools.is_empty());
628 }
629
630 #[test]
631 fn test_builder_build_without_provider_registry() {
632 let result = AgentRegistryBuilder::new()
633 .with_tools(empty_tools())
634 .build();
635
636 assert!(result.is_err());
637 }
638
639 #[test]
640 fn test_builder_build_success() {
641 let provider_registry = create_test_provider_registry();
642
643 let result = AgentRegistryBuilder::new()
644 .with_provider_registry(provider_registry)
645 .with_agent(
646 "test",
647 AgentConfig {
648 model: "default".to_string(),
649 system_prompt: Some("Test".to_string()),
650 tools: vec![],
651 max_tool_iterations: 5,
652 parallel_tools: false,
653 extra: HashMap::new(),
654 allowed_tools: None,
655 compaction_enabled: None,
656 },
657 )
658 .build();
659
660 assert!(result.is_ok());
661 let _registry = result.unwrap();
662 }
663
664 #[test]
669 fn test_type_to_name_custom() {
670 assert_eq!(
671 AgentRegistry::type_to_name(&AgentType::Custom("my-custom-agent".to_string())),
672 "my-custom-agent"
673 );
674 }
675
676 #[test]
681 fn test_registry_from_config() {
682 let provider_registry = create_test_provider_registry();
683 let tools = empty_tools();
684
685 let overlay_config = {
686 let mut map = HashMap::new();
687 map.insert(
688 "toml-agent".to_string(),
689 AgentConfig {
690 model: "default".to_string(),
691 system_prompt: Some("TOML prompt".to_string()),
692 tools: vec!["calculator".to_string()],
693 max_tool_iterations: 5,
694 parallel_tools: false,
695 extra: HashMap::new(),
696 allowed_tools: None,
697 compaction_enabled: None,
698 },
699 );
700 map
701 };
702
703 let registry = AgentRegistry::from_config(overlay_config, provider_registry, tools);
704
705 assert!(registry.has_agent("toml-agent"));
706 assert!(registry.get_config("toml-agent").is_some());
707 assert_eq!(
708 registry.get_agent_model("toml-agent"),
709 Some("default".to_string())
710 );
711 }
712
713 #[test]
718 fn test_builder_from_config_with_provider() {
719 let provider_registry = create_test_provider_registry();
720
721 let overlay_config = {
722 let mut map = HashMap::new();
723 map.insert(
724 "builder-agent".to_string(),
725 AgentConfig {
726 model: "default".to_string(),
727 system_prompt: Some("Builder prompt".to_string()),
728 tools: vec![],
729 max_tool_iterations: 10,
730 parallel_tools: true,
731 extra: HashMap::new(),
732 allowed_tools: None,
733 compaction_enabled: None,
734 },
735 );
736 map
737 };
738
739 let result = AgentRegistryBuilder::new()
740 .from_config(overlay_config)
741 .with_provider_registry(provider_registry)
742 .build();
743
744 assert!(result.is_ok());
745 let registry = result.unwrap();
746 assert!(registry.has_agent("builder-agent"));
747 }
748
749 #[test]
754 fn test_builder_with_tools_explicit() {
755 let provider_registry = create_test_provider_registry();
756 let custom_tools = empty_tools();
757
758 let result = AgentRegistryBuilder::new()
759 .with_provider_registry(provider_registry)
760 .with_tools(custom_tools.clone())
761 .with_agent(
762 "tool-agent",
763 AgentConfig {
764 model: "default".to_string(),
765 system_prompt: None,
766 tools: vec!["calculator".to_string()],
767 max_tool_iterations: 5,
768 parallel_tools: false,
769 extra: HashMap::new(),
770 allowed_tools: None,
771 compaction_enabled: None,
772 },
773 )
774 .build();
775
776 assert!(result.is_ok());
777 let registry = result.unwrap();
778 assert!(registry.has_agent("tool-agent"));
779 }
780
781 #[test]
782 fn test_builder_default_tools() {
783 let provider_registry = create_test_provider_registry();
784
785 let result = AgentRegistryBuilder::new()
787 .with_provider_registry(provider_registry)
788 .with_agent(
789 "no-tool-registry-agent",
790 AgentConfig {
791 model: "default".to_string(),
792 system_prompt: Some("No explicit tool registry".to_string()),
793 tools: vec![],
794 max_tool_iterations: 5,
795 parallel_tools: false,
796 extra: HashMap::new(),
797 allowed_tools: None,
798 compaction_enabled: None,
799 },
800 )
801 .build();
802
803 assert!(result.is_ok());
804 let registry = result.unwrap();
805 assert!(registry.has_agent("no-tool-registry-agent"));
806 }
807
808 #[test]
813 fn test_builder_default_is_new() {
814 let from_new = AgentRegistryBuilder::new();
815 let from_default = AgentRegistryBuilder::default();
816
817 assert!(from_new.build().is_err());
819 assert!(from_default.build().is_err());
820 }
821
822 #[test]
827 fn test_get_agent_system_prompt_toml_some() {
828 let provider_registry = create_test_provider_registry();
829 let tools = empty_tools();
830 let mut registry = AgentRegistry::new(provider_registry, tools);
831
832 registry.register(
833 "has-prompt",
834 AgentConfig {
835 model: "default".to_string(),
836 system_prompt: Some("My System Prompt".to_string()),
837 tools: vec![],
838 max_tool_iterations: 5,
839 parallel_tools: false,
840 extra: HashMap::new(),
841 allowed_tools: None,
842 compaction_enabled: None,
843 },
844 );
845
846 assert_eq!(
847 registry.get_agent_system_prompt("has-prompt"),
848 Some("My System Prompt".to_string())
849 );
850 }
851
852 #[test]
853 fn test_get_agent_system_prompt_toml_none() {
854 let provider_registry = create_test_provider_registry();
855 let tools = empty_tools();
856 let mut registry = AgentRegistry::new(provider_registry, tools);
857
858 registry.register(
859 "no-prompt",
860 AgentConfig {
861 model: "default".to_string(),
862 system_prompt: None,
863 tools: vec![],
864 max_tool_iterations: 5,
865 parallel_tools: false,
866 extra: HashMap::new(),
867 allowed_tools: None,
868 compaction_enabled: None,
869 },
870 );
871
872 assert_eq!(registry.get_agent_system_prompt("no-prompt"), None);
873 assert_eq!(registry.get_agent_system_prompt("missing"), None);
874 }
875
876 #[tokio::test]
881 async fn test_create_agent_not_found() {
882 let provider_registry = create_test_provider_registry();
883 let tools = empty_tools();
884 let registry = AgentRegistry::new(provider_registry, tools);
885
886 let result = registry.create_agent("nonexistent-agent").await;
887
888 assert!(result.is_err());
889 if let Err(AppError::Configuration(msg)) = result {
890 assert!(msg.contains("nonexistent-agent"));
891 assert!(msg.contains("not found"));
892 } else {
893 panic!("Expected Configuration error");
894 }
895 }
896
897 #[tokio::test]
902 async fn test_create_agent_by_type_not_registered() {
903 let provider_registry = create_test_provider_registry();
904 let tools = empty_tools();
905 let registry = AgentRegistry::new(provider_registry, tools);
906
907 let result = registry
909 .create_agent_by_type(AgentType::Custom("custom-unregistered".to_string()))
910 .await;
911
912 assert!(result.is_err());
913 if let Err(AppError::Configuration(msg)) = result {
914 assert!(msg.contains("custom-unregistered"));
915 } else {
916 panic!("Expected Configuration error");
917 }
918 }
919
920 fn create_test_dynamic_config_manager() -> Arc<dyn ToonAgents> {
925 let mut agents = HashMap::new();
926 agents.insert(
927 "toon-only-agent".to_string(),
928 AgentConfig {
929 model: "default".to_string(),
930 system_prompt: Some("TOON system prompt".to_string()),
931 tools: vec!["calculator".to_string()],
932 allowed_tools: None,
933 max_tool_iterations: 10,
934 parallel_tools: false,
935 extra: HashMap::new(),
936 compaction_enabled: None,
937 },
938 );
939 Arc::new(MapToon(agents))
940 }
941
942 #[test]
943 fn test_with_dynamic_config() {
944 let provider_registry = create_test_provider_registry();
945 let tools = empty_tools();
946 let dcm = create_test_dynamic_config_manager();
947
948 let registry = AgentRegistry::with_dynamic_config(
949 create_test_agent_map(),
950 provider_registry,
951 tools,
952 dcm.clone(),
953 );
954
955 assert!(registry.get_toon_config("toon-only-agent").is_some());
956 assert!(registry.has_agent("toon-only-agent"));
957 }
958
959 #[test]
960 fn test_set_dynamic_config() {
961 let provider_registry = create_test_provider_registry();
962 let tools = empty_tools();
963 let dcm = create_test_dynamic_config_manager();
964
965 let mut registry = AgentRegistry::new(provider_registry, tools);
966 assert!(!registry.has_agent("toon-only-agent")); registry.set_dynamic_config(dcm);
969 assert!(registry.has_agent("toon-only-agent"));
970 assert!(registry.get_toon_config("toon-only-agent").is_some());
971 }
972
973 #[test]
974 fn test_toon_merge_agent_names_no_duplicates() {
975 let provider_registry = create_test_provider_registry();
976 let tools = empty_tools();
977 let dcm = create_test_dynamic_config_manager();
978
979 let mut registry = AgentRegistry::new(provider_registry, tools);
980 registry.set_dynamic_config(dcm);
981
982 registry.register(
984 "toon-only-agent",
985 AgentConfig {
986 model: "default".to_string(),
987 system_prompt: Some("TOML override".to_string()),
988 tools: vec![],
989 max_tool_iterations: 5,
990 parallel_tools: false,
991 extra: HashMap::new(),
992 allowed_tools: None,
993 compaction_enabled: None,
994 },
995 );
996
997 let names = registry.agent_names();
998 let count = names.iter().filter(|n| *n == "toon-only-agent").count();
999 assert_eq!(count, 1, "Should not have duplicate agent names");
1000 assert!(registry.has_agent("toon-only-agent"));
1001 }
1002
1003 #[test]
1004 fn test_get_agent_model_toon() {
1005 let provider_registry = create_test_provider_registry();
1006 let tools = empty_tools();
1007 let dcm = create_test_dynamic_config_manager();
1008
1009 let registry = AgentRegistry::with_dynamic_config(
1010 create_test_agent_map(),
1011 provider_registry,
1012 tools,
1013 dcm,
1014 );
1015
1016 assert_eq!(
1018 registry.get_agent_model("toon-only-agent"),
1019 Some("default".to_string())
1020 );
1021 }
1022
1023 #[test]
1024 fn test_get_agent_tools_toon() {
1025 let provider_registry = create_test_provider_registry();
1026 let tools = empty_tools();
1027 let dcm = create_test_dynamic_config_manager();
1028
1029 let registry = AgentRegistry::with_dynamic_config(
1030 create_test_agent_map(),
1031 provider_registry,
1032 tools,
1033 dcm,
1034 );
1035
1036 let tools = registry.get_agent_tools("toon-only-agent");
1038 assert_eq!(tools, vec!["calculator".to_string()]);
1039 }
1040
1041 #[test]
1042 fn test_get_agent_system_prompt_toon() {
1043 let provider_registry = create_test_provider_registry();
1044 let tools = empty_tools();
1045 let dcm = create_test_dynamic_config_manager();
1046
1047 let registry = AgentRegistry::with_dynamic_config(
1048 create_test_agent_map(),
1049 provider_registry,
1050 tools,
1051 dcm,
1052 );
1053
1054 assert_eq!(
1056 registry.get_agent_system_prompt("toon-only-agent"),
1057 Some("TOON system prompt".to_string())
1058 );
1059 }
1060}
1061
1062impl cordis::Service for AgentRegistry {
1063 fn name(&self) -> &'static str { "agent_registry" }
1064 fn init(&self, _ctx: &std::sync::Arc<cordis::Context>) -> cordis::ServiceInitFuture<'_> {
1065 Box::pin(async { Ok(None) })
1066 }
1067 fn check(&self) -> bool { true }
1068}
1069