# Copyright (c) 2025 Patrick Motsch # All rights reserved. """Data models for the CodeEditor feature.""" from typing import List, Dict, Any, Optional from enum import Enum from pydantic import BaseModel, Field from modules.shared.timeUtils import getUtcTimestamp import uuid class SegmentTypeEnum(str, Enum): TEXT = "text" CODE_BLOCK = "code_block" FILE_EDIT = "file_edit" TOOL_CALL = "tool_call" class EditStatusEnum(str, Enum): PENDING = "pending" ACCEPTED = "accepted" REJECTED = "rejected" class FileContext(BaseModel): """A text file loaded as context for the AI.""" fileId: str fileName: str content: Optional[str] = None mimeType: str sizeBytes: int = 0 modifiedAt: Optional[float] = None tags: List[str] = Field(default_factory=list) class ResponseSegment(BaseModel): """A parsed segment from the AI response.""" type: SegmentTypeEnum content: str language: Optional[str] = None fileId: Optional[str] = None fileName: Optional[str] = None oldContent: Optional[str] = None newContent: Optional[str] = None toolName: Optional[str] = None toolArgs: Optional[Dict[str, Any]] = None class FileEditProposal(BaseModel): """A proposed file edit from the AI, awaiting user accept/reject.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) workflowId: str fileId: str fileName: str operation: str = "edit" oldContent: Optional[str] = None newContent: str diffSummary: Optional[str] = None status: EditStatusEnum = EditStatusEnum.PENDING createdAt: float = Field(default_factory=getUtcTimestamp) class FileVersion(BaseModel): """A new version of a file created after accepting an edit proposal.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) sourceFileId: str editProposalId: str newFileId: str createdAt: float = Field(default_factory=getUtcTimestamp) class AgentState(BaseModel): """Tracks state across an agent loop execution.""" workflowId: str currentRound: int = 0 maxRounds: int = 50 totalAiCalls: int = 0 totalToolCalls: int = 0 totalCostCHF: float = 0.0 totalProcessingTime: float = 0.0 conversationHistory: List[Dict[str, Any]] = Field(default_factory=list) status: str = "running" class ToolResult(BaseModel): """Result from executing a tool.""" toolName: str result: str success: bool = True executionTime: float = 0.0 TEXT_MIME_TYPES = { "text/plain", "text/markdown", "text/html", "text/css", "text/csv", "text/xml", "text/yaml", "text/x-python", "text/x-java", "text/javascript", "text/x-typescript", "text/x-sql", "application/json", "application/xml", "application/yaml", "application/x-yaml", "application/javascript", } TEXT_EXTENSIONS = { ".md", ".txt", ".json", ".yaml", ".yml", ".xml", ".csv", ".py", ".js", ".ts", ".tsx", ".jsx", ".html", ".htm", ".css", ".scss", ".sql", ".sh", ".bash", ".zsh", ".ps1", ".bat", ".toml", ".ini", ".cfg", ".conf", ".env", ".gitignore", ".dockerfile", ".docker-compose", ".makefile", ".java", ".kt", ".go", ".rs", ".rb", ".php", ".swift", ".c", ".cpp", ".h", ".r", ".lua", ".dart", ".vue", ".svelte", } def isTextFile(mimeType: Optional[str], fileName: Optional[str] = None) -> bool: """Check if a file is a text-based file suitable for the editor.""" if mimeType and mimeType.lower() in TEXT_MIME_TYPES: return True if mimeType and mimeType.lower().startswith("text/"): return True if fileName: ext = "." + fileName.rsplit(".", 1)[-1].lower() if "." in fileName else "" if ext in TEXT_EXTENSIONS: return True return False