# Copyright (c) 2025 Patrick Motsch # All rights reserved. """Prompt assembly for the CodeEditor feature. Builds Cursor-style system prompts with file context and format instructions.""" import logging from typing import List, Optional, Dict, Any from modules.datamodels.datamodelAi import AiCallRequest, AiCallOptions, OperationTypeEnum from modules.features.codeeditor.datamodelCodeeditor import FileContext logger = logging.getLogger(__name__) SYSTEM_PROMPT = """You are an AI assistant for text and code file editing. You receive files as context and can suggest changes. ## Rules for file edits - Use ```file_edit``` blocks for file changes - Each file_edit block must contain: fileName, oldContent (exact text to replace), newContent (replacement text) - Explain changes in normal text before or after the block - oldContent must EXACTLY match existing content (including whitespace and indentation) - You may propose edits to multiple files in one response ## Response format Normal text is displayed as explanation. File changes must use this format: ```file_edit fileName: oldContent: | newContent: | ``` Code examples (without edits) use standard markdown code blocks: ```language code here ``` ## Important - Only edit files that are provided in context - Make minimal, targeted changes - Preserve existing formatting and style - If a task is unclear, ask for clarification instead of guessing""" def buildRequest( userPrompt: str, fileContexts: List[FileContext], chatHistory: Optional[List[Dict[str, Any]]] = None ) -> AiCallRequest: """Build an AiCallRequest with system prompt, file context, and user prompt.""" systemPart = SYSTEM_PROMPT fileContextPart = _buildFileContext(fileContexts) historyPart = _buildChatHistory(chatHistory) if chatHistory else "" fullPrompt = systemPart if historyPart: fullPrompt += f"\n\n## Previous conversation\n{historyPart}" fullPrompt += f"\n\n## User request\n{userPrompt}" return AiCallRequest( prompt=fullPrompt, context=fileContextPart if fileContextPart else None, options=AiCallOptions( operationType=OperationTypeEnum.DATA_ANALYSE, temperature=0.0, compressPrompt=False, compressContext=False, resultFormat="txt" ) ) def _buildFileContext(fileContexts: List[FileContext]) -> str: """Build the file context string with line numbers.""" if not fileContexts: return "" parts = [] for fc in fileContexts: if not fc.content: continue lines = fc.content.split("\n") numberedLines = [f"{i + 1}|{line}" for i, line in enumerate(lines)] numbered = "\n".join(numberedLines) parts.append(f"--- FILE: {fc.fileName} ---\n{numbered}\n--- END FILE ---") return "\n\n".join(parts) def buildAgentRequest( userPrompt: Optional[str], fileListContext: str, conversationHistory: List[Dict[str, Any]] ) -> AiCallRequest: """Build an AiCallRequest for agent mode with tool definitions and conversation history.""" from modules.features.codeeditor.toolRegistry import formatToolDefinitions systemPrompt = _AGENT_SYSTEM_PROMPT.replace("{{TOOL_DEFINITIONS}}", formatToolDefinitions()) if not conversationHistory: fullPrompt = systemPrompt context = f"## Available files\n{fileListContext}\n\n## Task\n{userPrompt}" else: fullPrompt = systemPrompt historyText = _buildConversationHistory(conversationHistory) context = f"## Available files\n{fileListContext}\n\n## Conversation\n{historyText}" return AiCallRequest( prompt=fullPrompt, context=context, options=AiCallOptions( operationType=OperationTypeEnum.DATA_ANALYSE, temperature=0.0, compressPrompt=False, compressContext=False, resultFormat="txt" ) ) _AGENT_SYSTEM_PROMPT = """You are an AI agent for file analysis and editing. You work autonomously by using tools to read files, search content, and propose edits. ## Available tools {{TOOL_DEFINITIONS}} ## How to call tools Use this exact format for each tool call: ```tool_call tool: args: {"param": "value"} ``` ## Rules - Read files ONE AT A TIME with read_file, never assume file contents - First create a plan, then execute it step by step - Use search_files to find relevant files before reading them - Use list_files to discover what files are available - For file changes, use ```file_edit``` blocks (same format as before) - You may combine text explanations, tool calls, and file edits in one response - When you are DONE and need no more tool calls, simply respond with text only (no tool_call blocks) - Keep responses focused and efficient ## file_edit format (for changes) ```file_edit fileName: oldContent: | newContent: | ```""" def _buildConversationHistory(history: List[Dict[str, Any]]) -> str: """Build the full conversation history for agent multi-turn context.""" parts = [] for msg in history: role = msg.get("role", "unknown") content = msg.get("content", "") if role == "tool_result": toolName = msg.get("toolName", "") parts.append(f"[Tool Result - {toolName}]:\n{content}") else: parts.append(f"[{role}]:\n{content}") return "\n\n".join(parts) def _buildChatHistory(chatHistory: List[Dict[str, Any]]) -> str: """Build a condensed chat history string for multi-turn context.""" if not chatHistory: return "" parts = [] for msg in chatHistory[-10:]: role = msg.get("role", "unknown") content = msg.get("content", "") if len(content) > 500: content = content[:500] + "..." parts.append(f"[{role}]: {content}") return "\n".join(parts)