package agent
import (
"strings"
"testing"
)
func TestStripGarbledToolXML_FullToolCallBlock(t *testing.T) {
tests := []struct {
name string
input string
want string
}{
{
name: "removes whole block, keeps surrounding prose",
input: "PR #1217 looks good - approving.\n\n" +
"\n\n" +
"argus\n" +
"PR #1217 approved\n" +
"\n",
want: "PR #1217 looks good - approving.",
},
{
name: "block-only response collapses to empty",
input: "" +
"hi",
want: "",
},
{
name: "no tool xml — unchanged",
input: "Just a normal reply with no tool calls.",
want: "Just a normal reply with no tool calls.",
},
{
name: "partial stray tag still stripped (backward compat)",
input: "Result here oops",
want: "Result here oops",
},
{
name: "removes bare block without function_calls wrapper",
input: "Collecting PRs.\n\n" +
"\n" +
"gh pr view 1218\n" +
"",
want: "Collecting PRs.",
},
{
name: "bare invoke-only response collapses to empty",
input: "" +
"hi",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := stripGarbledToolXML(tt.input)
if got != tt.want {
t.Errorf("stripGarbledToolXML() = %q, want %q", got, tt.want)
}
})
}
}
// Regression: the tag-only strip used to leave argument values
// orphaned in the reply when a model emitted a tool call as text instead of
// invoking it natively (claude-cli thin-proxy). The whole block must be removed.
func TestSanitizeAssistantContent_NoToolCallArgumentLeak(t *testing.T) {
input := "Done.\n\n\n" +
"argus\n" +
"SECRET_ARG_VALUE\n" +
"\n"
got := SanitizeAssistantContent(input)
if strings.Contains(got, "SECRET_ARG_VALUE") || strings.Contains(got, "argus") {
t.Errorf("tool-call argument values leaked into reply: %q", got)
}
if got != "Done." {
t.Errorf("SanitizeAssistantContent() = %q, want %q", got, "Done.")
}
}
// Regression: claude-cli under a degraded session emits a tool call as a BARE
// block with no wrapper. The whole block must be
// removed so the command argument does not leak into the user-facing reply.
func TestSanitizeAssistantContent_BareInvokeNoWrapper(t *testing.T) {
input := "Format error, retrying.\n\n" +
"\n" +
"( cd ~/secret-path && gh pr view 1218 )\n" +
""
got := SanitizeAssistantContent(input)
if strings.Contains(got, "secret-path") || strings.Contains(got, "gh pr view") {
t.Errorf("bare-invoke command argument leaked into reply: %q", got)
}
if got != "Format error, retrying." {
t.Errorf("SanitizeAssistantContent() = %q, want %q", got, "Format error, retrying.")
}
}