from abc import ABC, abstractmethod from dataclasses import dataclass, field from datetime import datetime from typing import Dict, List, Optional, Any from uuid import UUID, uuid4 from enum import Enum class VersionStatus(Enum): ACTIVE = "active" INACTIVE = "inactive" ARCHIVED = "archived" @dataclass(frozen=True) class VersionId: value: UUID @classmethod def generate(cls) -> 'VersionId': return cls(value=uuid4()) @classmethod def from_string(cls, value: str) -> 'VersionId': return cls(value=UUID(value)) def __str__(self) -> str: return str(self.value) @dataclass(frozen=True) class AgentId: value: UUID @classmethod def from_string(cls, value: str) -> 'AgentId': return cls(value=UUID(value)) def __str__(self) -> str: return str(self.value) @dataclass(frozen=True) class UserId: value: UUID @classmethod def from_string(cls, value: str) -> 'UserId': return cls(value=UUID(value)) def __str__(self) -> str: return str(self.value) @dataclass(frozen=True) class VersionNumber: value: int def __post_init__(self): if self.value < 1: raise ValueError("Version number must be positive") def next(self) -> 'VersionNumber': return VersionNumber(self.value + 1) def __str__(self) -> str: return f"v{self.value}" @dataclass(frozen=True) class SystemPrompt: value: str def __post_init__(self): if not self.value or not self.value.strip(): raise ValueError("System prompt cannot be empty") @dataclass(frozen=True) class MCPConfiguration: name: str type: str config: Dict[str, Any] = field(default_factory=dict) enabled_tools: List[str] = field(default_factory=list) def __post_init__(self): if not self.name: raise ValueError("MCP name cannot be empty") @dataclass(frozen=True) class ToolConfiguration: tools: Dict[str, Any] = field(default_factory=dict) def is_tool_enabled(self, tool_name: str) -> bool: tool_config = self.tools.get(tool_name, {}) if isinstance(tool_config, bool): return tool_config return tool_config.get('enabled', False) def get_enabled_tools(self) -> List[str]: return [ name for name, config in self.tools.items() if self.is_tool_enabled(name) ] @dataclass class AgentVersion: version_id: VersionId agent_id: AgentId version_number: VersionNumber version_name: str system_prompt: SystemPrompt configured_mcps: List[MCPConfiguration] custom_mcps: List[MCPConfiguration] tool_configuration: ToolConfiguration status: VersionStatus created_at: datetime updated_at: datetime created_by: UserId change_description: Optional[str] = None previous_version_id: Optional[VersionId] = None @property def is_active(self) -> bool: return self.status == VersionStatus.ACTIVE def activate(self) -> None: self.status = VersionStatus.ACTIVE self.updated_at = datetime.utcnow() def deactivate(self) -> None: self.status = VersionStatus.INACTIVE self.updated_at = datetime.utcnow() def archive(self) -> None: self.status = VersionStatus.ARCHIVED self.updated_at = datetime.utcnow() def to_dict(self) -> Dict[str, Any]: return { 'version_id': str(self.version_id), 'agent_id': str(self.agent_id), 'version_number': self.version_number.value, 'version_name': self.version_name, 'system_prompt': self.system_prompt.value, 'configured_mcps': [ { 'name': mcp.name, 'type': mcp.type, 'config': mcp.config, 'enabled_tools': mcp.enabled_tools } for mcp in self.configured_mcps ], 'custom_mcps': [ { 'name': mcp.name, 'type': mcp.type, 'config': mcp.config, 'enabled_tools': mcp.enabled_tools } for mcp in self.custom_mcps ], 'agentpress_tools': self.tool_configuration.tools, 'is_active': self.is_active, 'status': self.status.value, 'created_at': self.created_at.isoformat(), 'updated_at': self.updated_at.isoformat(), 'created_by': str(self.created_by), 'change_description': self.change_description, 'previous_version_id': str(self.previous_version_id) if self.previous_version_id else None }