From c8d79c2355bf4fcd3179483cfe729dd297f257c5 Mon Sep 17 00:00:00 2001 From: dj-oyu <68707227+dj-oyu@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:03:48 +0900 Subject: [PATCH] fix(providers): strip XML tool call artifacts from response content Minimax (and potentially other providers) embed raw XML like in response Content alongside structured tool_calls. Strip these before returning to prevent leaking internal data to users. Co-Authored-By: Claude Opus 4.6 --- pkg/providers/http_provider.go | 9 ++++++++- pkg/providers/tool_call_extract.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index 967d089d5..6a731ccc7 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -23,7 +23,14 @@ func NewHTTPProvider(apiKey, apiBase, proxy string) *HTTPProvider { } func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) { - return p.delegate.Chat(ctx, messages, tools, model, options) + resp, err := p.delegate.Chat(ctx, messages, tools, model, options) + if err != nil { + return nil, err + } + // Strip provider-specific XML tool call artifacts (e.g. minimax) + // that leak into Content alongside structured tool_calls. + resp.Content = stripXMLToolCalls(resp.Content) + return resp, nil } func (p *HTTPProvider) GetDefaultModel() string { diff --git a/pkg/providers/tool_call_extract.go b/pkg/providers/tool_call_extract.go index 97a219283..7436cb908 100644 --- a/pkg/providers/tool_call_extract.go +++ b/pkg/providers/tool_call_extract.go @@ -56,6 +56,35 @@ func extractToolCallsFromText(text string) []ToolCall { return result } +// stripXMLToolCalls removes XML tool call blocks (e.g. ...) +// from response text. Some providers embed raw XML tool calls in Content alongside +// structured tool_calls; this prevents them from leaking to users. +func stripXMLToolCalls(text string) string { + // Match ... blocks (any namespace prefix) + idx := strings.Index(text, ":toolcall>") + if idx == -1 { + return text + } + // Find the opening tag start: scan backwards for '<' + tagStart := strings.LastIndex(text[:idx], "<") + if tagStart == -1 { + return text + } + // Extract namespace (e.g. "minimax" from "") + ns := text[tagStart+1 : idx] + closeTag := "" + closeIdx := strings.Index(text, closeTag) + if closeIdx == -1 { + return text + } + cleaned := text[:tagStart] + text[closeIdx+len(closeTag):] + // Recursively strip if there are more blocks + if strings.Contains(cleaned, ":toolcall>") { + cleaned = stripXMLToolCalls(cleaned) + } + return strings.TrimSpace(cleaned) +} + // stripToolCallsFromText removes tool call JSON from response text. func stripToolCallsFromText(text string) string { start := strings.Index(text, `{"tool_calls"`)