diff --git a/api/types.go b/api/types.go index e2c63b62..0f99de18 100644 --- a/api/types.go +++ b/api/types.go @@ -225,20 +225,68 @@ func (pt PropertyType) String() string { return fmt.Sprintf("%v", []string(pt)) } +type ToolProperty struct { + AnyOf []ToolProperty `json:"anyOf,omitempty"` + Type PropertyType `json:"type"` + Items any `json:"items,omitempty"` + Description string `json:"description"` + Enum []any `json:"enum,omitempty"` +} + +// ToTypeScriptType converts a ToolProperty to a TypeScript type string +func (tp ToolProperty) ToTypeScriptType() string { + if len(tp.AnyOf) > 0 { + var types []string + for _, anyOf := range tp.AnyOf { + types = append(types, anyOf.ToTypeScriptType()) + } + return strings.Join(types, " | ") + } + + if len(tp.Type) == 0 { + return "any" + } + + if len(tp.Type) == 1 { + return mapToTypeScriptType(tp.Type[0]) + } + + var types []string + for _, t := range tp.Type { + types = append(types, mapToTypeScriptType(t)) + } + return strings.Join(types, " | ") +} + +// mapToTypeScriptType maps JSON Schema types to TypeScript types +func mapToTypeScriptType(jsonType string) string { + switch jsonType { + case "string": + return "string" + case "number", "integer": + return "number" + case "boolean": + return "boolean" + case "array": + return "any[]" + case "object": + return "Record" + case "null": + return "null" + default: + return "any" + } +} + type ToolFunction struct { Name string `json:"name"` Description string `json:"description"` Parameters struct { - Type string `json:"type"` - Defs any `json:"$defs,omitempty"` - Items any `json:"items,omitempty"` - Required []string `json:"required"` - Properties map[string]struct { - Type PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - } `json:"properties"` + Type string `json:"type"` + Defs any `json:"$defs,omitempty"` + Items any `json:"items,omitempty"` + Required []string `json:"required"` + Properties map[string]ToolProperty `json:"properties"` } `json:"parameters"` } diff --git a/api/types_typescript_test.go b/api/types_typescript_test.go new file mode 100644 index 00000000..9902c5be --- /dev/null +++ b/api/types_typescript_test.go @@ -0,0 +1,142 @@ +package api + +import ( + "testing" +) + +func TestToolParameterToTypeScriptType(t *testing.T) { + tests := []struct { + name string + param ToolProperty + expected string + }{ + { + name: "single string type", + param: ToolProperty{ + Type: PropertyType{"string"}, + }, + expected: "string", + }, + { + name: "single number type", + param: ToolProperty{ + Type: PropertyType{"number"}, + }, + expected: "number", + }, + { + name: "integer maps to number", + param: ToolProperty{ + Type: PropertyType{"integer"}, + }, + expected: "number", + }, + { + name: "boolean type", + param: ToolProperty{ + Type: PropertyType{"boolean"}, + }, + expected: "boolean", + }, + { + name: "array type", + param: ToolProperty{ + Type: PropertyType{"array"}, + }, + expected: "any[]", + }, + { + name: "object type", + param: ToolProperty{ + Type: PropertyType{"object"}, + }, + expected: "Record", + }, + { + name: "null type", + param: ToolProperty{ + Type: PropertyType{"null"}, + }, + expected: "null", + }, + { + name: "multiple types as union", + param: ToolProperty{ + Type: PropertyType{"string", "number"}, + }, + expected: "string | number", + }, + { + name: "string or null union", + param: ToolProperty{ + Type: PropertyType{"string", "null"}, + }, + expected: "string | null", + }, + { + name: "anyOf with single types", + param: ToolProperty{ + AnyOf: []ToolProperty{ + {Type: PropertyType{"string"}}, + {Type: PropertyType{"number"}}, + }, + }, + expected: "string | number", + }, + { + name: "anyOf with multiple types in each branch", + param: ToolProperty{ + AnyOf: []ToolProperty{ + {Type: PropertyType{"string", "null"}}, + {Type: PropertyType{"number"}}, + }, + }, + expected: "string | null | number", + }, + { + name: "nested anyOf", + param: ToolProperty{ + AnyOf: []ToolProperty{ + {Type: PropertyType{"boolean"}}, + { + AnyOf: []ToolProperty{ + {Type: PropertyType{"string"}}, + {Type: PropertyType{"number"}}, + }, + }, + }, + }, + expected: "boolean | string | number", + }, + { + name: "empty type returns any", + param: ToolProperty{ + Type: PropertyType{}, + }, + expected: "any", + }, + { + name: "unknown type maps to any", + param: ToolProperty{ + Type: PropertyType{"unknown_type"}, + }, + expected: "any", + }, + { + name: "multiple types including array", + param: ToolProperty{ + Type: PropertyType{"string", "array", "null"}, + }, + expected: "string | any[] | null", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.param.ToTypeScriptType() + if result != tt.expected { + t.Errorf("ToTypeScriptType() = %q, want %q", result, tt.expected) + } + }) + } +} diff --git a/openai/openai_test.go b/openai/openai_test.go index a24093ad..471b4737 100644 --- a/openai/openai_test.go +++ b/openai/openai_test.go @@ -280,25 +280,15 @@ func TestChatMiddleware(t *testing.T) { Name: "get_weather", Description: "Get the current weather", Parameters: struct { - Type string `json:"type"` - Defs any `json:"$defs,omitempty"` - Items any `json:"items,omitempty"` - Required []string `json:"required"` - Properties map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - } `json:"properties"` + Type string `json:"type"` + Defs any `json:"$defs,omitempty"` + Items any `json:"items,omitempty"` + Required []string `json:"required"` + Properties map[string]api.ToolProperty `json:"properties"` }{ Type: "object", Required: []string{"location"}, - Properties: map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - }{ + Properties: map[string]api.ToolProperty{ "location": { Type: api.PropertyType{"string"}, Description: "The city and state", diff --git a/server/routes_generate_test.go b/server/routes_generate_test.go index 477d6b81..506071ed 100644 --- a/server/routes_generate_test.go +++ b/server/routes_generate_test.go @@ -388,25 +388,15 @@ func TestGenerateChat(t *testing.T) { Name: "get_weather", Description: "Get the current weather", Parameters: struct { - Type string `json:"type"` - Defs any `json:"$defs,omitempty"` - Items any `json:"items,omitempty"` - Required []string `json:"required"` - Properties map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - } `json:"properties"` + Type string `json:"type"` + Defs any `json:"$defs,omitempty"` + Items any `json:"items,omitempty"` + Required []string `json:"required"` + Properties map[string]api.ToolProperty `json:"properties"` }{ Type: "object", Required: []string{"location"}, - Properties: map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - }{ + Properties: map[string]api.ToolProperty{ "location": { Type: api.PropertyType{"string"}, Description: "The city and state", @@ -489,25 +479,15 @@ func TestGenerateChat(t *testing.T) { Name: "get_weather", Description: "Get the current weather", Parameters: struct { - Type string `json:"type"` - Defs any `json:"$defs,omitempty"` - Items any `json:"items,omitempty"` - Required []string `json:"required"` - Properties map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - } `json:"properties"` + Type string `json:"type"` + Defs any `json:"$defs,omitempty"` + Items any `json:"items,omitempty"` + Required []string `json:"required"` + Properties map[string]api.ToolProperty `json:"properties"` }{ Type: "object", Required: []string{"location"}, - Properties: map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - }{ + Properties: map[string]api.ToolProperty{ "location": { Type: api.PropertyType{"string"}, Description: "The city and state", diff --git a/server/routes_harmony_streaming_test.go b/server/routes_harmony_streaming_test.go index 503cb4d7..1b86f84c 100644 --- a/server/routes_harmony_streaming_test.go +++ b/server/routes_harmony_streaming_test.go @@ -27,25 +27,15 @@ func getTestTools() []api.Tool { Name: "get_weather", Description: "Get the current weather in a given location", Parameters: struct { - Type string `json:"type"` - Defs any `json:"$defs,omitempty"` - Items any `json:"items,omitempty"` - Required []string `json:"required"` - Properties map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - } `json:"properties"` + Type string `json:"type"` + Defs any `json:"$defs,omitempty"` + Items any `json:"items,omitempty"` + Required []string `json:"required"` + Properties map[string]api.ToolProperty `json:"properties"` }{ Type: "object", Required: []string{"location"}, - Properties: map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - }{ + Properties: map[string]api.ToolProperty{ "location": { Type: api.PropertyType{"string"}, Description: "The city and state, e.g. San Francisco, CA", @@ -60,25 +50,15 @@ func getTestTools() []api.Tool { Name: "calculate", Description: "Calculate a mathematical expression", Parameters: struct { - Type string `json:"type"` - Defs any `json:"$defs,omitempty"` - Items any `json:"items,omitempty"` - Required []string `json:"required"` - Properties map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - } `json:"properties"` + Type string `json:"type"` + Defs any `json:"$defs,omitempty"` + Items any `json:"items,omitempty"` + Required []string `json:"required"` + Properties map[string]api.ToolProperty `json:"properties"` }{ Type: "object", Required: []string{"expression"}, - Properties: map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - }{ + Properties: map[string]api.ToolProperty{ "expression": { Type: api.PropertyType{"string"}, Description: "The mathematical expression to calculate", diff --git a/template/template.go b/template/template.go index bfd02a92..f2775b91 100644 --- a/template/template.go +++ b/template/template.go @@ -127,6 +127,16 @@ var funcs = template.FuncMap{ // Default format is YYYY-MM-DD return time.Now().Format("2006-01-02") }, + "toTypeScriptType": func(v any) string { + if param, ok := v.(api.ToolProperty); ok { + return param.ToTypeScriptType() + } + // Handle pointer case + if param, ok := v.(*api.ToolProperty); ok && param != nil { + return param.ToTypeScriptType() + } + return "any" + }, } func Parse(s string) (*Template, error) { diff --git a/tools/tools_test.go b/tools/tools_test.go index a0f7b6b0..7f00be20 100644 --- a/tools/tools_test.go +++ b/tools/tools_test.go @@ -41,25 +41,15 @@ func TestParser(t *testing.T) { Name: "get_temperature", Description: "Retrieve the temperature for a given location", Parameters: struct { - Type string `json:"type"` - Defs any `json:"$defs,omitempty"` - Items any `json:"items,omitempty"` - Required []string `json:"required"` - Properties map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - } `json:"properties"` + Type string `json:"type"` + Defs any `json:"$defs,omitempty"` + Items any `json:"items,omitempty"` + Required []string `json:"required"` + Properties map[string]api.ToolProperty `json:"properties"` }{ Type: "object", Required: []string{"city"}, - Properties: map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - }{ + Properties: map[string]api.ToolProperty{ "format": { Type: api.PropertyType{"string"}, Description: "The format to return the temperature in", @@ -79,24 +69,14 @@ func TestParser(t *testing.T) { Name: "get_conditions", Description: "Retrieve the current weather conditions for a given location", Parameters: struct { - Type string `json:"type"` - Defs any `json:"$defs,omitempty"` - Items any `json:"items,omitempty"` - Required []string `json:"required"` - Properties map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - } `json:"properties"` + Type string `json:"type"` + Defs any `json:"$defs,omitempty"` + Items any `json:"items,omitempty"` + Required []string `json:"required"` + Properties map[string]api.ToolProperty `json:"properties"` }{ Type: "object", - Properties: map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - }{ + Properties: map[string]api.ToolProperty{ "location": { Type: api.PropertyType{"string"}, Description: "The location to get the weather conditions for", @@ -125,24 +105,14 @@ func TestParser(t *testing.T) { Name: "get_address", Description: "Get the address of a given location", Parameters: struct { - Type string `json:"type"` - Defs any `json:"$defs,omitempty"` - Items any `json:"items,omitempty"` - Required []string `json:"required"` - Properties map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - } `json:"properties"` + Type string `json:"type"` + Defs any `json:"$defs,omitempty"` + Items any `json:"items,omitempty"` + Required []string `json:"required"` + Properties map[string]api.ToolProperty `json:"properties"` }{ Type: "object", - Properties: map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - }{ + Properties: map[string]api.ToolProperty{ "location": { Type: api.PropertyType{"string"}, Description: "The location to get the address for", @@ -157,24 +127,14 @@ func TestParser(t *testing.T) { Name: "add", Description: "Add two numbers", Parameters: struct { - Type string `json:"type"` - Defs any `json:"$defs,omitempty"` - Items any `json:"items,omitempty"` - Required []string `json:"required"` - Properties map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - } `json:"properties"` + Type string `json:"type"` + Defs any `json:"$defs,omitempty"` + Items any `json:"items,omitempty"` + Required []string `json:"required"` + Properties map[string]api.ToolProperty `json:"properties"` }{ Type: "object", - Properties: map[string]struct { - Type api.PropertyType `json:"type"` - Items any `json:"items,omitempty"` - Description string `json:"description"` - Enum []any `json:"enum,omitempty"` - }{ + Properties: map[string]api.ToolProperty{ "a": { Type: api.PropertyType{"string"}, Description: "The first number to add",