From 1c094038bcfe0ca40c90273cb7228f8ad34b7417 Mon Sep 17 00:00:00 2001 From: Parth Sareen Date: Wed, 17 Dec 2025 11:54:09 -0800 Subject: [PATCH] types: add nested property support for tool definitions (#13508) --- api/types.go | 11 ++--- api/types_test.go | 101 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 5 deletions(-) diff --git a/api/types.go b/api/types.go index d8467629..3ccf3ce2 100644 --- a/api/types.go +++ b/api/types.go @@ -283,11 +283,12 @@ func (pt PropertyType) String() string { } type ToolProperty struct { - AnyOf []ToolProperty `json:"anyOf,omitempty"` - Type PropertyType `json:"type,omitempty"` - Items any `json:"items,omitempty"` - Description string `json:"description,omitempty"` - Enum []any `json:"enum,omitempty"` + AnyOf []ToolProperty `json:"anyOf,omitempty"` + Type PropertyType `json:"type,omitempty"` + Items any `json:"items,omitempty"` + Description string `json:"description,omitempty"` + Enum []any `json:"enum,omitempty"` + Properties map[string]ToolProperty `json:"properties,omitempty"` } // ToTypeScriptType converts a ToolProperty to a TypeScript type string diff --git a/api/types_test.go b/api/types_test.go index 187012db..da1581f4 100644 --- a/api/types_test.go +++ b/api/types_test.go @@ -504,6 +504,107 @@ func TestThinking_UnmarshalJSON(t *testing.T) { } } +func TestToolPropertyNestedProperties(t *testing.T) { + tests := []struct { + name string + input string + expected ToolProperty + }{ + { + name: "nested object properties", + input: `{ + "type": "object", + "description": "Location details", + "properties": { + "address": { + "type": "string", + "description": "Street address" + }, + "city": { + "type": "string", + "description": "City name" + } + } + }`, + expected: ToolProperty{ + Type: PropertyType{"object"}, + Description: "Location details", + Properties: map[string]ToolProperty{ + "address": { + Type: PropertyType{"string"}, + Description: "Street address", + }, + "city": { + Type: PropertyType{"string"}, + Description: "City name", + }, + }, + }, + }, + { + name: "deeply nested properties", + input: `{ + "type": "object", + "description": "Event", + "properties": { + "location": { + "type": "object", + "description": "Location", + "properties": { + "coordinates": { + "type": "object", + "description": "GPS coordinates", + "properties": { + "lat": {"type": "number", "description": "Latitude"}, + "lng": {"type": "number", "description": "Longitude"} + } + } + } + } + } + }`, + expected: ToolProperty{ + Type: PropertyType{"object"}, + Description: "Event", + Properties: map[string]ToolProperty{ + "location": { + Type: PropertyType{"object"}, + Description: "Location", + Properties: map[string]ToolProperty{ + "coordinates": { + Type: PropertyType{"object"}, + Description: "GPS coordinates", + Properties: map[string]ToolProperty{ + "lat": {Type: PropertyType{"number"}, Description: "Latitude"}, + "lng": {Type: PropertyType{"number"}, Description: "Longitude"}, + }, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var prop ToolProperty + err := json.Unmarshal([]byte(tt.input), &prop) + require.NoError(t, err) + assert.Equal(t, tt.expected, prop) + + // Round-trip test: marshal and unmarshal again + data, err := json.Marshal(prop) + require.NoError(t, err) + + var prop2 ToolProperty + err = json.Unmarshal(data, &prop2) + require.NoError(t, err) + assert.Equal(t, tt.expected, prop2) + }) + } +} + func TestToolFunctionParameters_String(t *testing.T) { tests := []struct { name string