package openai import ( "ds2api/internal/toolcall" "regexp" "strings" ) // --- XML tool call support for the streaming sieve --- //nolint:unused // kept as explicit tag inventory for future XML sieve refinements. var xmlToolCallClosingTags = []string{"", "", "", "", "", "", // Agent-style XML tags (Roo Code, Cline, etc.) "", "", "", ""} var xmlToolCallOpeningTags = []string{""}, {""}, {""}, {""}, {""}, {""}, // Agent-style: these are XML "tool call" patterns from coding agents. // They get captured → parsed. If parsing fails, the raw XML is preserved // so the caller can still see the original text. {""}, {""}, {""}, } // xmlToolCallBlockPattern matches a complete XML tool call block (wrapper or standalone). // //nolint:unused // reserved for future fast-path XML block detection. var xmlToolCallBlockPattern = regexp.MustCompile(`(?is)(\s*(?:.*?)\s*|\s*(?:.*?)\s*|]*>(?:.*?)|]*>(?:.*?)|(?:.*?)|(?:.*?)|(?:.*?)|(?:.*?))`) // xmlToolTagsToDetect is the set of XML tag prefixes used by findToolSegmentStart. var xmlToolTagsToDetect = []string{"", "", "", "", // Agent-style tags "", "", ""} // consumeXMLToolCapture tries to extract complete XML tool call blocks from captured text. func consumeXMLToolCapture(captured string, toolNames []string) (prefix string, calls []toolcall.ParsedToolCall, suffix string, ready bool) { lower := strings.ToLower(captured) // Find the FIRST matching open/close pair, preferring wrapper tags. // Tag pairs are ordered longest-first (e.g. 0 { prefixPart, suffixPart = trimWrappingJSONFence(prefixPart, suffixPart) return prefixPart, parsed, suffixPart, true } // If this block failed to become a tool call, pass it through as text. return prefixPart + xmlBlock, nil, suffixPart, true } return "", nil, "", false } // hasOpenXMLToolTag returns true if captured text contains an XML tool opening tag // whose SPECIFIC closing tag has not appeared yet. func hasOpenXMLToolTag(captured string) bool { lower := strings.ToLower(captured) for _, pair := range xmlToolCallTagPairs { if strings.Contains(lower, pair.open) { if !strings.Contains(lower, pair.close) { return true } } } return false } // findPartialXMLToolTagStart checks if the string ends with a partial XML tool tag // (e.g., "' in the tail, the tag is closed — not partial. if strings.Contains(tail, ">") { return -1 } lowerTail := strings.ToLower(tail) // Check if the tail is a prefix of any known XML tool tag. for _, tag := range xmlToolCallOpeningTags { tagWithLT := tag if !strings.HasPrefix(tagWithLT, "<") { tagWithLT = "<" + tagWithLT } if strings.HasPrefix(tagWithLT, lowerTail) { return lastLT } } return -1 }