gateway/modules/features/codeeditor/datamodelCodeeditor.py
2026-02-23 23:01:28 +01:00

122 lines
3.7 KiB
Python

# 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