from typing import List, Optional, Dict, Any from models import Message import re class MessageAdapter: """Converts between OpenAI message format and Claude Code prompts.""" @staticmethod def messages_to_prompt(messages: List[Message]) -> tuple[str, Optional[str]]: """ Convert OpenAI messages to Claude Code prompt format. Returns (prompt, system_prompt) """ system_prompt = None conversation_parts = [] for message in messages: if message.role == "system": # Use the last system message as the system prompt system_prompt = message.content elif message.role == "user": conversation_parts.append(f"Human: {message.content}") elif message.role == "assistant": conversation_parts.append(f"Assistant: {message.content}") # Join conversation parts prompt = "\n\n".join(conversation_parts) # If the last message wasn't from the user, add a prompt for assistant if messages and messages[-1].role != "user": prompt += "\n\nHuman: Please continue." return prompt, system_prompt @staticmethod def filter_content(content: str) -> str: """ Filter content for unsupported features and tool usage. Remove thinking blocks, tool calls, and image references. """ if not content: return content # Remove thinking blocks (common when tools are disabled but Claude tries to think) thinking_pattern = r'.*?' content = re.sub(thinking_pattern, '', content, flags=re.DOTALL) # Extract content from attempt_completion blocks (these contain the actual user response) attempt_completion_pattern = r'(.*?)' attempt_matches = re.findall(attempt_completion_pattern, content, flags=re.DOTALL) if attempt_matches: # Use the content from the attempt_completion block extracted_content = attempt_matches[0].strip() # If there's a tag inside, extract from that result_pattern = r'(.*?)' result_matches = re.findall(result_pattern, extracted_content, flags=re.DOTALL) if result_matches: extracted_content = result_matches[0].strip() if extracted_content: content = extracted_content else: # Remove other tool usage blocks (when tools are disabled but Claude tries to use them) tool_patterns = [ r'.*?', r'.*?', r'.*?', r'.*?', r'.*?', r'.*?', r'.*?', r'.*?', r'.*?', r'.*?', r'.*?', ] for pattern in tool_patterns: content = re.sub(pattern, '', content, flags=re.DOTALL) # Pattern to match image references or base64 data image_pattern = r'\[Image:.*?\]|data:image/.*?;base64,.*?(?=\s|$)' def replace_image(match): return "[Image: Content not supported by Claude Code]" content = re.sub(image_pattern, replace_image, content) # Clean up extra whitespace and newlines content = re.sub(r'\n\s*\n\s*\n', '\n\n', content) # Multiple newlines to double content = content.strip() # If content is now empty or only whitespace, provide a fallback if not content or content.isspace(): return "I understand you're testing the system. How can I help you today?" return content @staticmethod def format_claude_response(content: str, model: str, finish_reason: str = "stop") -> Dict[str, Any]: """Format Claude response for OpenAI compatibility.""" return { "role": "assistant", "content": content, "finish_reason": finish_reason, "model": model } @staticmethod def estimate_tokens(text: str) -> int: """ Rough estimation of token count. OpenAI's rule of thumb: ~4 characters per token for English text. """ return len(text) // 4