feat: expand DSML tool-call alias and fence handling

Add support for DSML wrapper aliases (<dsml|tool_calls>, <|tool_calls>,
<|tool_calls>) alongside canonical XML. Normalize mixed DSML/canonical
tags instead of rejecting them. Add tilde fence (~~~) support, fix
nested fence and unclosed fence handling, support CDATA-protected fence
content, and skip prose mentions when scanning for real tool blocks.
Mirror all changes between Go and Node.js runtimes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
CJACK
2026-04-27 13:39:50 +08:00
parent 90ce595325
commit a13293e113
19 changed files with 1524 additions and 125 deletions

View File

@@ -93,7 +93,11 @@ func filterToolCallsDetailed(parsed []ParsedToolCall) ([]ParsedToolCall, []strin
func looksLikeToolCallSyntax(text string) bool {
lower := strings.ToLower(text)
return strings.Contains(lower, "<|dsml|tool_calls") || strings.Contains(lower, "<tool_calls")
return strings.Contains(lower, "<|dsml|tool_calls") ||
strings.Contains(lower, "<dsml|tool_calls") ||
strings.Contains(lower, "<tool_calls") ||
strings.Contains(lower, "<|tool_calls") ||
strings.Contains(lower, "<tool_calls")
}
func stripFencedCodeBlocks(text string) string {
@@ -107,6 +111,9 @@ func stripFencedCodeBlocks(text string) string {
inFence := false
fenceMarker := ""
inCDATA := false
// Track builder length when a fence opens so we can preserve content
// collected before the unclosed fence.
beforeFenceLen := 0
for _, line := range lines {
if inCDATA || cdataStartsBeforeFence(line) {
b.WriteString(line)
@@ -118,6 +125,7 @@ func stripFencedCodeBlocks(text string) string {
if marker, ok := parseFenceOpen(trimmed); ok {
inFence = true
fenceMarker = marker
beforeFenceLen = b.Len()
continue
}
b.WriteString(line)
@@ -131,6 +139,12 @@ func stripFencedCodeBlocks(text string) string {
}
if inFence {
// Unclosed fence: preserve content that was collected before the
// fence started rather than dropping everything.
result := b.String()
if beforeFenceLen > 0 && beforeFenceLen <= len(result) {
return result[:beforeFenceLen]
}
return ""
}
return b.String()