Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
139 lines
3.1 KiB
Go
139 lines
3.1 KiB
Go
package tools
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// CastParams performs schema-driven type casting on tool arguments.
|
|
// LLMs sometimes return incorrect types (e.g., "true" instead of true, "123" instead of 123).
|
|
// This function attempts safe conversions based on the JSON Schema definition of the tool's parameters.
|
|
//
|
|
// If the schema is nil or cannot be parsed, the original args are returned unchanged.
|
|
func CastParams(args json.RawMessage, schema json.RawMessage) json.RawMessage {
|
|
if len(schema) == 0 || len(args) == 0 {
|
|
return args
|
|
}
|
|
|
|
var schemaDef map[string]interface{}
|
|
if err := json.Unmarshal(schema, &schemaDef); err != nil {
|
|
return args
|
|
}
|
|
|
|
properties, ok := schemaDef["properties"].(map[string]interface{})
|
|
if !ok || len(properties) == 0 {
|
|
return args
|
|
}
|
|
|
|
var argsMap map[string]interface{}
|
|
if err := json.Unmarshal(args, &argsMap); err != nil {
|
|
return args
|
|
}
|
|
|
|
changed := false
|
|
for key, val := range argsMap {
|
|
propDef, exists := properties[key]
|
|
if !exists {
|
|
continue
|
|
}
|
|
prop, ok := propDef.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
targetType, _ := prop["type"].(string)
|
|
if targetType == "" {
|
|
continue
|
|
}
|
|
|
|
newVal, didCast := castValue(val, targetType)
|
|
if didCast {
|
|
argsMap[key] = newVal
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
if !changed {
|
|
return args
|
|
}
|
|
|
|
result, err := json.Marshal(argsMap)
|
|
if err != nil {
|
|
return args
|
|
}
|
|
return result
|
|
}
|
|
|
|
// castValue attempts to convert val to the expected targetType.
|
|
// Returns (newValue, true) if a conversion was made, (val, false) otherwise.
|
|
func castValue(val interface{}, targetType string) (interface{}, bool) {
|
|
switch targetType {
|
|
case "array":
|
|
if s, ok := val.(string); ok {
|
|
// Try JSON parsing first (handles "[{...}]" → []interface{})
|
|
var parsed []interface{}
|
|
if err := json.Unmarshal([]byte(s), &parsed); err == nil {
|
|
return parsed, true
|
|
}
|
|
// Fall back: single string → string array
|
|
return []string{s}, true
|
|
}
|
|
|
|
case "boolean":
|
|
if s, ok := val.(string); ok {
|
|
lower := strings.ToLower(s)
|
|
switch lower {
|
|
case "true", "1", "yes":
|
|
return true, true
|
|
case "false", "0", "no":
|
|
return false, true
|
|
}
|
|
}
|
|
// JSON number 0/1 -> bool
|
|
if n, ok := val.(float64); ok {
|
|
if n == 0 {
|
|
return false, true
|
|
}
|
|
if n == 1 {
|
|
return true, true
|
|
}
|
|
}
|
|
|
|
case "integer":
|
|
if s, ok := val.(string); ok {
|
|
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
|
|
return i, true
|
|
}
|
|
}
|
|
// JSON numbers are float64 in Go; convert to int if it's a whole number
|
|
if f, ok := val.(float64); ok {
|
|
if f == float64(int64(f)) {
|
|
return int64(f), true
|
|
}
|
|
}
|
|
|
|
case "number":
|
|
if s, ok := val.(string); ok {
|
|
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
|
return f, true
|
|
}
|
|
}
|
|
|
|
case "string":
|
|
// Non-string values -> string (e.g., number or bool passed as non-string)
|
|
switch v := val.(type) {
|
|
case bool:
|
|
if v {
|
|
return "true", true
|
|
}
|
|
return "false", true
|
|
case float64:
|
|
return strconv.FormatFloat(v, 'f', -1, 64), true
|
|
case int64:
|
|
return strconv.FormatInt(v, 10), true
|
|
}
|
|
}
|
|
|
|
return val, false
|
|
}
|