diff --git a/cmd/generate/internal/emit/types.go b/cmd/generate/internal/emit/types.go index 8545bba..a00bf7b 100644 --- a/cmd/generate/internal/emit/types.go +++ b/cmd/generate/internal/emit/types.go @@ -193,6 +193,27 @@ func WriteTypesJen(outDir string, schema *load.Schema, meta *load.Meta) error { f.Const().Defs(defs...) } f.Line() + case len(def.AnyOf) > 0 && isOpenStringEnum(def): + // "Open" string enum: `anyOf` of string consts plus (typically) a + // free-form string catch-all. Emit as a named string type with + // constants for the known values. Unknown values are still + // representable as strings, matching the schema's extensibility. + f.Type().Id(name).String() + defs := []Code{} + for _, v := range def.AnyOf { + if v == nil || v.Const == nil { + continue + } + s, ok := v.Const.(string) + if !ok { + continue + } + defs = append(defs, Id(util.ToEnumConst(name, s)).Id(name).Op("=").Lit(s)) + } + if len(defs) > 0 { + f.Const().Defs(defs...) + } + f.Line() case len(def.AnyOf) > 0: emitUnion(f, name, schema, def, def.AnyOf, false, usedTypeNames) case len(def.OneOf) > 0 && !isStringConstUnion(def): @@ -516,6 +537,43 @@ func isStringConstUnion(def *load.Definition) bool { return true } +// isOpenStringEnum reports whether def is an "open" string enum: an `anyOf` +// where every variant is a plain string schema (type: "string") and at least +// one variant has a string `const`. Such schemas describe a string value with +// a recommended set of known values while still permitting arbitrary strings +// for extensibility (see e.g. SessionConfigOptionCategory). +// +// These are emitted as a Go string-derived type with named constants for each +// known value, rather than as a discriminated union wrapper. +func isOpenStringEnum(def *load.Definition) bool { + if def == nil || len(def.AnyOf) == 0 { + return false + } + sawConst := false + for _, v := range def.AnyOf { + if v == nil { + return false + } + // Each variant must be a plain string schema: type "string", no nested + // structure (properties, items, refs, nested unions, allOf, etc.). + if ir.PrimaryType(v) != "string" { + return false + } + if v.Ref != "" || len(v.Properties) > 0 || v.Items != nil || + len(v.AnyOf) > 0 || len(v.OneOf) > 0 || len(v.AllOf) > 0 || + len(v.Enum) > 0 { + return false + } + if v.Const != nil { + if _, ok := v.Const.(string); !ok { + return false + } + sawConst = true + } + } + return sawConst +} + // emitValidateJen generates validators for selected types (logic unchanged). func emitValidateJen(f *File, name string, def *load.Definition) { diff --git a/types_gen.go b/types_gen.go index 76e41ed..0ecafed 100644 --- a/types_gen.go +++ b/types_gen.go @@ -4227,49 +4227,13 @@ func (u *SessionConfigOption) Validate() error { // // Category names beginning with '_' are free for custom use, like other ACP extension methods. // Category names that do not begin with '_' are reserved for the ACP spec. -// Unknown / uncategorized selector. -type SessionConfigOptionCategoryOther string +type SessionConfigOptionCategory string -type SessionConfigOptionCategory struct { - // Unknown / uncategorized selector. - Other *SessionConfigOptionCategoryOther `json:"-"` -} - -func (u *SessionConfigOptionCategory) UnmarshalJSON(b []byte) error { - var m map[string]json.RawMessage - if err := json.Unmarshal(b, &m); err == nil { - } else { - if _, ok := err.(*json.UnmarshalTypeError); !ok { - return err - } - } - var arr []map[string]json.RawMessage - if json.Unmarshal(b, &arr) == nil { - } - { - var v SessionConfigOptionCategoryOther - if json.Unmarshal(b, &v) == nil { - u.Other = &v - return nil - } - } - return errors.New("no matching variant for union") -} -func (u SessionConfigOptionCategory) MarshalJSON() ([]byte, error) { - if u.Other != nil { - _b, _e := json.Marshal(*u.Other) - if _e != nil { - return []byte{}, _e - } - return _b, nil - var m map[string]any - if json.Unmarshal(_b, &m) != nil { - return []byte{}, errors.New("invalid variant payload") - } - return json.Marshal(m) - } - return []byte{}, nil -} +const ( + SessionConfigOptionCategoryMode SessionConfigOptionCategory = "mode" + SessionConfigOptionCategoryModel SessionConfigOptionCategory = "model" + SessionConfigOptionCategoryThoughtLevel SessionConfigOptionCategory = "thought_level" +) // A single-value selector (dropdown) session configuration option payload. type SessionConfigSelect struct { @@ -7150,49 +7114,15 @@ func (v *UnstableListProvidersResponse) Validate() error { // // Protocol names beginning with '_' are free for custom use, like other ACP extension methods. // Protocol names that do not begin with '_' are reserved for the ACP spec. -// Unknown or custom protocol. -type UnstableLlmProtocolOther string +type UnstableLlmProtocol string -type UnstableLlmProtocol struct { - // Unknown or custom protocol. - Other *UnstableLlmProtocolOther `json:"-"` -} - -func (u *UnstableLlmProtocol) UnmarshalJSON(b []byte) error { - var m map[string]json.RawMessage - if err := json.Unmarshal(b, &m); err == nil { - } else { - if _, ok := err.(*json.UnmarshalTypeError); !ok { - return err - } - } - var arr []map[string]json.RawMessage - if json.Unmarshal(b, &arr) == nil { - } - { - var v UnstableLlmProtocolOther - if json.Unmarshal(b, &v) == nil { - u.Other = &v - return nil - } - } - return errors.New("no matching variant for union") -} -func (u UnstableLlmProtocol) MarshalJSON() ([]byte, error) { - if u.Other != nil { - _b, _e := json.Marshal(*u.Other) - if _e != nil { - return []byte{}, _e - } - return _b, nil - var m map[string]any - if json.Unmarshal(_b, &m) != nil { - return []byte{}, errors.New("invalid variant payload") - } - return json.Marshal(m) - } - return []byte{}, nil -} +const ( + UnstableLlmProtocolAnthropic UnstableLlmProtocol = "anthropic" + UnstableLlmProtocolOpenai UnstableLlmProtocol = "openai" + UnstableLlmProtocolAzure UnstableLlmProtocol = "azure" + UnstableLlmProtocolVertex UnstableLlmProtocol = "vertex" + UnstableLlmProtocolBedrock UnstableLlmProtocol = "bedrock" +) // **UNSTABLE** // diff --git a/unstable_types_test.go b/unstable_types_test.go index 9e611dc..537e458 100644 --- a/unstable_types_test.go +++ b/unstable_types_test.go @@ -54,6 +54,7 @@ func TestSessionConfigSelectOptions_UnmarshalArrayVariants(t *testing.T) { } func TestSessionConfigOptionSelect_MetadataRoundTrip(t *testing.T) { + modelCategory := SessionConfigOptionCategoryModel in := SessionConfigOption{ Select: &SessionConfigOptionSelect{ Type: "select", @@ -65,10 +66,9 @@ func TestSessionConfigOptionSelect_MetadataRoundTrip(t *testing.T) { {Name: "Low", Value: SessionConfigValueId("low")}, {Name: "Medium", Value: SessionConfigValueId("medium")}, }}, + Category: &modelCategory, }, } - modelCategory := SessionConfigOptionCategoryOther("model") - in.Select.Category = &SessionConfigOptionCategory{Other: &modelCategory} b, err := json.Marshal(in) if err != nil {