package parser import "strings" // BlockInfo contains information about a tool call block type BlockInfo struct { StartIndex int TagType string } // FindToolCallBlockAtEnd finds tool call block at the end of text func FindToolCallBlockAtEnd(text string) *BlockInfo { trimmed := strings.TrimRight(text, " \t\n\r") // Find first occurrence of each tool call tag firstFunctionCalls := strings.Index(trimmed, "") firstTool := strings.Index(trimmed, "") firstTools := strings.Index(trimmed, "") // Find the earliest tag firstOpenTag := -1 tagType := "" if firstFunctionCalls != -1 { firstOpenTag = firstFunctionCalls tagType = "function_calls" } if firstTool != -1 && (firstOpenTag == -1 || firstTool < firstOpenTag) { firstOpenTag = firstTool tagType = "tool" } if firstTools != -1 && (firstOpenTag == -1 || firstTools < firstOpenTag) { firstOpenTag = firstTools tagType = "tools" } if firstOpenTag == -1 { return nil } // Use stack to find matching close tag openTag := "<" + tagType + ">" closeTag := "" depth := 0 pos := firstOpenTag lastClosePos := -1 for pos < len(trimmed) { nextOpen := strings.Index(trimmed[pos:], openTag) nextClose := strings.Index(trimmed[pos:], closeTag) if nextOpen == -1 && nextClose == -1 { break } if nextOpen != -1 && (nextClose == -1 || nextOpen < nextClose) { // Found open tag depth++ pos = pos + nextOpen + len(openTag) } else { // Found close tag depth-- if depth == 0 { lastClosePos = pos + nextClose + len(closeTag) } pos = pos + nextClose + len(closeTag) } } // Check if tool call is valid if lastClosePos != -1 { // Has complete open and close tags - this is valid // Accept tool calls even if there's text after them } else if depth > 0 { // Unclosed tool call, might be in progress (streaming) // Allow this case } else { // No valid tool call found return nil } return &BlockInfo{ StartIndex: firstOpenTag, TagType: tagType, } } // HasCompleteToolCall checks if text has complete tool call func HasCompleteToolCall(text string) bool { trimmed := strings.TrimRight(text, " \t\n\r") return strings.HasSuffix(trimmed, "") || strings.HasSuffix(trimmed, "") || strings.HasSuffix(trimmed, "") } // HasIncompleteToolCall checks if text has incomplete tool call func HasIncompleteToolCall(text string) bool { trimmed := strings.TrimRight(text, " \t\n\r") // Find first occurrence of each tool call tag firstFunctionCalls := strings.Index(trimmed, "") firstTool := strings.Index(trimmed, "") firstTools := strings.Index(trimmed, "") firstOpenTag := -1 tagType := "" if firstFunctionCalls != -1 { firstOpenTag = firstFunctionCalls tagType = "function_calls" } if firstTool != -1 && (firstOpenTag == -1 || firstTool < firstOpenTag) { firstOpenTag = firstTool tagType = "tool" } if firstTools != -1 && (firstOpenTag == -1 || firstTools < firstOpenTag) { firstOpenTag = firstTools tagType = "tools" } if firstOpenTag == -1 { return false } // Check if there's a corresponding close tag closeTag := "" closeIndex := strings.LastIndex(trimmed, closeTag) // If no close tag or close tag is before open tag, tool call is incomplete return closeIndex == -1 || closeIndex < firstOpenTag }