mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-03 16:05:26 +08:00
- Add shared ToolMarkupTag scanner (toolcalls_scan.go) replacing hardcoded alias tables - Support DSML collapsed tag names (<DSMLtool_calls>, <DSMLinvoke>, <DSMLparameter>) - Parse JSON literal values from parameter bodies (123→number, true→bool, null) - Recover unclosed CDATA in final parse/flush via SanitizeLooseCDATA - Align Go and Node implementations (scanToolMarkupTagAt, findMatchingToolMarkupClose) - Reject bare <invoke> as unsupported syntax, only tool_calls wrapper triggers tool path - Update API.md and toolcall-semantics.md documentation Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
82 lines
2.8 KiB
Go
82 lines
2.8 KiB
Go
package toolcall
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestRegression_RobustXMLAndCDATA(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
text string
|
|
expected []ParsedToolCall
|
|
}{
|
|
{
|
|
name: "Standard JSON scalar parameters (Regression)",
|
|
text: `<tool_calls><invoke name="foo"><parameter name="a">1</parameter></invoke></tool_calls>`,
|
|
expected: []ParsedToolCall{{Name: "foo", Input: map[string]any{"a": float64(1)}}},
|
|
},
|
|
{
|
|
name: "XML tags parameters (Regression)",
|
|
text: `<tool_calls><invoke name="foo"><parameter name="arg1">hello</parameter></invoke></tool_calls>`,
|
|
expected: []ParsedToolCall{{Name: "foo", Input: map[string]any{"arg1": "hello"}}},
|
|
},
|
|
{
|
|
name: "CDATA parameters (New Feature)",
|
|
text: `<tool_calls><invoke name="write_file"><parameter name="content"><![CDATA[line 1
|
|
line 2 with <tags> and & symbols]]></parameter></invoke></tool_calls>`,
|
|
expected: []ParsedToolCall{{
|
|
Name: "write_file",
|
|
Input: map[string]any{"content": "line 1\nline 2 with <tags> and & symbols"},
|
|
}},
|
|
},
|
|
{
|
|
name: "Nested XML with repeated parameters (New Feature)",
|
|
text: `<tool_calls><invoke name="write_file"><parameter name="path">script.sh</parameter><parameter name="content"><![CDATA[#!/bin/bash
|
|
echo "hello"
|
|
]]></parameter><parameter name="item">first</parameter><parameter name="item">second</parameter></invoke></tool_calls>`,
|
|
expected: []ParsedToolCall{{
|
|
Name: "write_file",
|
|
Input: map[string]any{
|
|
"path": "script.sh",
|
|
"content": "#!/bin/bash\necho \"hello\"\n",
|
|
"item": []any{"first", "second"},
|
|
},
|
|
}},
|
|
},
|
|
{
|
|
name: "Dirty XML with unescaped symbols (Robustness Improvement)",
|
|
text: `<tool_calls><invoke name="bash"><parameter name="command">echo "hello" > out.txt && cat out.txt</parameter></invoke></tool_calls>`,
|
|
expected: []ParsedToolCall{{
|
|
Name: "bash",
|
|
Input: map[string]any{"command": "echo \"hello\" > out.txt && cat out.txt"},
|
|
}},
|
|
},
|
|
{
|
|
name: "Mixed JSON inside CDATA (New Hybrid Case)",
|
|
text: `<tool_calls><invoke name="foo"><parameter name="json_param"><![CDATA[works]]></parameter></invoke></tool_calls>`,
|
|
expected: []ParsedToolCall{{
|
|
Name: "foo",
|
|
Input: map[string]any{"json_param": "works"},
|
|
}},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := ParseToolCalls(tt.text, []string{"foo", "write_file", "bash"})
|
|
if len(got) != len(tt.expected) {
|
|
t.Fatalf("expected %d calls, got %d", len(tt.expected), len(got))
|
|
}
|
|
for i := range got {
|
|
if got[i].Name != tt.expected[i].Name {
|
|
t.Errorf("expected name %q, got %q", tt.expected[i].Name, got[i].Name)
|
|
}
|
|
if !reflect.DeepEqual(got[i].Input, tt.expected[i].Input) {
|
|
t.Errorf("expected input %#v, got %#v", tt.expected[i].Input, got[i].Input)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|