Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

225 lines
6.1 KiB
Go

package command
import (
"encoding/json"
"fmt"
"math"
"strings"
)
// ValidateInputSchema rejects schema constructs that the Connector command
// runtime cannot enforce. Connector schemas cross a package trust boundary,
// so unsupported JSON Schema keywords must fail closed.
func ValidateInputSchema(schema map[string]any) error {
if len(schema) == 0 {
return nil
}
return validateSupportedSchema(schema, "input")
}
func validateSupportedSchema(schema map[string]any, path string) error {
supported := map[string]struct{}{
"type": {}, "properties": {}, "required": {}, "additionalProperties": {}, "items": {},
"enum": {}, "minimum": {}, "maximum": {}, "allOf": {}, "anyOf": {}, "oneOf": {},
"title": {}, "description": {}, "default": {}, "examples": {}, "deprecated": {}, "readOnly": {}, "writeOnly": {},
}
for keyword := range schema {
if _, ok := supported[keyword]; !ok {
return fmt.Errorf("%s uses unsupported schema keyword %q", path, keyword)
}
}
typeName := ""
if rawType, exists := schema["type"]; exists {
var ok bool
typeName, ok = rawType.(string)
if !ok {
return fmt.Errorf("%s type schema is invalid", path)
}
switch strings.TrimSpace(typeName) {
case "object", "array", "string", "boolean", "integer", "number":
default:
return fmt.Errorf("%s has unsupported schema type %q", path, typeName)
}
}
if raw, exists := schema["properties"]; exists {
properties, ok := raw.(map[string]any)
if !ok || strings.TrimSpace(typeName) != "object" {
return fmt.Errorf("%s properties schema is invalid", path)
}
for name, rawProperty := range properties {
property, ok := rawProperty.(map[string]any)
if !ok || strings.TrimSpace(name) == "" {
return fmt.Errorf("%s.%s schema is invalid", path, name)
}
if err := validateSupportedSchema(property, path+"."+name); err != nil {
return err
}
}
}
if _, exists := schema["required"]; exists {
required, valid := schemaStringList(schema, "required")
if !valid || strings.TrimSpace(typeName) != "object" {
return fmt.Errorf("%s required schema is invalid", path)
}
seen := make(map[string]struct{}, len(required))
for _, name := range required {
if strings.TrimSpace(name) == "" {
return fmt.Errorf("%s required schema is invalid", path)
}
if _, duplicate := seen[name]; duplicate {
return fmt.Errorf("%s required schema contains duplicate %q", path, name)
}
seen[name] = struct{}{}
}
}
if raw, exists := schema["additionalProperties"]; exists {
if strings.TrimSpace(typeName) != "object" {
return fmt.Errorf("%s additionalProperties schema is invalid", path)
}
switch additional := raw.(type) {
case bool:
case map[string]any:
if err := validateSupportedSchema(additional, path+".*"); err != nil {
return err
}
default:
return fmt.Errorf("%s additionalProperties schema is invalid", path)
}
}
if raw, exists := schema["items"]; exists {
items, ok := raw.(map[string]any)
if !ok || strings.TrimSpace(typeName) != "array" {
return fmt.Errorf("%s items schema is invalid", path)
}
if err := validateSupportedSchema(items, path+"[]"); err != nil {
return err
}
}
if raw, exists := schema["enum"]; exists {
values, present, valid := schemaValueList(raw)
if !present || !valid || len(values) == 0 {
return fmt.Errorf("%s enum schema is invalid", path)
}
}
for _, keyword := range []string{"minimum", "maximum"} {
if raw, exists := schema[keyword]; exists {
if _, valid := schemaNumber(raw); !valid || (typeName != "integer" && typeName != "number") {
return fmt.Errorf("%s %s schema is invalid", path, keyword)
}
}
}
for _, keyword := range []string{"allOf", "anyOf", "oneOf"} {
branches, present, valid := schemaMapList(schema[keyword])
if !present {
continue
}
if !valid || len(branches) == 0 {
return fmt.Errorf("%s %s schema is invalid", path, keyword)
}
for index, branch := range branches {
if err := validateSupportedSchema(branch, fmt.Sprintf("%s.%s[%d]", path, keyword, index)); err != nil {
return err
}
}
}
return nil
}
func schemaMapList(value any) ([]map[string]any, bool, bool) {
if value == nil {
return nil, false, true
}
switch values := value.(type) {
case []map[string]any:
return values, true, true
case []any:
result := make([]map[string]any, 0, len(values))
for _, item := range values {
schema, ok := item.(map[string]any)
if !ok {
return nil, true, false
}
result = append(result, schema)
}
return result, true, true
default:
return nil, true, false
}
}
func schemaValueList(value any) ([]any, bool, bool) {
if value == nil {
return nil, false, true
}
switch values := value.(type) {
case []any:
return values, true, true
case []string:
result := make([]any, len(values))
for index := range values {
result[index] = values[index]
}
return result, true, true
default:
return nil, true, false
}
}
func schemaStringList(schema map[string]any, key string) ([]string, bool) {
value, exists := schema[key]
if !exists {
return nil, true
}
switch values := value.(type) {
case []string:
return values, true
case []any:
result := make([]string, 0, len(values))
for _, item := range values {
text, ok := item.(string)
if !ok {
return nil, false
}
result = append(result, text)
}
return result, true
default:
return nil, false
}
}
func schemaNumber(value any) (float64, bool) {
switch number := value.(type) {
case json.Number:
parsed, err := number.Float64()
return parsed, err == nil && !math.IsNaN(parsed) && !math.IsInf(parsed, 0)
case int:
return float64(number), true
case int8:
return float64(number), true
case int16:
return float64(number), true
case int32:
return float64(number), true
case int64:
return float64(number), true
case uint:
return float64(number), true
case uint8:
return float64(number), true
case uint16:
return float64(number), true
case uint32:
return float64(number), true
case uint64:
return float64(number), true
case float32:
parsed := float64(number)
return parsed, !math.IsNaN(parsed) && !math.IsInf(parsed, 0)
case float64:
return number, !math.IsNaN(number) && !math.IsInf(number, 0)
default:
return 0, false
}
}