diff --git a/pkg/providers/claude_cli_provider_test.go b/pkg/providers/claude_cli_provider_test.go index 4cf0797a5..9f237cb68 100644 --- a/pkg/providers/claude_cli_provider_test.go +++ b/pkg/providers/claude_cli_provider_test.go @@ -1058,6 +1058,43 @@ Done.` } } +func TestExtractXMLToolCalls_MismatchedCloseTag(t *testing.T) { + // MiniMax uses but closes with (underscore) + text := ` + +/home/user/project/pyproject.toml + +` + + calls := extractXMLToolCalls(text) + if len(calls) != 1 { + t.Fatalf("expected 1 tool call, got %d", len(calls)) + } + if calls[0].Name != "readfile" { + t.Errorf("Name = %q, want %q", calls[0].Name, "readfile") + } + if calls[0].Arguments["path"] != "/home/user/project/pyproject.toml" { + t.Errorf("Arguments[path] = %v, want pyproject.toml path", calls[0].Arguments["path"]) + } +} + +func TestStripXMLToolCalls_MismatchedCloseTag(t *testing.T) { + text := `今テスト走らせるね。 + + +cd /home/user && pytest + +` + + got := stripXMLToolCalls(text) + if strings.Contains(got, "toolcall") || strings.Contains(got, "tool_call") { + t.Errorf("should remove XML block, got %q", got) + } + if !strings.Contains(got, "今テスト走らせるね。") { + t.Errorf("should keep text before, got %q", got) + } +} + func TestStripXMLToolCalls_NoXML(t *testing.T) { text := "Just regular text." got := stripXMLToolCalls(text) diff --git a/pkg/providers/tool_call_extract.go b/pkg/providers/tool_call_extract.go index addfeecfc..93740c573 100644 --- a/pkg/providers/tool_call_extract.go +++ b/pkg/providers/tool_call_extract.go @@ -86,6 +86,11 @@ func extractXMLToolCalls(text string) []ToolCall { ns := remaining[tagStart+1 : idx] closeTag := "" closeIdx := strings.Index(remaining, closeTag) + // Fallback: some models use inconsistent close tags (e.g. tool_call vs toolcall) + if closeIdx == -1 { + closeTag = "" + closeIdx = strings.Index(remaining, closeTag) + } if closeIdx == -1 { break } @@ -191,6 +196,11 @@ func stripXMLToolCalls(text string) string { ns := text[tagStart+1 : idx] closeTag := "" closeIdx := strings.Index(text, closeTag) + // Fallback: some models use inconsistent close tags (e.g. tool_call vs toolcall) + if closeIdx == -1 { + closeTag = "" + closeIdx = strings.Index(text, closeTag) + } if closeIdx == -1 { return text }