88 lines
2.5 KiB
Go
88 lines
2.5 KiB
Go
|
|
package skill
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
)
|
||
|
|
|
||
|
|
// jsonObject is a yaml-aware wrapper around arbitrary JSON. It decodes from
|
||
|
|
// both YAML and JSON maps and, on encode, emits itself as a proper YAML map
|
||
|
|
// rather than a byte/number array. This is needed because Tool.Parameters is
|
||
|
|
// a JSON Schema (json.RawMessage) that must round-trip through the markdown
|
||
|
|
// frontmatter as a readable nested object.
|
||
|
|
//
|
||
|
|
// We implement yaml.Node-based encode/decode indirectly: by converting to/from
|
||
|
|
// map[string]any at the boundaries the default yaml encoder produces the
|
||
|
|
// expected nested structure. For JSON (the HTTP API), the underlying JSON is
|
||
|
|
// passed through unchanged.
|
||
|
|
type jsonObject json.RawMessage
|
||
|
|
|
||
|
|
// MarshalJSON returns the underlying JSON bytes unchanged.
|
||
|
|
func (j jsonObject) MarshalJSON() ([]byte, error) {
|
||
|
|
if len(j) == 0 {
|
||
|
|
return []byte("null"), nil
|
||
|
|
}
|
||
|
|
return j, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// UnmarshalJSON stores the raw bytes verbatim.
|
||
|
|
func (j *jsonObject) UnmarshalJSON(b []byte) error {
|
||
|
|
*j = append((*j)[:0], b...)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// MarshalYAML converts the JSON to a generic value tree so the YAML encoder
|
||
|
|
// writes a nested map/sequence rather than a list of byte integers.
|
||
|
|
func (j jsonObject) MarshalYAML() (interface{}, error) {
|
||
|
|
if len(j) == 0 {
|
||
|
|
return nil, nil
|
||
|
|
}
|
||
|
|
var v interface{}
|
||
|
|
if err := json.Unmarshal(j, &v); err != nil {
|
||
|
|
return nil, fmt.Errorf("json object -> yaml: %w", err)
|
||
|
|
}
|
||
|
|
return v, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// UnmarshalYAML accepts a YAML node and re-encodes it as canonical JSON, so
|
||
|
|
// editors writing hand-typed JSON Schema in YAML form still parse correctly.
|
||
|
|
func (j *jsonObject) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||
|
|
var v interface{}
|
||
|
|
if err := unmarshal(&v); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
// Numeric keys are not valid in JSON Schema, but YAML may have produced
|
||
|
|
// map[interface{}]interface{}; normalise through JSON round-trip.
|
||
|
|
b, err := json.Marshal(normaliseYAMLMap(v))
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
*j = b
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// normaliseYAMLMap recursively converts map[interface{}]interface{} (which
|
||
|
|
// yaml.v3 sometimes yields for nested structures) into map[string]any so the
|
||
|
|
// value is JSON-encodable.
|
||
|
|
func normaliseYAMLMap(v interface{}) interface{} {
|
||
|
|
switch m := v.(type) {
|
||
|
|
case map[interface{}]interface{}:
|
||
|
|
out := make(map[string]interface{}, len(m))
|
||
|
|
for k, val := range m {
|
||
|
|
out[fmt.Sprint(k)] = normaliseYAMLMap(val)
|
||
|
|
}
|
||
|
|
return out
|
||
|
|
case map[string]interface{}:
|
||
|
|
for k, val := range m {
|
||
|
|
m[k] = normaliseYAMLMap(val)
|
||
|
|
}
|
||
|
|
return m
|
||
|
|
case []interface{}:
|
||
|
|
for i, val := range m {
|
||
|
|
m[i] = normaliseYAMLMap(val)
|
||
|
|
}
|
||
|
|
return m
|
||
|
|
}
|
||
|
|
return v
|
||
|
|
}
|