Handle variable-length markdown fences in toolcall parser

This commit is contained in:
CJACK.
2026-04-19 23:37:31 +08:00
parent 5b7cdaa729
commit 69eb71159d
2 changed files with 39 additions and 7 deletions

View File

@@ -124,19 +124,40 @@ func stripFencedCodeBlocks(text string) string {
}
func parseFenceOpen(line string) (string, bool) {
if strings.HasPrefix(line, "```") {
return "```", true
if len(line) < 3 {
return "", false
}
if strings.HasPrefix(line, "~~~") {
return "~~~", true
ch := line[0]
if ch != '`' && ch != '~' {
return "", false
}
return "", false
count := countLeadingFenceChars(line, ch)
if count < 3 {
return "", false
}
return strings.Repeat(string(ch), count), true
}
func isFenceClose(line, marker string) bool {
if marker == "" || !strings.HasPrefix(line, marker) {
if marker == "" {
return false
}
rest := strings.TrimSpace(strings.TrimPrefix(line, marker))
ch := marker[0]
if line == "" || line[0] != ch {
return false
}
count := countLeadingFenceChars(line, ch)
if count < len(marker) {
return false
}
rest := strings.TrimSpace(line[count:])
return rest == ""
}
func countLeadingFenceChars(line string, ch byte) int {
count := 0
for count < len(line) && line[count] == ch {
count++
}
return count
}

View File

@@ -474,3 +474,14 @@ func TestParseToolCallsParsesOnlyNonFencedXMLToolCall(t *testing.T) {
t.Fatalf("expected non-fenced tool call to be parsed, got %#v", res.Calls[0])
}
}
func TestParseToolCallsParsesAfterFourBacktickFence(t *testing.T) {
text := "````markdown\n```xml\n<tool_call><tool_name>read_file</tool_name><parameters>{\"path\":\"README.md\"}</parameters></tool_call>\n```\n````\n<tool_call><tool_name>search</tool_name><parameters>{\"q\":\"outside\"}</parameters></tool_call>"
res := ParseToolCallsDetailed(text, []string{"read_file", "search"})
if len(res.Calls) != 1 {
t.Fatalf("expected exactly one parsed call outside four-backtick fence, got %#v", res.Calls)
}
if res.Calls[0].Name != "search" {
t.Fatalf("expected non-fenced tool call to be parsed, got %#v", res.Calls[0])
}
}