package toolcall import "strings" var toolMarkupNames = []string{"tool_calls", "invoke", "parameter"} type ToolMarkupTag struct { Start int End int NameStart int NameEnd int Name string Closing bool SelfClosing bool DSMLLike bool Canonical bool } func ContainsToolMarkupSyntaxOutsideIgnored(text string) (hasDSML, hasCanonical bool) { lower := strings.ToLower(text) for i := 0; i < len(text); { next, advanced, blocked := skipXMLIgnoredSection(lower, i) if blocked { return hasDSML, hasCanonical } if advanced { i = next continue } if tag, ok := scanToolMarkupTagAt(text, i); ok { if tag.DSMLLike { hasDSML = true } else { hasCanonical = true } if hasDSML && hasCanonical { return true, true } i = tag.End + 1 continue } i++ } return hasDSML, hasCanonical } func ContainsToolCallWrapperSyntaxOutsideIgnored(text string) (hasDSML, hasCanonical bool) { lower := strings.ToLower(text) for i := 0; i < len(text); { next, advanced, blocked := skipXMLIgnoredSection(lower, i) if blocked { return hasDSML, hasCanonical } if advanced { i = next continue } if tag, ok := scanToolMarkupTagAt(text, i); ok { if tag.Name != "tool_calls" { i = tag.End + 1 continue } if tag.DSMLLike { hasDSML = true } else { hasCanonical = true } if hasDSML && hasCanonical { return true, true } i = tag.End + 1 continue } i++ } return hasDSML, hasCanonical } func FindToolMarkupTagOutsideIgnored(text string, start int) (ToolMarkupTag, bool) { lower := strings.ToLower(text) for i := maxInt(start, 0); i < len(text); { next, advanced, blocked := skipXMLIgnoredSection(lower, i) if blocked { return ToolMarkupTag{}, false } if advanced { i = next continue } if tag, ok := scanToolMarkupTagAt(text, i); ok { return tag, true } i++ } return ToolMarkupTag{}, false } func FindMatchingToolMarkupClose(text string, open ToolMarkupTag) (ToolMarkupTag, bool) { if text == "" || open.Name == "" || open.Closing { return ToolMarkupTag{}, false } depth := 1 for pos := open.End + 1; pos < len(text); { tag, ok := FindToolMarkupTagOutsideIgnored(text, pos) if !ok { return ToolMarkupTag{}, false } if tag.Name != open.Name { pos = tag.End + 1 continue } if tag.Closing { depth-- if depth == 0 { return tag, true } } else if !tag.SelfClosing { depth++ } pos = tag.End + 1 } return ToolMarkupTag{}, false } func scanToolMarkupTagAt(text string, start int) (ToolMarkupTag, bool) { if start < 0 || start >= len(text) || text[start] != '<' { return ToolMarkupTag{}, false } lower := strings.ToLower(text) i := start + 1 closing := false if i < len(text) && text[i] == '/' { closing = true i++ } dsmlLike := false if next, ok := consumeToolMarkupPipe(text, i); ok { dsmlLike = true i = next } if strings.HasPrefix(lower[i:], "dsml") { dsmlLike = true i += len("dsml") for next, ok := consumeToolMarkupSeparator(text, i); ok; next, ok = consumeToolMarkupSeparator(text, i) { i = next } } name, nameLen := matchToolMarkupName(lower, i) if nameLen == 0 { return ToolMarkupTag{}, false } nameEnd := i + nameLen if !hasToolMarkupBoundary(text, nameEnd) { return ToolMarkupTag{}, false } end := findXMLTagEnd(text, nameEnd) if end < 0 { return ToolMarkupTag{}, false } trimmed := strings.TrimSpace(text[start : end+1]) return ToolMarkupTag{ Start: start, End: end, NameStart: i, NameEnd: nameEnd, Name: name, Closing: closing, SelfClosing: strings.HasSuffix(trimmed, "/>"), DSMLLike: dsmlLike, Canonical: !dsmlLike, }, true } func matchToolMarkupName(lower string, start int) (string, int) { for _, name := range toolMarkupNames { if strings.HasPrefix(lower[start:], name) { return name, len(name) } } return "", 0 } func consumeToolMarkupPipe(text string, idx int) (int, bool) { if idx >= len(text) { return idx, false } if text[idx] == '|' { return idx + 1, true } if strings.HasPrefix(text[idx:], "|") { return idx + len("|"), true } return idx, false } func consumeToolMarkupSeparator(text string, idx int) (int, bool) { if idx >= len(text) { return idx, false } if text[idx] == ' ' || text[idx] == '\t' || text[idx] == '\r' || text[idx] == '\n' { return idx + 1, true } if next, ok := consumeToolMarkupPipe(text, idx); ok { return next, true } return idx, false } func hasToolMarkupBoundary(text string, idx int) bool { if idx >= len(text) { return true } switch text[idx] { case ' ', '\t', '\n', '\r', '>', '/': return true default: return false } }