| package toolcall |
|
|
| import ( |
| "encoding/json" |
| "html" |
| "regexp" |
| "strings" |
| ) |
|
|
| var toolCallMarkupKVPattern = regexp.MustCompile(`(?is)<(?:[a-z0-9_:-]+:)?([a-z0-9_\-.]+)\b[^>]*>(.*?)</(?:[a-z0-9_:-]+:)?([a-z0-9_\-.]+)>`) |
|
|
| func parseMarkupKVObject(text string) map[string]any { |
| matches := toolCallMarkupKVPattern.FindAllStringSubmatch(strings.TrimSpace(text), -1) |
| if len(matches) == 0 { |
| return nil |
| } |
| out := map[string]any{} |
| for _, m := range matches { |
| if len(m) < 4 { |
| continue |
| } |
| key := strings.TrimSpace(m[1]) |
| endKey := strings.TrimSpace(m[3]) |
| if key == "" { |
| continue |
| } |
| if !strings.EqualFold(key, endKey) { |
| continue |
| } |
| value := parseMarkupValue(m[2]) |
| if value == nil { |
| continue |
| } |
| appendMarkupValue(out, key, value) |
| } |
| if len(out) == 0 { |
| return nil |
| } |
| return out |
| } |
|
|
| func parseMarkupValue(inner string) any { |
| if value, ok := extractStandaloneCDATA(inner); ok { |
| return value |
| } |
| value := strings.TrimSpace(extractRawTagValue(inner)) |
| if value == "" { |
| return "" |
| } |
|
|
| if strings.Contains(value, "<") && strings.Contains(value, ">") { |
| if parsed := parseStructuredToolCallInput(value); len(parsed) > 0 { |
| if len(parsed) == 1 { |
| if raw, ok := parsed["_raw"].(string); ok { |
| return raw |
| } |
| } |
| return parsed |
| } |
| } |
|
|
| var jsonValue any |
| if json.Unmarshal([]byte(value), &jsonValue) == nil { |
| return jsonValue |
| } |
| return value |
| } |
|
|
| func appendMarkupValue(out map[string]any, key string, value any) { |
| if existing, ok := out[key]; ok { |
| switch current := existing.(type) { |
| case []any: |
| out[key] = append(current, value) |
| default: |
| out[key] = []any{current, value} |
| } |
| return |
| } |
| out[key] = value |
| } |
|
|
| |
| |
| |
| func extractRawTagValue(inner string) string { |
| trimmed := strings.TrimSpace(inner) |
| if trimmed == "" { |
| return "" |
| } |
|
|
| |
| if value, ok := extractStandaloneCDATA(trimmed); ok { |
| return value |
| } |
|
|
| |
| |
| |
| |
|
|
| |
| |
| return html.UnescapeString(inner) |
| } |
|
|
| func extractStandaloneCDATA(inner string) (string, bool) { |
| trimmed := strings.TrimSpace(inner) |
| if openLen := toolCDATAOpenLenAt(trimmed, 0); openLen > 0 { |
| if closeStart := findTrailingToolCDATACloseStart(trimmed); closeStart >= openLen { |
| return trimmed[openLen:closeStart], true |
| } |
| if end := findToolCDATAEnd(trimmed, openLen); end >= 0 { |
| return trimmed[openLen:end], true |
| } |
| return trimmed[openLen:], true |
| } |
| return "", false |
| } |
|
|
| func parseJSONLiteralValue(raw string) (any, bool) { |
| trimmed := strings.TrimSpace(raw) |
| if trimmed == "" { |
| return nil, false |
| } |
|
|
| switch trimmed[0] { |
| case '{', '[', '"', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 't', 'f', 'n': |
| default: |
| return nil, false |
| } |
|
|
| var parsed any |
| if err := json.Unmarshal([]byte(trimmed), &parsed); err != nil { |
| return nil, false |
| } |
| return parsed, true |
| } |
|
|
| |
| |
| |
| |
| func SanitizeLooseCDATA(text string) string { |
| if text == "" { |
| return "" |
| } |
|
|
| var b strings.Builder |
| b.Grow(len(text)) |
| changed := false |
| pos := 0 |
| for pos < len(text) { |
| start := indexToolCDATAOpen(text, pos) |
| if start < 0 { |
| b.WriteString(text[pos:]) |
| break |
| } |
| openLen := toolCDATAOpenLenAt(text, start) |
| contentStart := start + openLen |
| b.WriteString(text[pos:start]) |
|
|
| if endRel := findToolCDATAEnd(text, contentStart); endRel >= 0 { |
| end := endRel + toolCDATACloseLenAt(text, endRel) |
| b.WriteString(text[start:end]) |
| pos = end |
| continue |
| } |
|
|
| changed = true |
| b.WriteString(text[contentStart:]) |
| pos = len(text) |
| } |
|
|
| if !changed { |
| return text |
| } |
| return b.String() |
| } |
|
|