config_generator.py•11.3 kB
"""Configuration file generator for orchestrators and delegation rules.
This module generates YAML configuration files based on user selections
during installation.
"""
import logging
import shutil
from datetime import datetime
from pathlib import Path
from typing import Any
import yaml
from rich.console import Console
from rich.prompt import Confirm
from ..agent_discovery import AgentMetadata
from .agent_profiles import get_agent_profile
from .task_mapper import TASK_CATEGORIES
logger = logging.getLogger(__name__)
console = Console()
class ConfigGenerator:
"""Generates configuration files based on user selections."""
def __init__(self):
"""Initialize the config generator."""
self.orchestrators_config: dict[str, Any] = {}
self.delegation_config: dict[str, Any] = {}
def check_existing_configs(self, project_dir: Path) -> bool:
"""
Check if configuration files already exist.
Args:
project_dir: Project directory path
Returns:
True if configs exist, False otherwise
"""
config_dir = project_dir / "config"
orchestrators_file = config_dir / "orchestrators.yaml"
delegation_file = config_dir / "delegation_rules.yaml"
return orchestrators_file.exists() or delegation_file.exists()
def prompt_existing_configs(self, project_dir: Path) -> str:
"""
Prompt user for how to handle existing configs.
Args:
project_dir: Project directory path
Returns:
User choice: "overwrite", "backup", or "skip"
"""
console.print("\n[yellow]⚠ Existing configuration detected[/yellow]")
console.print("\nConfiguration files already exist in this project.")
console.print("You can:")
console.print(" [cyan]O[/cyan]verwrite - Replace existing files with new configuration")
console.print(" [cyan]B[/cyan]ackup - Backup existing files and create new ones")
console.print(" [cyan]S[/cyan]kip - Keep existing files unchanged\n")
choice = ""
while choice not in ["o", "b", "s"]:
choice = input("Choose an option (O/B/S): ").lower().strip()
choice_map = {"o": "overwrite", "b": "backup", "s": "skip"}
return choice_map[choice]
def backup_existing_configs(self, project_dir: Path) -> None:
"""
Backup existing configuration files.
Args:
project_dir: Project directory path
"""
config_dir = project_dir / "config"
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
files_to_backup = [
"orchestrators.yaml",
"delegation_rules.yaml",
]
for filename in files_to_backup:
source = config_dir / filename
if source.exists():
backup_name = f"{source.stem}.backup_{timestamp}{source.suffix}"
backup_path = config_dir / backup_name
shutil.copy2(source, backup_path)
console.print(f"[green]✓[/green] Backed up {filename} to {backup_name}")
def generate_orchestrators_yaml(
self,
selected_agents: list[str],
agent_metadata: dict[str, AgentMetadata],
) -> dict[str, Any]:
"""
Generate orchestrators.yaml configuration.
Args:
selected_agents: List of selected agent names
agent_metadata: Dictionary of agent name to metadata
Returns:
Orchestrators configuration dictionary
"""
config: dict[str, Any] = {"orchestrators": {}}
for agent_name in selected_agents:
metadata = agent_metadata.get(agent_name)
if not metadata:
continue
# Get command and args from metadata
# command can be a string or list[str]
if isinstance(metadata.command, list):
command = metadata.command[0] if metadata.command else agent_name
args = metadata.command[1:] if len(metadata.command) > 1 else []
else:
command = metadata.command or agent_name
args = []
# Build orchestrator config
orch_config: dict[str, Any] = {
"name": agent_name,
"command": command,
"args": args,
"enabled": True,
"env": {},
"timeout": 300,
"max_retries": 3,
}
config["orchestrators"][agent_name] = orch_config
self.orchestrators_config = config
return config
def generate_delegation_rules_yaml(
self,
selected_agents: list[str],
task_mappings: dict[str, str],
agent_metadata: dict[str, AgentMetadata],
primary_orchestrator: str,
) -> dict[str, Any]:
"""
Generate delegation_rules.yaml configuration.
Args:
selected_agents: List of selected agent names
task_mappings: Dictionary of task_key -> agent_name
agent_metadata: Dictionary of agent name to metadata
primary_orchestrator: Name of primary orchestrator
Returns:
Delegation rules configuration dictionary
"""
config: dict[str, Any] = {
"orchestrator": primary_orchestrator,
"routing_strategy": "hybrid",
"orchestrators": {},
"rules": [],
}
# Build orchestrators section with capabilities
for agent_name in selected_agents:
metadata = agent_metadata.get(agent_name)
if not metadata:
continue
profile = get_agent_profile(agent_name)
capabilities = profile["capabilities"]
# Get command and args from metadata
# command can be a string or list[str]
if isinstance(metadata.command, list):
command = metadata.command[0] if metadata.command else agent_name
args = metadata.command[1:] if len(metadata.command) > 1 else []
else:
command = metadata.command or agent_name
args = []
orch_config: dict[str, Any] = {
"name": agent_name,
"command": command,
"args": args,
"enabled": True,
"env": {},
"timeout": 300,
"max_retries": 3,
"cost_per_1k_tokens": 0.001,
"capabilities": dict(capabilities),
}
config["orchestrators"][agent_name] = orch_config
# Build delegation rules from task mappings
# Create task key to category mapping
category_map = {cat["key"]: cat for cat in TASK_CATEGORIES}
priority = 10 # Start with high priority
for task_key, agent_name in task_mappings.items():
category = category_map.get(task_key)
if not category:
continue
# Build pattern from examples
pattern_parts = category["pattern_examples"]
pattern = "|".join(pattern_parts)
rule: dict[str, Any] = {
"delegate_to": agent_name,
"description": category["description"],
"pattern": pattern,
"priority": max(1, priority),
"requires_approval": False,
}
config["rules"].append(rule)
priority -= 1 # Decrease priority for next rule
# Add fallback rule (general tasks)
if "general" not in task_mappings:
# Use primary orchestrator as fallback
fallback_agent = primary_orchestrator
else:
fallback_agent = task_mappings["general"]
fallback_rule: dict[str, Any] = {
"delegate_to": fallback_agent,
"description": "General queries and fallback",
"pattern": ".*",
"priority": 1,
"requires_approval": False,
}
config["rules"].append(fallback_rule)
self.delegation_config = config
return config
def save_configs(self, project_dir: Path) -> None:
"""
Save configuration files to disk.
Args:
project_dir: Project directory path
"""
config_dir = project_dir / "config"
config_dir.mkdir(parents=True, exist_ok=True)
# Save orchestrators.yaml
orchestrators_file = config_dir / "orchestrators.yaml"
with open(orchestrators_file, "w", encoding="utf-8") as f:
yaml.dump(
self.orchestrators_config,
f,
default_flow_style=False,
sort_keys=False,
)
console.print(f"[green]✓[/green] Generated {orchestrators_file}")
# Save delegation_rules.yaml
delegation_file = config_dir / "delegation_rules.yaml"
with open(delegation_file, "w", encoding="utf-8") as f:
# Add header comment
f.write("# Delegation MCP Configuration\n")
f.write("# Auto-generated based on user selections\n\n")
yaml.dump(
self.delegation_config,
f,
default_flow_style=False,
sort_keys=False,
)
console.print(f"[green]✓[/green] Generated {delegation_file}")
def generate_configs(
self,
selected_agents: list[str],
task_mappings: dict[str, str],
agent_metadata: dict[str, AgentMetadata],
primary_orchestrator: str,
project_dir: Path,
scope: str = "local",
) -> None:
"""
Complete configuration generation flow.
Args:
selected_agents: List of selected agent names
task_mappings: Dictionary of task_key -> agent_name
agent_metadata: Dictionary of agent name to metadata
primary_orchestrator: Name of primary orchestrator
project_dir: Project directory path
scope: Installation scope - "local" for project-level, "user" for user-level
"""
# Determine config directory based on scope
if scope == "user":
config_base_dir = Path.home() / ".delegation-mcp"
else:
config_base_dir = project_dir
# Check for existing configs
if self.check_existing_configs(config_base_dir):
choice = self.prompt_existing_configs(config_base_dir)
if choice == "skip":
console.print("\n[yellow]⚠[/yellow] Keeping existing configuration files\n")
return
elif choice == "backup":
self.backup_existing_configs(config_base_dir)
# Generate configurations
self.generate_orchestrators_yaml(selected_agents, agent_metadata)
self.generate_delegation_rules_yaml(
selected_agents, task_mappings, agent_metadata, primary_orchestrator
)
# Save to disk
self.save_configs(config_base_dir)
console.print("\n[green]✓[/green] Configuration files generated successfully\n")