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 }