Wrap long lines for flake8, rename extensions remove command handler to avoid Click shadowing, and drop unused migration imports.
628 lines
20 KiB
Python
628 lines
20 KiB
Python
"""Extension mechanisms for project-specific Kaizen agent customizations."""
|
|
|
|
import json
|
|
import yaml
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional, Any
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
|
|
|
|
class ExtensionType(Enum):
|
|
"""Types of agent extensions."""
|
|
|
|
CONFIGURATION_OVERLAY = "config_overlay" # Override default configurations
|
|
FUNCTIONAL_EXTENSION = "functional_extension" # Add new functionality
|
|
WORKFLOW_INTEGRATION = "workflow_integration" # Integrate with project workflows
|
|
CUSTOM_COMMANDS = "custom_commands" # Add custom commands
|
|
DATA_TRANSFORMATION = "data_transformation" # Transform data formats
|
|
ENVIRONMENT_ADAPTATION = "env_adaptation" # Adapt to specific environments
|
|
|
|
|
|
@dataclass
|
|
class AgentExtension:
|
|
"""Defines an extension to a Kaizen agent."""
|
|
|
|
name: str
|
|
base_agent: str # The Kaizen agent this extends
|
|
extension_type: ExtensionType
|
|
description: str
|
|
version: str = "1.0.0"
|
|
author: Optional[str] = None
|
|
|
|
# Extension configuration
|
|
configuration: Dict[str, Any] = field(default_factory=dict)
|
|
custom_commands: Dict[str, str] = field(default_factory=dict)
|
|
workflow_hooks: Dict[str, str] = field(default_factory=dict)
|
|
data_transformations: Dict[str, str] = field(default_factory=dict)
|
|
environment_overrides: Dict[str, Any] = field(default_factory=dict)
|
|
|
|
# Metadata
|
|
dependencies: List[str] = field(default_factory=list)
|
|
compatibility: List[str] = field(default_factory=list) # Compatible Kaizen versions
|
|
enabled: bool = True
|
|
|
|
|
|
@dataclass
|
|
class ProjectExtensionRegistry:
|
|
"""Registry of extensions for a project."""
|
|
|
|
project_path: Path
|
|
extensions: List[AgentExtension] = field(default_factory=list)
|
|
global_config: Dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
class ExtensionManager:
|
|
"""Manages agent extensions for projects."""
|
|
|
|
def __init__(self, project_path: Path):
|
|
self.project_path = Path(project_path)
|
|
self.extensions_dir = self.project_path / ".kaizen" / "extensions"
|
|
self.config_file = self.project_path / ".kaizen" / "extensions.yml"
|
|
|
|
def create_extension(
|
|
self,
|
|
name: str,
|
|
base_agent: str,
|
|
extension_type: ExtensionType,
|
|
description: str,
|
|
**kwargs,
|
|
) -> AgentExtension:
|
|
"""Create a new agent extension."""
|
|
extension = AgentExtension(
|
|
name=name,
|
|
base_agent=base_agent,
|
|
extension_type=extension_type,
|
|
description=description,
|
|
**kwargs,
|
|
)
|
|
|
|
self._save_extension(extension)
|
|
return extension
|
|
|
|
def install_extension(self, extension_path: Path) -> AgentExtension:
|
|
"""Install an extension from a file."""
|
|
if extension_path.suffix == ".json":
|
|
with open(extension_path) as f:
|
|
data = json.load(f)
|
|
elif extension_path.suffix in [".yml", ".yaml"]:
|
|
with open(extension_path) as f:
|
|
data = yaml.safe_load(f)
|
|
else:
|
|
raise ValueError(
|
|
f"Unsupported extension file format: {extension_path.suffix}"
|
|
)
|
|
|
|
extension = AgentExtension(**data)
|
|
self._save_extension(extension)
|
|
return extension
|
|
|
|
def list_extensions(self, base_agent: Optional[str] = None) -> List[AgentExtension]:
|
|
"""List all extensions, optionally filtered by base agent."""
|
|
extensions = self._load_extensions()
|
|
|
|
if base_agent:
|
|
return [ext for ext in extensions if ext.base_agent == base_agent]
|
|
|
|
return extensions
|
|
|
|
def get_extension(self, name: str) -> Optional[AgentExtension]:
|
|
"""Get a specific extension by name."""
|
|
extensions = self._load_extensions()
|
|
return next((ext for ext in extensions if ext.name == name), None)
|
|
|
|
def enable_extension(self, name: str) -> bool:
|
|
"""Enable an extension."""
|
|
return self._toggle_extension(name, True)
|
|
|
|
def disable_extension(self, name: str) -> bool:
|
|
"""Disable an extension."""
|
|
return self._toggle_extension(name, False)
|
|
|
|
def remove_extension(self, name: str) -> bool:
|
|
"""Remove an extension."""
|
|
extensions = self._load_extensions()
|
|
original_count = len(extensions)
|
|
extensions = [ext for ext in extensions if ext.name != name]
|
|
|
|
if len(extensions) < original_count:
|
|
self._save_extensions(extensions)
|
|
|
|
# Remove extension files
|
|
extension_dir = self.extensions_dir / name
|
|
if extension_dir.exists():
|
|
import shutil
|
|
|
|
shutil.rmtree(extension_dir)
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
def get_effective_config(self, base_agent: str) -> Dict[str, Any]:
|
|
"""Get the effective configuration for an agent with all extensions applied."""
|
|
base_config = self._get_base_agent_config(base_agent)
|
|
extensions = [
|
|
ext
|
|
for ext in self._load_extensions()
|
|
if ext.base_agent == base_agent and ext.enabled
|
|
]
|
|
|
|
# Apply extensions in order
|
|
for extension in extensions:
|
|
base_config = self._apply_extension_config(base_config, extension)
|
|
|
|
return base_config
|
|
|
|
def create_project_specific_agent(
|
|
self,
|
|
name: str,
|
|
base_agent: str,
|
|
custom_instructions: str,
|
|
custom_commands: Optional[Dict[str, str]] = None,
|
|
environment_config: Optional[Dict[str, Any]] = None,
|
|
) -> AgentExtension:
|
|
"""Create a project-specific agent based on a Kaizen agent."""
|
|
|
|
# Create extension configuration
|
|
config = {
|
|
"custom_instructions": custom_instructions,
|
|
"project_context": {
|
|
"name": self.project_path.name,
|
|
"path": str(self.project_path),
|
|
"type": self._detect_project_type(),
|
|
},
|
|
}
|
|
|
|
if environment_config:
|
|
config["environment"] = environment_config
|
|
|
|
extension = self.create_extension(
|
|
name=name,
|
|
base_agent=base_agent,
|
|
extension_type=ExtensionType.FUNCTIONAL_EXTENSION,
|
|
description=f"Project-specific extension of {base_agent} for {self.project_path.name}",
|
|
configuration=config,
|
|
custom_commands=custom_commands or {},
|
|
environment_overrides=environment_config or {},
|
|
)
|
|
|
|
# Create agent file
|
|
self._create_extended_agent_file(extension)
|
|
|
|
return extension
|
|
|
|
def integrate_legacy_agent(
|
|
self,
|
|
legacy_agent_path: Path,
|
|
target_kaizen_agent: str,
|
|
migration_strategy: str = "preserve_functionality",
|
|
) -> AgentExtension:
|
|
"""Integrate a legacy agent as an extension to a Kaizen agent."""
|
|
|
|
# Analyze legacy agent
|
|
legacy_analysis = self._analyze_legacy_agent(legacy_agent_path)
|
|
|
|
# Create extension based on analysis
|
|
extension_name = f"{legacy_agent_path.stem}_integration"
|
|
|
|
config = {
|
|
"legacy_source": str(legacy_agent_path),
|
|
"migration_strategy": migration_strategy,
|
|
"preserved_functionality": legacy_analysis.get("functionality", []),
|
|
"custom_config": legacy_analysis.get("config", {}),
|
|
}
|
|
|
|
extension = self.create_extension(
|
|
name=extension_name,
|
|
base_agent=target_kaizen_agent,
|
|
extension_type=ExtensionType.WORKFLOW_INTEGRATION,
|
|
description=f"Legacy integration of {legacy_agent_path.name}",
|
|
configuration=config,
|
|
)
|
|
|
|
# Create migration wrapper
|
|
self._create_migration_wrapper(extension, legacy_agent_path)
|
|
|
|
return extension
|
|
|
|
def _save_extension(self, extension: AgentExtension):
|
|
"""Save an extension to the registry."""
|
|
extensions = self._load_extensions()
|
|
|
|
# Remove existing extension with same name
|
|
extensions = [ext for ext in extensions if ext.name != extension.name]
|
|
extensions.append(extension)
|
|
|
|
self._save_extensions(extensions)
|
|
|
|
# Create extension directory and files
|
|
extension_dir = self.extensions_dir / extension.name
|
|
extension_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Save extension definition
|
|
with open(extension_dir / "extension.yml", "w") as f:
|
|
# Convert dataclass to dict for YAML serialization
|
|
data = {
|
|
"name": extension.name,
|
|
"base_agent": extension.base_agent,
|
|
"extension_type": extension.extension_type.value,
|
|
"description": extension.description,
|
|
"version": extension.version,
|
|
"author": extension.author,
|
|
"configuration": extension.configuration,
|
|
"custom_commands": extension.custom_commands,
|
|
"workflow_hooks": extension.workflow_hooks,
|
|
"data_transformations": extension.data_transformations,
|
|
"environment_overrides": extension.environment_overrides,
|
|
"dependencies": extension.dependencies,
|
|
"compatibility": extension.compatibility,
|
|
"enabled": extension.enabled,
|
|
}
|
|
yaml.dump(data, f, default_flow_style=False)
|
|
|
|
def _load_extensions(self) -> List[AgentExtension]:
|
|
"""Load all extensions from the registry."""
|
|
if not self.config_file.exists():
|
|
return []
|
|
|
|
try:
|
|
with open(self.config_file) as f:
|
|
data = yaml.safe_load(f) or {}
|
|
|
|
extensions = []
|
|
for ext_data in data.get("extensions", []):
|
|
# Convert string back to enum
|
|
ext_data["extension_type"] = ExtensionType(ext_data["extension_type"])
|
|
extensions.append(AgentExtension(**ext_data))
|
|
|
|
return extensions
|
|
except Exception:
|
|
return []
|
|
|
|
def _save_extensions(self, extensions: List[AgentExtension]):
|
|
"""Save extensions to the registry."""
|
|
self.config_file.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Convert to serializable format
|
|
data = {
|
|
"extensions": [
|
|
{
|
|
"name": ext.name,
|
|
"base_agent": ext.base_agent,
|
|
"extension_type": ext.extension_type.value,
|
|
"description": ext.description,
|
|
"version": ext.version,
|
|
"author": ext.author,
|
|
"configuration": ext.configuration,
|
|
"custom_commands": ext.custom_commands,
|
|
"workflow_hooks": ext.workflow_hooks,
|
|
"data_transformations": ext.data_transformations,
|
|
"environment_overrides": ext.environment_overrides,
|
|
"dependencies": ext.dependencies,
|
|
"compatibility": ext.compatibility,
|
|
"enabled": ext.enabled,
|
|
}
|
|
for ext in extensions
|
|
]
|
|
}
|
|
|
|
with open(self.config_file, "w") as f:
|
|
yaml.dump(data, f, default_flow_style=False)
|
|
|
|
def _toggle_extension(self, name: str, enabled: bool) -> bool:
|
|
"""Enable or disable an extension."""
|
|
extensions = self._load_extensions()
|
|
|
|
for extension in extensions:
|
|
if extension.name == name:
|
|
extension.enabled = enabled
|
|
self._save_extensions(extensions)
|
|
return True
|
|
|
|
return False
|
|
|
|
def _get_base_agent_config(self, base_agent: str) -> Dict[str, Any]:
|
|
"""Get the base configuration for a Kaizen agent."""
|
|
# This would load the actual agent configuration
|
|
# For now, return a basic structure
|
|
return {
|
|
"name": base_agent,
|
|
"type": "kaizen_agent",
|
|
"enabled": True,
|
|
"config": {},
|
|
}
|
|
|
|
def _apply_extension_config(
|
|
self, base_config: Dict[str, Any], extension: AgentExtension
|
|
) -> Dict[str, Any]:
|
|
"""Apply an extension's configuration to the base config."""
|
|
config = base_config.copy()
|
|
|
|
# Apply configuration overlays
|
|
if extension.configuration:
|
|
config["config"].update(extension.configuration)
|
|
|
|
# Add custom commands
|
|
if extension.custom_commands:
|
|
config.setdefault("custom_commands", {}).update(extension.custom_commands)
|
|
|
|
# Apply environment overrides
|
|
if extension.environment_overrides:
|
|
config.setdefault("environment", {}).update(extension.environment_overrides)
|
|
|
|
# Add extension metadata
|
|
config.setdefault("extensions", []).append(
|
|
{
|
|
"name": extension.name,
|
|
"type": extension.extension_type.value,
|
|
"version": extension.version,
|
|
}
|
|
)
|
|
|
|
return config
|
|
|
|
def _detect_project_type(self) -> str:
|
|
"""Detect the type of project."""
|
|
if (self.project_path / "pyproject.toml").exists():
|
|
return "python"
|
|
elif (self.project_path / "package.json").exists():
|
|
return "nodejs"
|
|
elif (self.project_path / "Cargo.toml").exists():
|
|
return "rust"
|
|
elif (self.project_path / "go.mod").exists():
|
|
return "go"
|
|
else:
|
|
return "generic"
|
|
|
|
def _analyze_legacy_agent(self, agent_path: Path) -> Dict[str, Any]:
|
|
"""Analyze a legacy agent to understand its functionality."""
|
|
analysis = {
|
|
"functionality": [],
|
|
"config": {},
|
|
"commands": [],
|
|
"dependencies": [],
|
|
}
|
|
|
|
if agent_path.suffix == ".py":
|
|
# Analyze Python agent
|
|
content = agent_path.read_text()
|
|
|
|
# Simple analysis - look for class and function definitions
|
|
import re
|
|
|
|
classes = re.findall(r"class\s+(\w+)", content)
|
|
functions = re.findall(r"def\s+(\w+)", content)
|
|
|
|
analysis["functionality"] = classes + functions
|
|
|
|
elif agent_path.suffix in [".yml", ".yaml"]:
|
|
# Analyze YAML configuration
|
|
try:
|
|
with open(agent_path) as f:
|
|
data = yaml.safe_load(f)
|
|
|
|
analysis["config"] = data
|
|
analysis["commands"] = data.get("commands", [])
|
|
|
|
except Exception:
|
|
pass
|
|
|
|
elif agent_path.suffix == ".json":
|
|
# Analyze JSON configuration
|
|
try:
|
|
with open(agent_path) as f:
|
|
data = json.load(f)
|
|
|
|
analysis["config"] = data
|
|
analysis["functionality"] = data.get("features", [])
|
|
|
|
except Exception:
|
|
pass
|
|
|
|
return analysis
|
|
|
|
def _create_extended_agent_file(self, extension: AgentExtension):
|
|
"""Create an agent file for a project-specific extension."""
|
|
agent_content = f"""---
|
|
name: {extension.name}
|
|
description: {extension.description}
|
|
base_agent: {extension.base_agent}
|
|
extension_type: {extension.extension_type.value}
|
|
version: {extension.version}
|
|
"""
|
|
|
|
if extension.author:
|
|
agent_content += f"author: {extension.author}\n"
|
|
|
|
agent_content += "---\n\n"
|
|
agent_content += f"# {extension.name}\n\n"
|
|
agent_content += f"{extension.description}\n\n"
|
|
agent_content += (
|
|
f"This agent extends **{extension.base_agent}** "
|
|
f"with project-specific functionality.\n\n"
|
|
)
|
|
|
|
if extension.configuration.get("custom_instructions"):
|
|
agent_content += "## Custom Instructions\n\n"
|
|
agent_content += f"{extension.configuration['custom_instructions']}\n\n"
|
|
|
|
if extension.custom_commands:
|
|
agent_content += "## Custom Commands\n\n"
|
|
for cmd, desc in extension.custom_commands.items():
|
|
agent_content += f"- **{cmd}**: {desc}\n"
|
|
agent_content += "\n"
|
|
|
|
# Save to agents directory
|
|
agents_dir = self.project_path / "agents"
|
|
agents_dir.mkdir(exist_ok=True)
|
|
|
|
agent_file = agents_dir / f"agent-{extension.name}.md"
|
|
agent_file.write_text(agent_content)
|
|
|
|
def _create_migration_wrapper(self, extension: AgentExtension, legacy_path: Path):
|
|
"""Create a wrapper to integrate legacy agent functionality."""
|
|
wrapper_dir = self.extensions_dir / extension.name
|
|
wrapper_file = wrapper_dir / "legacy_wrapper.py"
|
|
|
|
wrapper_content = f'''"""
|
|
Legacy agent wrapper for {extension.name}
|
|
|
|
This wrapper provides compatibility with the legacy agent at {legacy_path}
|
|
while integrating with the Kaizen agent {extension.base_agent}.
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Add legacy agent path to system path
|
|
legacy_path = Path("{legacy_path}").parent
|
|
if str(legacy_path) not in sys.path:
|
|
sys.path.insert(0, str(legacy_path))
|
|
|
|
class LegacyWrapper:
|
|
"""Wrapper for legacy agent functionality."""
|
|
|
|
def __init__(self):
|
|
self.extension_config = {extension.configuration}
|
|
self.legacy_path = Path("{legacy_path}")
|
|
|
|
def execute_legacy_functionality(self, *args, **kwargs):
|
|
"""Execute legacy agent functionality."""
|
|
# Implementation would depend on the legacy agent type
|
|
pass
|
|
|
|
def migrate_data(self, data):
|
|
"""Migrate data from legacy format to Kaizen format."""
|
|
# Implementation for data transformation
|
|
return data
|
|
'''
|
|
|
|
wrapper_file.write_text(wrapper_content)
|
|
|
|
|
|
def create_extension_template(
|
|
name: str, base_agent: str, project_path: Path, template_type: str = "basic"
|
|
) -> str:
|
|
"""Create a template for a new agent extension."""
|
|
|
|
templates = {
|
|
"basic": f"""# Extension Template: {name}
|
|
|
|
This template helps you create a custom extension for the {base_agent} agent.
|
|
|
|
## Configuration
|
|
|
|
```yaml
|
|
name: {name}
|
|
base_agent: {base_agent}
|
|
extension_type: functional_extension
|
|
description: "Custom extension for {base_agent}"
|
|
version: "1.0.0"
|
|
author: "Your Name"
|
|
|
|
configuration:
|
|
custom_setting: "value"
|
|
project_specific_config: {{}}
|
|
|
|
custom_commands:
|
|
custom-command: "Description of custom command"
|
|
|
|
environment_overrides:
|
|
CUSTOM_ENV_VAR: "value"
|
|
```
|
|
|
|
## Implementation
|
|
|
|
1. Define your custom configuration in the `configuration` section
|
|
2. Add custom commands in the `custom_commands` section
|
|
3. Override environment variables in `environment_overrides`
|
|
4. Implement any custom logic in separate Python files
|
|
|
|
## Usage
|
|
|
|
Save this as `.kaizen/extensions/{name}/extension.yml` and run:
|
|
|
|
```bash
|
|
kaizen-agentic extensions install {name}
|
|
```
|
|
""",
|
|
"advanced": f"""# Advanced Extension Template: {name}
|
|
|
|
This template provides advanced customization options for the {base_agent} agent.
|
|
|
|
## Full Configuration
|
|
|
|
```yaml
|
|
name: {name}
|
|
base_agent: {base_agent}
|
|
extension_type: workflow_integration
|
|
description: "Advanced extension with workflow integration"
|
|
version: "1.0.0"
|
|
author: "Your Name"
|
|
|
|
configuration:
|
|
# Custom instructions for the agent
|
|
custom_instructions: |
|
|
You are working on the {project_path.name} project.
|
|
Follow these project-specific guidelines:
|
|
- Use our coding standards
|
|
- Reference our documentation style
|
|
- Integrate with our CI/CD pipeline
|
|
|
|
# Project context
|
|
project_context:
|
|
name: "{project_path.name}"
|
|
type: "python" # or nodejs, rust, etc.
|
|
framework: "custom"
|
|
|
|
# Custom behaviors
|
|
behaviors:
|
|
auto_commit: false
|
|
generate_tests: true
|
|
update_docs: true
|
|
|
|
custom_commands:
|
|
project-setup: "Initialize project structure following our standards"
|
|
custom-deploy: "Deploy using our specific deployment pipeline"
|
|
generate-config: "Generate project-specific configuration files"
|
|
|
|
workflow_hooks:
|
|
pre_action: "scripts/pre_action.py"
|
|
post_action: "scripts/post_action.py"
|
|
|
|
data_transformations:
|
|
input_format: "custom"
|
|
output_format: "kaizen_standard"
|
|
|
|
environment_overrides:
|
|
PROJECT_ROOT: "{project_path}"
|
|
CUSTOM_CONFIG_PATH: "{project_path}/.config"
|
|
|
|
dependencies:
|
|
- "requests>=2.25.0"
|
|
- "pyyaml>=5.0.0"
|
|
|
|
compatibility:
|
|
- "kaizen-agentic>=0.2.0"
|
|
```
|
|
|
|
## Custom Scripts
|
|
|
|
Create these files in `.kaizen/extensions/{name}/scripts/`:
|
|
|
|
- `pre_action.py` - Run before agent actions
|
|
- `post_action.py` - Run after agent actions
|
|
- `data_transform.py` - Transform data formats
|
|
- `validation.py` - Validate project-specific requirements
|
|
|
|
## Installation
|
|
|
|
1. Save the configuration as `.kaizen/extensions/{name}/extension.yml`
|
|
2. Add any custom scripts to the scripts directory
|
|
3. Install with: `kaizen-agentic extensions install {name}`
|
|
""",
|
|
}
|
|
|
|
return templates.get(template_type, templates["basic"])
|