mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-04 00:15:28 +08:00
Fix path corruption when parsing tool call JSON strings
This commit is contained in:
@@ -3,6 +3,7 @@ package util
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type ParsedToolCall struct {
|
||||
@@ -269,6 +270,7 @@ func parseToolCallInput(v any) map[string]any {
|
||||
}
|
||||
var parsed map[string]any
|
||||
if err := json.Unmarshal([]byte(raw), &parsed); err == nil && parsed != nil {
|
||||
repairPathLikeControlChars(parsed)
|
||||
return parsed
|
||||
}
|
||||
// Try to repair invalid backslashes (common in Windows paths output by models)
|
||||
@@ -298,3 +300,58 @@ func parseToolCallInput(v any) map[string]any {
|
||||
return map[string]any{}
|
||||
}
|
||||
}
|
||||
|
||||
func repairPathLikeControlChars(m map[string]any) {
|
||||
for k, v := range m {
|
||||
switch vv := v.(type) {
|
||||
case map[string]any:
|
||||
repairPathLikeControlChars(vv)
|
||||
case []any:
|
||||
for _, item := range vv {
|
||||
if child, ok := item.(map[string]any); ok {
|
||||
repairPathLikeControlChars(child)
|
||||
}
|
||||
}
|
||||
case string:
|
||||
if isPathLikeKey(k) && containsControlRune(vv) {
|
||||
m[k] = escapeControlRunes(vv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isPathLikeKey(key string) bool {
|
||||
k := strings.ToLower(strings.TrimSpace(key))
|
||||
return strings.Contains(k, "path") || strings.Contains(k, "file")
|
||||
}
|
||||
|
||||
func containsControlRune(s string) bool {
|
||||
for _, r := range s {
|
||||
if unicode.IsControl(r) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func escapeControlRunes(s string) string {
|
||||
var b strings.Builder
|
||||
b.Grow(len(s) + 8)
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case '\b':
|
||||
b.WriteString(`\b`)
|
||||
case '\f':
|
||||
b.WriteString(`\f`)
|
||||
case '\n':
|
||||
b.WriteString(`\n`)
|
||||
case '\r':
|
||||
b.WriteString(`\r`)
|
||||
case '\t':
|
||||
b.WriteString(`\t`)
|
||||
default:
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -419,6 +419,27 @@ func TestParseToolCallsWithMixedWindowsPaths(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseToolCallInputRepairsControlCharsInPath(t *testing.T) {
|
||||
in := `{"path":"D:\tmp\new\readme.txt","content":"line1\nline2"}`
|
||||
parsed := parseToolCallInput(in)
|
||||
|
||||
path, ok := parsed["path"].(string)
|
||||
if !ok {
|
||||
t.Fatalf("expected path string in parsed input, got %#v", parsed["path"])
|
||||
}
|
||||
if path != `D:\tmp\new\readme.txt` {
|
||||
t.Fatalf("expected repaired windows path, got %q", path)
|
||||
}
|
||||
|
||||
content, ok := parsed["content"].(string)
|
||||
if !ok {
|
||||
t.Fatalf("expected content string in parsed input, got %#v", parsed["content"])
|
||||
}
|
||||
if content != "line1\nline2" {
|
||||
t.Fatalf("expected non-path field to keep decoded escapes, got %q", content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepairLooseJSONWithNestedObjects(t *testing.T) {
|
||||
// 测试嵌套对象的修复:DeepSeek 幻觉输出,每个元素内部包含嵌套 {}
|
||||
// 注意:正则只支持单层嵌套,不支持更深层次的嵌套
|
||||
|
||||
Reference in New Issue
Block a user