Initial commit
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
package openapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ReflectRegistry provides helper methods for registering tools using reflection.
|
||||
type ReflectRegistry struct {
|
||||
*MapRegistry
|
||||
}
|
||||
|
||||
// NewReflectRegistry creates a new ReflectRegistry wrapping a MapRegistry.
|
||||
func NewReflectRegistry(r *MapRegistry) *ReflectRegistry {
|
||||
return &ReflectRegistry{MapRegistry: r}
|
||||
}
|
||||
|
||||
// RegisterTool registers a tool with a typed handler.
|
||||
// The handler's third argument must be a struct that defines the tool's parameters.
|
||||
func RegisterTool[T any](r *MapRegistry, name, description string, handler func(context.Context, *Session, T) (any, error)) error {
|
||||
var arg T
|
||||
schema, err := generateSchema(reflect.TypeOf(arg))
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate schema: %w", err)
|
||||
}
|
||||
|
||||
wrappedHandler := func(ctx context.Context, s *Session, args json.RawMessage) (any, error) {
|
||||
var params T
|
||||
if err := json.Unmarshal(args, ¶ms); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal arguments: %w", err)
|
||||
}
|
||||
return handler(ctx, s, params)
|
||||
}
|
||||
|
||||
return r.Register(name, description, schema, wrappedHandler)
|
||||
}
|
||||
|
||||
func generateSchema(t reflect.Type) (map[string]any, error) {
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("expected struct, got %s", t.Kind())
|
||||
}
|
||||
|
||||
properties := make(map[string]any)
|
||||
var required []string
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag == "" || jsonTag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Split json tag to handle omitempty etc.
|
||||
parts := strings.Split(jsonTag, ",")
|
||||
name := parts[0]
|
||||
|
||||
prop := make(map[string]any)
|
||||
|
||||
switch field.Type.Kind() {
|
||||
case reflect.String:
|
||||
prop["type"] = "string"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
prop["type"] = "integer"
|
||||
case reflect.Float32, reflect.Float64:
|
||||
prop["type"] = "number"
|
||||
case reflect.Bool:
|
||||
prop["type"] = "boolean"
|
||||
case reflect.Slice:
|
||||
prop["type"] = "array"
|
||||
// Simple slice handling, could be improved
|
||||
itemType, _ := getJsonType(field.Type.Elem())
|
||||
prop["items"] = map[string]string{"type": itemType}
|
||||
case reflect.Struct:
|
||||
subSchema, err := generateSchema(field.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prop = subSchema
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported field type: %s", field.Type.Kind())
|
||||
}
|
||||
|
||||
if desc := field.Tag.Get("desc"); desc != "" {
|
||||
prop["description"] = desc
|
||||
}
|
||||
|
||||
properties[name] = prop
|
||||
|
||||
// Determine if field is required.
|
||||
// Priority: 'required' tag > 'omitempty' in 'json' tag.
|
||||
isRequired := true
|
||||
for _, p := range parts[1:] {
|
||||
if p == "omitempty" {
|
||||
isRequired = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if reqTag := field.Tag.Get("required"); reqTag != "" {
|
||||
isRequired = (reqTag == "true")
|
||||
}
|
||||
|
||||
if isRequired {
|
||||
required = append(required, name)
|
||||
}
|
||||
}
|
||||
|
||||
schema := map[string]any{
|
||||
"type": "object",
|
||||
"properties": properties,
|
||||
}
|
||||
if len(required) > 0 {
|
||||
schema["required"] = required
|
||||
}
|
||||
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
func getJsonType(t reflect.Type) (string, error) {
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
return "string", nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return "integer", nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return "number", nil
|
||||
case reflect.Bool:
|
||||
return "boolean", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported item type: %s", t.Kind())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user