package cc.unitmesh.agent.parser /** * Parser for DevIn blocks in LLM responses * Handles extraction and processing of ... blocks */ class DevinBlockParser { /** * Extract all DevIn blocks from text */ fun extractDevinBlocks(text: String): List { val blocks = mutableListOf() val devinRegex = Regex("([\\s\\S]*?)", RegexOption.MULTILINE) val matches = devinRegex.findAll(text) for ((index, match) in matches.withIndex()) { val content = match.groupValues[1].trim() val startOffset = match.range.first val endOffset = match.range.last blocks.add(DevinBlock( id = "devin_block_$index", content = content, startOffset = startOffset, endOffset = endOffset, rawMatch = match.value )) } return blocks } /** * Check if text contains incomplete DevIn blocks */ fun hasIncompleteDevinBlock(text: String): Boolean { val openTags = text.count { it == '<' && text.indexOf("", text.indexOf(it)) == text.indexOf(it) } val closeTags = text.count { it == '<' && text.indexOf("", text.indexOf(it)) == text.indexOf(it) } return openTags > closeTags } /** * Remove DevIn blocks from text, leaving only the reasoning content */ fun filterDevinBlocks(text: String): String { val devinRegex = Regex("([\\s\\S]*?)", RegexOption.MULTILINE) return devinRegex.replace(text, "") } /** * Extract the first tool call from DevIn blocks */ fun extractFirstToolCall(blocks: List): ToolCallInfo? { for (block in blocks) { val toolCall = parseToolCallFromBlock(block) if (toolCall != null) { return toolCall } } return null } /** * Parse a tool call from a DevIn block */ private fun parseToolCallFromBlock(block: DevinBlock): ToolCallInfo? { val lines = block.content.lines() for (line in lines) { val trimmed = line.trim() if (trimmed.isEmpty()) continue // Check if this line starts with a tool call if (trimmed.startsWith("/")) { return parseToolCallFromLine(trimmed) } } return null } /** * Parse a tool call from a single line */ private fun parseToolCallFromLine(line: String): ToolCallInfo? { val toolPattern = Regex("""/(\w+(?:-\w+)*)(.*)""") val match = toolPattern.find(line) ?: return null val toolName = match.groups[1]?.value ?: return null val rest = match.groups[2]?.value?.trim() ?: "" return ToolCallInfo( toolName = toolName, rawParams = rest, originalLine = line ) } } /** * Represents a DevIn block extracted from text */ data class DevinBlock( val id: String, val content: String, val startOffset: Int, val endOffset: Int, val rawMatch: String ) /** * Represents a tool call extracted from a DevIn block */ data class ToolCallInfo( val toolName: String, val rawParams: String, val originalLine: String )