package toolcall import ( "strings" ) type ParsedToolCall struct { Name string `json:"name"` Input map[string]any `json:"input"` } type ToolCallParseResult struct { Calls []ParsedToolCall SawToolCallSyntax bool RejectedByPolicy bool RejectedToolNames []string } func ParseToolCalls(text string, availableToolNames []string) []ParsedToolCall { return ParseToolCallsDetailed(text, availableToolNames).Calls } func ParseToolCallsDetailed(text string, availableToolNames []string) ToolCallParseResult { return parseToolCallsDetailedXMLOnly(text) } func ParseStandaloneToolCalls(text string, availableToolNames []string) []ParsedToolCall { return ParseStandaloneToolCallsDetailed(text, availableToolNames).Calls } func ParseStandaloneToolCallsDetailed(text string, availableToolNames []string) ToolCallParseResult { return parseToolCallsDetailedXMLOnly(text) } func ParseAssistantToolCallsDetailed(text, thinking string, availableToolNames []string) ToolCallParseResult { textParsed := ParseStandaloneToolCallsDetailed(text, availableToolNames) if len(textParsed.Calls) > 0 { return textParsed } if strings.TrimSpace(text) != "" { return textParsed } thinkingParsed := ParseStandaloneToolCallsDetailed(thinking, availableToolNames) if len(thinkingParsed.Calls) > 0 { return thinkingParsed } return textParsed } func parseToolCallsDetailedXMLOnly(text string) ToolCallParseResult { result := ToolCallParseResult{} trimmed := strings.TrimSpace(text) if trimmed == "" { return result } result.SawToolCallSyntax = looksLikeToolCallSyntax(trimmed) trimmed = stripFencedCodeBlocks(trimmed) trimmed = strings.TrimSpace(trimmed) if trimmed == "" { return result } normalized, ok := normalizeDSMLToolCallMarkup(trimmed) if !ok { return result } parsed := parseXMLToolCalls(normalized) if len(parsed) == 0 { return result } result.SawToolCallSyntax = true calls, rejectedNames := filterToolCallsDetailed(parsed) result.Calls = calls result.RejectedToolNames = rejectedNames result.RejectedByPolicy = len(rejectedNames) > 0 && len(calls) == 0 return result } func filterToolCallsDetailed(parsed []ParsedToolCall) ([]ParsedToolCall, []string) { out := make([]ParsedToolCall, 0, len(parsed)) for _, tc := range parsed { if tc.Name == "" { continue } if tc.Input == nil { tc.Input = map[string]any{} } out = append(out, tc) } return out, nil } func looksLikeToolCallSyntax(text string) bool { lower := strings.ToLower(text) return strings.Contains(lower, "<|dsml|tool_calls") || strings.Contains(lower, "") if end < 0 { return true } pos += end + len("]]>") state = false continue } start := strings.Index(lower[pos:], "