from datetime import datetime import uuid import xml.etree.ElementTree as ET from typing import Union from .command import Command from .delete_command import DeleteCommand from .stop_command import StopCommand from .entry import Entry from .background_entry import BackgroundEntry from .parse_error_entry import ParseErrorEntry from .read_entry import ReadEntry from .reasoning_entry import ReasoningEntry from .repeat_entry import RepeatEntry from .single_entry import SingleEntry from .write_entry import WriteEntry class ResponseParser: """ Parses XML responses from the LLM into commands or entries. The parser validates the XML structure and converts it into the appropriate Command or Entry object based on the root element tag. Invalid input results in ParseErrorEntry objects rather than raising exceptions. Attributes: io_buffer: Buffer to use for IO operations """ def __init__(self, io_buffer): """ Initialize parser with IO buffer. Args: io_buffer: Buffer to use for IO operations """ self.io_buffer = io_buffer def _extract_content(self, xml: str, root: ET.Element) -> str: """ Extract content from XML, handling CDATA sections and preserving special characters. Args: xml: Original XML string root: Parsed XML element Returns: str: Extracted content with proper handling of CDATA and special characters """ if root.text is None: return "" # Find opening tag end tag_name = root.tag tag_start = xml.find('<' + tag_name) tag_end = xml.find('>', tag_start) if tag_end == -1: return "" # Check for self-closing tag if xml[tag_end-1] == '/': return "" # Find closing tag start close_tag = '' close_start = xml.rfind(close_tag) if close_start == -1: return "" # Extract content content = xml[tag_end+1:close_start] # Handle CDATA sections if content.startswith(''): content = content[9:-3] # Remove CDATA markers return content def parse(self, xml: str) -> Union[Command, Entry]: """ Parse XML response into a Command or Entry. Args: xml: XML string to parse Returns: Command or Entry based on the XML content ParseErrorEntry for any parsing or validation errors """ entry_id = str(uuid.uuid4()) timestamp = datetime.now() # First try to parse the XML try: root = ET.fromstring(xml.strip()) except ET.ParseError as e: return ParseErrorEntry(xml, f"Invalid XML: {str(e)}", entry_id, timestamp) # Extract content preserving special characters and handling CDATA content = self._extract_content(xml, root) # Convert to appropriate type based on root tag try: if root.tag == 'delete': # Extract target ID and create delete command target_id = root.get('id') if not target_id: return ParseErrorEntry(xml, "Delete command missing required 'id' attribute", entry_id, timestamp) return DeleteCommand(target_id) elif root.tag == 'stop': # Create stop command return StopCommand() elif root.tag == 'background': # Create background script entry if not content: return ParseErrorEntry(xml, "Background entry missing script content", entry_id, timestamp) return BackgroundEntry(content, entry_id, timestamp) elif root.tag == 'repeat': # Create repeat script entry if not content: return ParseErrorEntry(xml, "Repeat entry missing script content", entry_id, timestamp) return RepeatEntry(content, entry_id, timestamp) elif root.tag == 'single': # Create single shot script entry if not content: return ParseErrorEntry(xml, "Single shot entry missing script content", entry_id, timestamp) return SingleEntry(content, entry_id, timestamp) elif root.tag == 'reasoning': # Create reasoning entry if not content: return ParseErrorEntry(xml, "Reasoning entry missing content", entry_id, timestamp) return ReasoningEntry(content, entry_id, timestamp) elif root.tag == 'read_stdin': # Create read entry - no content required return ReadEntry(self.io_buffer, entry_id, timestamp) elif root.tag == 'write_stdout': # Create write entry if not content: return ParseErrorEntry(xml, "Write entry missing content", entry_id, timestamp) return WriteEntry(content, self.io_buffer, entry_id, timestamp) else: return ParseErrorEntry(xml, f"Unknown root element: {root.tag}", entry_id, timestamp) except Exception as e: return ParseErrorEntry(xml, f"Error parsing response: {str(e)}", entry_id, timestamp)