diff --git a/docs/en/resources/tools/_index.md b/docs/en/resources/tools/_index.md index 4d9044c91f64..43a72427f77a 100644 --- a/docs/en/resources/tools/_index.md +++ b/docs/en/resources/tools/_index.md @@ -77,17 +77,18 @@ the parameter. description: Airline unique 2 letter identifier ``` -| **field** | **type** | **required** | **description** | -|---------------|:--------------:|:------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| name | string | true | Name of the parameter. | -| type | string | true | Must be one of "string", "integer", "float", "boolean" "array" | -| description | string | true | Natural language description of the parameter to describe it to the agent. | -| default | parameter type | false | Default value of the parameter. If provided, `required` will be `false`. | -| required | bool | false | Indicate if the parameter is required. Default to `true`. | -| allowedValues | []string | false | Input value will be checked against this field. Regex is also supported. | -| escape | string | false | Only available for type `string`. Indicate the escaping delimiters used for the parameter. This field is intended to be used with templateParameters. Must be one of "single-quotes", "double-quotes", "backticks", "square-brackets". | -| minValue | int or float | false | Only available for type `integer` and `float`. Indicate the minimum value allowed. | -| maxValue | int or float | false | Only available for type `integer` and `float`. Indicate the maximum value allowed. | +| **field** | **type** | **required** | **description** | +|----------------|:--------------:|:------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| name | string | true | Name of the parameter. | +| type | string | true | Must be one of "string", "integer", "float", "boolean" "array" | +| description | string | true | Natural language description of the parameter to describe it to the agent. | +| default | parameter type | false | Default value of the parameter. If provided, `required` will be `false`. | +| required | bool | false | Indicate if the parameter is required. Default to `true`. | +| allowedValues | []string | false | Input value will be checked against this field. Regex is also supported. | +| excludedValues | []string | false | Input value will be checked against this field. Regex is also supported. | +| escape | string | false | Only available for type `string`. Indicate the escaping delimiters used for the parameter. This field is intended to be used with templateParameters. Must be one of "single-quotes", "double-quotes", "backticks", "square-brackets". | +| minValue | int or float | false | Only available for type `integer` and `float`. Indicate the minimum value allowed. | +| maxValue | int or float | false | Only available for type `integer` and `float`. Indicate the maximum value allowed. | ### Array Parameters @@ -108,15 +109,16 @@ in the list using the items field: SELECT * FROM airlines WHERE preferred_airlines = ANY($1); ``` -| **field** | **type** | **required** | **description** | -|---------------|:----------------:|:------------:|----------------------------------------------------------------------------| -| name | string | true | Name of the parameter. | -| type | string | true | Must be "array" | -| description | string | true | Natural language description of the parameter to describe it to the agent. | -| default | parameter type | false | Default value of the parameter. If provided, `required` will be `false`. | -| required | bool | false | Indicate if the parameter is required. Default to `true`. | -| allowedValues | []string | false | Input value will be checked against this field. Regex is also supported. | -| items | parameter object | true | Specify a Parameter object for the type of the values in the array. | +| **field** | **type** | **required** | **description** | +|----------------|:----------------:|:------------:|----------------------------------------------------------------------------| +| name | string | true | Name of the parameter. | +| type | string | true | Must be "array" | +| description | string | true | Natural language description of the parameter to describe it to the agent. | +| default | parameter type | false | Default value of the parameter. If provided, `required` will be `false`. | +| required | bool | false | Indicate if the parameter is required. Default to `true`. | +| allowedValues | []string | false | Input value will be checked against this field. Regex is also supported. | +| excludedValues | []string | false | Input value will be checked against this field. Regex is also supported. | +| items | parameter object | true | Specify a Parameter object for the type of the values in the array. | {{< notice note >}} Items in array should not have a `default` or `required` value. If provided, it @@ -247,15 +249,16 @@ tools: escape: double-quotes # with this, the statement will resolve to `SELECT "id", "name" FROM flights` ``` -| **field** | **type** | **required** | **description** | -|---------------|:----------------:|:---------------:|-------------------------------------------------------------------------------------| -| name | string | true | Name of the template parameter. | -| type | string | true | Must be one of "string", "integer", "float", "boolean", "array" | -| description | string | true | Natural language description of the template parameter to describe it to the agent. | -| default | parameter type | false | Default value of the parameter. If provided, `required` will be `false`. | -| required | bool | false | Indicate if the parameter is required. Default to `true`. | -| allowedValues | []string | false | Input value will be checked against this field. Regex is also supported. | -| items | parameter object | true (if array) | Specify a Parameter object for the type of the values in the array (string only). | +| **field** | **type** | **required** | **description** | +|----------------|:----------------:|:---------------:|-------------------------------------------------------------------------------------| +| name | string | true | Name of the template parameter. | +| type | string | true | Must be one of "string", "integer", "float", "boolean", "array" | +| description | string | true | Natural language description of the template parameter to describe it to the agent. | +| default | parameter type | false | Default value of the parameter. If provided, `required` will be `false`. | +| required | bool | false | Indicate if the parameter is required. Default to `true`. | +| allowedValues | []string | false | Input value will be checked against this field. Regex is also supported. | +| excludedValues | []string | false | Input value will be checked against this field. Regex is also supported. | +| items | parameter object | true (if array) | Specify a Parameter object for the type of the values in the array (string only). | ## Authorized Invocations diff --git a/internal/tools/parameters.go b/internal/tools/parameters.go index c01135c904dd..bceac453b2d2 100644 --- a/internal/tools/parameters.go +++ b/internal/tools/parameters.go @@ -423,13 +423,14 @@ type ParameterMcpManifest struct { // CommonParameter are default fields that are emebdding in most Parameter implementations. Embedding this stuct will give the object Name() and Type() functions. type CommonParameter struct { - Name string `yaml:"name" validate:"required"` - Type string `yaml:"type" validate:"required"` - Desc string `yaml:"description" validate:"required"` - Required *bool `yaml:"required"` - AllowedValues []any `yaml:"allowedValues"` - AuthServices []ParamAuthService `yaml:"authServices"` - AuthSources []ParamAuthService `yaml:"authSources"` // Deprecated: Kept for compatibility. + Name string `yaml:"name" validate:"required"` + Type string `yaml:"type" validate:"required"` + Desc string `yaml:"description" validate:"required"` + Required *bool `yaml:"required"` + AllowedValues []any `yaml:"allowedValues"` + ExcludedValues []any `yaml:"excludedValues"` + AuthServices []ParamAuthService `yaml:"authServices"` + AuthSources []ParamAuthService `yaml:"authSources"` // Deprecated: Kept for compatibility. } // GetName returns the name specified for the Parameter. @@ -469,6 +470,24 @@ func (p *CommonParameter) IsAllowedValues(v any) bool { return false } +// GetExcludedValues returns the excluded values for the Parameter. +func (p *CommonParameter) GetExcludedValues() []any { + return p.ExcludedValues +} + +// IsExcludedValues checks if the value is allowed. +func (p *CommonParameter) IsExcludedValues(v any) bool { + if len(p.ExcludedValues) == 0 { + return false + } + for _, av := range p.ExcludedValues { + if MatchStringOrRegex(v, av) { + return true + } + } + return false +} + // MatchStringOrRegex checks if the input matches the target func MatchStringOrRegex(input, target any) bool { targetS, ok := target.(string) @@ -480,7 +499,7 @@ func MatchStringOrRegex(input, target any) bool { // if target is not regex, run direct comparison return input == target } - inputS := fmt.Sprintf("%s", input) + inputS := fmt.Sprintf("%v", input) return re.MatchString(inputS) } @@ -594,6 +613,19 @@ func NewStringParameterWithAllowedValues(name string, desc string, allowedValues } } +// NewStringParameterWithExcludedValues is a convenience function for initializing a StringParameter with a list of excludedValues +func NewStringParameterWithExcludedValues(name string, desc string, excludedValues []any) *StringParameter { + return &StringParameter{ + CommonParameter: CommonParameter{ + Name: name, + Type: typeString, + Desc: desc, + ExcludedValues: excludedValues, + AuthServices: nil, + }, + } +} + var _ Parameter = &StringParameter{} // StringParameter is a parameter representing the "string" type. @@ -612,6 +644,9 @@ func (p *StringParameter) Parse(v any) (any, error) { if !p.IsAllowedValues(newV) { return nil, fmt.Errorf("%s is not an allowed value", newV) } + if p.IsExcludedValues(newV) { + return nil, fmt.Errorf("%s is an excluded value", newV) + } if p.Escape != nil { return applyEscape(*p.Escape, newV) } @@ -735,6 +770,19 @@ func NewIntParameterWithAllowedValues(name string, desc string, allowedValues [] } } +// NewIntParameterWithExcludedValues is a convenience function for initializing a IntParameter with a list of excludedValues +func NewIntParameterWithExcludedValues(name string, desc string, excludedValues []any) *IntParameter { + return &IntParameter{ + CommonParameter: CommonParameter{ + Name: name, + Type: typeString, + Desc: desc, + ExcludedValues: excludedValues, + AuthServices: nil, + }, + } +} + var _ Parameter = &IntParameter{} // IntParameter is a parameter representing the "int" type. @@ -766,6 +814,9 @@ func (p *IntParameter) Parse(v any) (any, error) { if !p.IsAllowedValues(out) { return nil, fmt.Errorf("%d is not an allowed value", out) } + if p.IsExcludedValues(out) { + return nil, fmt.Errorf("%d is an excluded value", out) + } if p.MinValue != nil && out < *p.MinValue { return nil, fmt.Errorf("%d is under the minimum value", out) } @@ -877,6 +928,19 @@ func NewFloatParameterWithAllowedValues(name string, desc string, allowedValues } } +// NewFloatParameterWithExcludedValues is a convenience function for initializing a FloatParameter with a list of excluded values. +func NewFloatParameterWithExcludedValues(name string, desc string, excludedValues []any) *FloatParameter { + return &FloatParameter{ + CommonParameter: CommonParameter{ + Name: name, + Type: typeFloat, + Desc: desc, + ExcludedValues: excludedValues, + AuthServices: nil, + }, + } +} + var _ Parameter = &FloatParameter{} // FloatParameter is a parameter representing the "float" type. @@ -906,6 +970,9 @@ func (p *FloatParameter) Parse(v any) (any, error) { if !p.IsAllowedValues(out) { return nil, fmt.Errorf("%g is not an allowed value", out) } + if p.IsExcludedValues(out) { + return nil, fmt.Errorf("%g is an excluded value", out) + } if p.MinValue != nil && out < *p.MinValue { return nil, fmt.Errorf("%g is under the minimum value", out) } @@ -1013,6 +1080,19 @@ func NewBooleanParameterWithAllowedValues(name string, desc string, allowedValue } } +// NewBooleanParameterWithExcludedValues is a convenience function for initializing a BooleanParameter with a list of excluded values. +func NewBooleanParameterWithExcludedValues(name string, desc string, excludedValues []any) *BooleanParameter { + return &BooleanParameter{ + CommonParameter: CommonParameter{ + Name: name, + Type: typeBool, + Desc: desc, + ExcludedValues: excludedValues, + AuthServices: nil, + }, + } +} + var _ Parameter = &BooleanParameter{} // BooleanParameter is a parameter representing the "boolean" type. @@ -1029,6 +1109,9 @@ func (p *BooleanParameter) Parse(v any) (any, error) { if !p.IsAllowedValues(newV) { return nil, fmt.Errorf("%t is not an allowed value", newV) } + if p.IsExcludedValues(newV) { + return nil, fmt.Errorf("%t is an excluded value", newV) + } return newV, nil } @@ -1125,6 +1208,20 @@ func NewArrayParameterWithAllowedValues(name string, desc string, allowedValues } } +// NewArrayParameterWithExcludedValues is a convenience function for initializing a ArrayParameter with a list of excluded values. +func NewArrayParameterWithExcludedValues(name string, desc string, excludedValues []any, items Parameter) *ArrayParameter { + return &ArrayParameter{ + CommonParameter: CommonParameter{ + Name: name, + Type: typeArray, + Desc: desc, + ExcludedValues: excludedValues, + AuthServices: nil, + }, + Items: items, + } +} + var _ Parameter = &ArrayParameter{} // ArrayParameter is a parameter representing the "array" type. @@ -1170,6 +1267,19 @@ func (p *ArrayParameter) IsAllowedValues(v []any) bool { return false } +func (p *ArrayParameter) IsExcludedValues(v []any) bool { + a := p.GetExcludedValues() + if len(a) == 0 { + return false + } + for _, av := range a { + if reflect.DeepEqual(v, av) { + return true + } + } + return false +} + func (p *ArrayParameter) Parse(v any) (any, error) { arrVal, ok := v.([]any) if !ok { @@ -1178,6 +1288,9 @@ func (p *ArrayParameter) Parse(v any) (any, error) { if !p.IsAllowedValues(arrVal) { return nil, fmt.Errorf("%s is not an allowed value", arrVal) } + if p.IsExcludedValues(arrVal) { + return nil, fmt.Errorf("%s is an excluded value", arrVal) + } rtn := make([]any, 0, len(arrVal)) for idx, val := range arrVal { val, err := p.Items.Parse(val) @@ -1310,6 +1423,19 @@ func NewMapParameterWithAllowedValues(name string, desc string, allowedValues [] } } +// NewMapParameterWithExcludedValues is a convenience function for initializing a MapParameter with a list of excluded values. +func NewMapParameterWithExcludedValues(name string, desc string, excludedValues []any, valueType string) *MapParameter { + return &MapParameter{ + CommonParameter: CommonParameter{ + Name: name, + Type: "map", + Desc: desc, + ExcludedValues: excludedValues, + }, + ValueType: valueType, + } +} + // UnmarshalYAML handles parsing the MapParameter from YAML input. func (p *MapParameter) UnmarshalYAML(ctx context.Context, unmarshal func(interface{}) error) error { var rawItem struct { @@ -1364,6 +1490,19 @@ func (p *MapParameter) IsAllowedValues(v map[string]any) bool { return false } +func (p *MapParameter) IsExcludedValues(v map[string]any) bool { + a := p.GetExcludedValues() + if len(a) == 0 { + return false + } + for _, av := range a { + if reflect.DeepEqual(v, av) { + return true + } + } + return false +} + // Parse validates and parses an incoming value for the map parameter. func (p *MapParameter) Parse(v any) (any, error) { m, ok := v.(map[string]any) @@ -1373,6 +1512,9 @@ func (p *MapParameter) Parse(v any) (any, error) { if !p.IsAllowedValues(m) { return nil, fmt.Errorf("%s is not an allowed value", m) } + if p.IsExcludedValues(m) { + return nil, fmt.Errorf("%s is an excluded value", m) + } // for generic maps, convert json.Numbers to their corresponding types if p.ValueType == "" { convertedData, err := util.ConvertNumbers(m) diff --git a/internal/tools/parameters_test.go b/internal/tools/parameters_test.go index bf5b422c5b52..9f33e74cc744 100644 --- a/internal/tools/parameters_test.go +++ b/internal/tools/parameters_test.go @@ -750,6 +750,43 @@ func TestParametersParse(t *testing.T) { "my_string": "bar", }, }, + { + name: "string not allowed regex", + params: tools.Parameters{ + tools.NewStringParameterWithAllowedValues("my_string", "this param is a string", []any{"^f.*"}), + }, + in: map[string]any{ + "my_string": "bar", + }, + }, + { + name: "string excluded", + params: tools.Parameters{ + tools.NewStringParameterWithExcludedValues("my_string", "this param is a string", []any{"foo"}), + }, + in: map[string]any{ + "my_string": "foo", + }, + }, + { + name: "string excluded regex", + params: tools.Parameters{ + tools.NewStringParameterWithExcludedValues("my_string", "this param is a string", []any{"^f.*"}), + }, + in: map[string]any{ + "my_string": "foo", + }, + }, + { + name: "string not excluded", + params: tools.Parameters{ + tools.NewStringParameterWithExcludedValues("my_string", "this param is a string", []any{"foo"}), + }, + in: map[string]any{ + "my_string": "bar", + }, + want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: "bar"}}, + }, { name: "string with escape backticks", params: tools.Parameters{ @@ -829,6 +866,16 @@ func TestParametersParse(t *testing.T) { }, want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 1}}, }, + { + name: "int allowed regex", + params: tools.Parameters{ + tools.NewIntParameterWithAllowedValues("my_int", "this param is an int", []any{"^\\d{2}$"}), + }, + in: map[string]any{ + "my_int": 10, + }, + want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 10}}, + }, { name: "int not allowed", params: tools.Parameters{ @@ -838,6 +885,53 @@ func TestParametersParse(t *testing.T) { "my_int": 2, }, }, + { + name: "int not allowed regex", + params: tools.Parameters{ + tools.NewIntParameterWithAllowedValues("my_int", "this param is an int", []any{"^\\d{2}$"}), + }, + in: map[string]any{ + "my_int": 100, + }, + }, + { + name: "int excluded", + params: tools.Parameters{ + tools.NewIntParameterWithExcludedValues("my_int", "this param is an int", []any{1}), + }, + in: map[string]any{ + "my_int": 1, + }, + }, + { + name: "int excluded regex", + params: tools.Parameters{ + tools.NewIntParameterWithExcludedValues("my_int", "this param is an int", []any{"^\\d{2}$"}), + }, + in: map[string]any{ + "my_int": 10, + }, + }, + { + name: "int not excluded", + params: tools.Parameters{ + tools.NewIntParameterWithExcludedValues("my_int", "this param is an int", []any{1}), + }, + in: map[string]any{ + "my_int": 2, + }, + want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 2}}, + }, + { + name: "int not excluded regex", + params: tools.Parameters{ + tools.NewIntParameterWithExcludedValues("my_int", "this param is an int", []any{"^\\d{2}$"}), + }, + in: map[string]any{ + "my_int": 2, + }, + want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 2}}, + }, { name: "int minValue", params: tools.Parameters{ @@ -905,6 +999,16 @@ func TestParametersParse(t *testing.T) { }, want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 1.1}}, }, + { + name: "float allowed regex", + params: tools.Parameters{ + tools.NewFloatParameterWithAllowedValues("my_float", "this param is a float", []any{"^0\\.\\d+$"}), + }, + in: map[string]any{ + "my_float": 0.99, + }, + want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 0.99}}, + }, { name: "float not allowed", params: tools.Parameters{ @@ -914,6 +1018,54 @@ func TestParametersParse(t *testing.T) { "my_float": 1.2, }, }, + { + name: "float not allowed regex", + params: tools.Parameters{ + tools.NewFloatParameterWithAllowedValues("my_float", "this param is a float", []any{"^0\\.\\d+$"}), + }, + in: map[string]any{ + "my_float": 1.99, + }, + }, + { + name: "float excluded", + params: tools.Parameters{ + tools.NewFloatParameterWithExcludedValues("my_float", "this param is a float", []any{1.1}), + }, + in: map[string]any{ + "my_float": 1.1, + }, + }, + { + name: "float excluded regex", + params: tools.Parameters{ + tools.NewFloatParameterWithExcludedValues("my_float", "this param is a float", []any{"^0\\.\\d+$"}), + }, + in: map[string]any{ + "my_float": 0.99, + }, + }, + { + name: "float not excluded", + params: tools.Parameters{ + tools.NewFloatParameterWithExcludedValues("my_float", "this param is a float", []any{1.1}), + }, + in: map[string]any{ + "my_float": 1.2, + }, + want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 1.2}}, + }, + { + name: "float not excluded regex", + params: tools.Parameters{ + tools.NewFloatParameterWithExcludedValues("my_float", "this param is a float", []any{"^0\\.\\d+$"}), + }, + in: map[string]any{ + "my_float": 1.99, + }, + want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 1.99}}, + }, + { name: "float minValue", params: tools.Parameters{ @@ -990,6 +1142,25 @@ func TestParametersParse(t *testing.T) { "my_bool": true, }, }, + { + name: "bool excluded", + params: tools.Parameters{ + tools.NewBooleanParameterWithExcludedValues("my_bool", "this param is a bool", []any{true}), + }, + in: map[string]any{ + "my_bool": true, + }, + }, + { + name: "bool not excluded", + params: tools.Parameters{ + tools.NewBooleanParameterWithExcludedValues("my_bool", "this param is a bool", []any{false}), + }, + in: map[string]any{ + "my_bool": true, + }, + want: tools.ParamValues{tools.ParamValue{Name: "my_bool", Value: true}}, + }, { name: "string default", params: tools.Parameters{ @@ -1136,6 +1307,25 @@ func TestParametersParse(t *testing.T) { "my_map": map[string]any{"key1": "val2"}, }, }, + { + name: "map excluded", + params: tools.Parameters{ + tools.NewMapParameterWithExcludedValues("my_map", "a map", []any{map[string]any{"key1": "val1"}}, "string"), + }, + in: map[string]any{ + "my_map": map[string]any{"key1": "val1"}, + }, + }, + { + name: "map not excluded", + params: tools.Parameters{ + tools.NewMapParameterWithExcludedValues("my_map", "a map", []any{map[string]any{"key1": "val1"}}, "string"), + }, + in: map[string]any{ + "my_map": map[string]any{"key1": "val2"}, + }, + want: tools.ParamValues{tools.ParamValue{Name: "my_map", Value: map[string]any{"key1": "val2"}}}, + }, } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) {