mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-22 08:57:42 +08:00
feat: add support for parsing loose JSON lists into arrays in tool call parameters
This commit is contained in:
164
internal/toolcall/toolcalls_array_parse.go
Normal file
164
internal/toolcall/toolcalls_array_parse.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package toolcall
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parseLooseJSONArrayValue(raw, paramName string) ([]any, bool) {
|
||||
if preservesCDATAStringParameter(paramName) {
|
||||
return nil, false
|
||||
}
|
||||
trimmed := strings.TrimSpace(html.UnescapeString(raw))
|
||||
if trimmed == "" {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if parsed, ok := parseLooseJSONArrayCandidate(trimmed, paramName); ok {
|
||||
return parsed, true
|
||||
}
|
||||
|
||||
segments, ok := splitTopLevelJSONValues(trimmed)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
out := make([]any, 0, len(segments))
|
||||
for _, segment := range segments {
|
||||
parsed, ok := parseLooseArrayElementValue(segment)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
out = append(out, parsed)
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
|
||||
func parseLooseJSONArrayCandidate(raw, paramName string) ([]any, bool) {
|
||||
parsed, ok := parseLooseArrayElementValue(raw)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return coerceArrayValue(parsed, paramName)
|
||||
}
|
||||
|
||||
func parseLooseArrayElementValue(raw string) (any, bool) {
|
||||
trimmed := strings.TrimSpace(html.UnescapeString(raw))
|
||||
if trimmed == "" {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var parsed any
|
||||
if err := json.Unmarshal([]byte(trimmed), &parsed); err == nil {
|
||||
return parsed, true
|
||||
}
|
||||
|
||||
repairedBackslashes := repairInvalidJSONBackslashes(trimmed)
|
||||
if repairedBackslashes != trimmed {
|
||||
if err := json.Unmarshal([]byte(repairedBackslashes), &parsed); err == nil {
|
||||
return parsed, true
|
||||
}
|
||||
}
|
||||
|
||||
repairedLoose := RepairLooseJSON(trimmed)
|
||||
if repairedLoose != trimmed {
|
||||
if err := json.Unmarshal([]byte(repairedLoose), &parsed); err == nil {
|
||||
return parsed, true
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(trimmed, "<") && strings.Contains(trimmed, ">") {
|
||||
if parsedXML, ok := parseXMLFragmentValue(trimmed); ok {
|
||||
return parsedXML, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func coerceArrayValue(value any, paramName string) ([]any, bool) {
|
||||
switch x := value.(type) {
|
||||
case []any:
|
||||
return x, true
|
||||
case map[string]any:
|
||||
if len(x) != 1 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if items, ok := x["item"]; ok {
|
||||
if arr, ok := coerceArrayValue(items, ""); ok {
|
||||
return arr, true
|
||||
}
|
||||
return []any{items}, true
|
||||
}
|
||||
|
||||
if paramName != "" {
|
||||
if wrapped, ok := x[paramName]; ok {
|
||||
if arr, ok := coerceArrayValue(wrapped, ""); ok {
|
||||
return arr, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func splitTopLevelJSONValues(raw string) ([]string, bool) {
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
if trimmed == "" {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
values := make([]string, 0, 2)
|
||||
start := 0
|
||||
depth := 0
|
||||
inString := false
|
||||
escaped := false
|
||||
|
||||
for i, r := range trimmed {
|
||||
if inString {
|
||||
if escaped {
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
switch r {
|
||||
case '\\':
|
||||
escaped = true
|
||||
case '"':
|
||||
inString = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '"':
|
||||
inString = true
|
||||
case '{', '[':
|
||||
depth++
|
||||
case '}', ']':
|
||||
if depth > 0 {
|
||||
depth--
|
||||
}
|
||||
case ',':
|
||||
if depth == 0 {
|
||||
segment := strings.TrimSpace(trimmed[start:i])
|
||||
if segment == "" {
|
||||
return nil, false
|
||||
}
|
||||
values = append(values, segment)
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
last := strings.TrimSpace(trimmed[start:])
|
||||
if last == "" {
|
||||
return nil, false
|
||||
}
|
||||
values = append(values, last)
|
||||
if len(values) < 2 {
|
||||
return nil, false
|
||||
}
|
||||
return values, true
|
||||
}
|
||||
Reference in New Issue
Block a user