mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-01-11 16:38:15 -05:00
Compare commits
3 Commits
config-pre
...
map
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0f94a559b | ||
|
|
32dbb0c0f4 | ||
|
|
ce2c121c95 |
@@ -77,12 +77,12 @@ 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" |
|
||||
| default | parameter type | false | Default value of the parameter. If provided, the parameter is not required. |
|
||||
| description | string | true | Natural language description of the parameter to describe it to the agent. |
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------------:|:------------:|-----------------------------------------------------------------------------|
|
||||
| name | string | true | Name of the parameter. |
|
||||
| type | string | true | Must be one of "string", "integer", "float", "boolean" "array" |
|
||||
| default | parameter type | false | Default value of the parameter. If provided, the parameter is not required. |
|
||||
| description | string | true | Natural language description of the parameter to describe it to the agent. |
|
||||
|
||||
### Array Parameters
|
||||
|
||||
@@ -107,7 +107,7 @@ in the list using the items field:
|
||||
|-------------|:----------------:|:------------:|-----------------------------------------------------------------------------|
|
||||
| name | string | true | Name of the parameter. |
|
||||
| type | string | true | Must be "array" |
|
||||
| default | parameter type | false | Default value of the parameter. If provided, the parameter is not required. |
|
||||
| default | parameter type | false | Default value of the parameter. If provided, the parameter is not required. |
|
||||
| description | string | true | Natural language description of the parameter to describe it to the agent. |
|
||||
| items | parameter object | true | Specify a Parameter object for the type of the values in the array. |
|
||||
|
||||
@@ -115,6 +115,43 @@ in the list using the items field:
|
||||
Items in array should not have a default value. If provided, it will be ignored.
|
||||
{{< /notice >}}
|
||||
|
||||
### Object Parameters
|
||||
|
||||
The object type is a collection of key-value pairs passed in as a single
|
||||
parameter. To use the object type, you must specify the schema for each
|
||||
key-value pair using the properties field.
|
||||
|
||||
```yaml
|
||||
parameters:
|
||||
- name: new_user
|
||||
type: object
|
||||
description: A new user's profile information.
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: The full name of the user.
|
||||
age:
|
||||
type: integer
|
||||
description: The age of the user.
|
||||
is_subscriber:
|
||||
type: boolean
|
||||
description: Whether the user is a subscriber.
|
||||
statement: |
|
||||
INSERT INTO users (name, age, is_subscriber) VALUES ($1->>'name', ($1->>'age')::integer, ($1->>'is_subscriber')::boolean);
|
||||
```
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:------------------------:|:------------:|---------------------------------------------------------------------------------------------------------------------|
|
||||
| name | string | true | Name of the parameter. |
|
||||
| type | string | true | Must be "object" |
|
||||
| default | parameter type | false | Default value of the parameter. If provided, the parameter is not required. |
|
||||
| description | string | true | Natural language description of the parameter to describe it to the agent. |
|
||||
| properties | map of parameter objects | true | A map where each key is a property name and each value is a Parameter object defining the schema for that property. |
|
||||
|
||||
{{< notice note >}}
|
||||
Properties within an object should not have a default value. If provided, it will be ignored. A default can only be provided for the top-level object parameter.
|
||||
{{< /notice >}}
|
||||
|
||||
### Authenticated Parameters
|
||||
|
||||
Authenticated parameters are automatically populated with user
|
||||
@@ -143,10 +180,10 @@ user's ID token.
|
||||
field: sub
|
||||
```
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-----------|:--------:|:------------:|-----------------------------------------------------------------------------------------|
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-----------|:--------:|:------------:|---------------------------------------------------------------------------------|
|
||||
| name | string | true | Name of the [authServices](../authservices) used to verify the OIDC auth token. |
|
||||
| field | string | true | Claim field decoded from the OIDC token used to auto-populate this parameter. |
|
||||
| field | string | true | Claim field decoded from the OIDC token used to auto-populate this parameter. |
|
||||
|
||||
### Template Parameters
|
||||
|
||||
@@ -195,12 +232,12 @@ tools:
|
||||
description: Name of a column to select
|
||||
```
|
||||
|
||||
| **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. |
|
||||
| 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. |
|
||||
| items | parameter object | true (if array) | Specify a Parameter object for the type of the values in the array (string only). |
|
||||
|
||||
## Authorized Invocations
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ const (
|
||||
typeFloat = "float"
|
||||
typeBool = "boolean"
|
||||
typeArray = "array"
|
||||
typeObject = "object"
|
||||
)
|
||||
|
||||
// ParamValues is an ordered list of ParamValue
|
||||
@@ -367,6 +368,17 @@ func parseParamFromDelayedUnmarshaler(ctx context.Context, u *util.DelayedUnmars
|
||||
a.AuthSources = nil
|
||||
}
|
||||
return a, nil
|
||||
case typeObject:
|
||||
a := &ObjectParameter{}
|
||||
if err := dec.DecodeContext(ctx, a); err != nil {
|
||||
return nil, fmt.Errorf("unable to parse as %q: %w", t, err)
|
||||
}
|
||||
if a.AuthSources != nil {
|
||||
logger.WarnContext(ctx, "`authSources` is deprecated, use `authServices` for parameters instead")
|
||||
a.AuthServices = append(a.AuthServices, a.AuthSources...)
|
||||
a.AuthSources = nil
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%q is not valid type for a parameter", t)
|
||||
}
|
||||
@@ -401,19 +413,23 @@ func (ps Parameters) McpManifest() McpToolsSchema {
|
||||
|
||||
// ParameterManifest represents parameters when served as part of a ToolManifest.
|
||||
type ParameterManifest struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Required bool `json:"required"`
|
||||
Description string `json:"description"`
|
||||
AuthServices []string `json:"authSources"`
|
||||
Items *ParameterManifest `json:"items,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Required bool `json:"required"`
|
||||
Description string `json:"description"`
|
||||
AuthServices []string `json:"authSources"`
|
||||
Items *ParameterManifest `json:"items,omitempty"`
|
||||
Properties map[string]*ParameterManifest `json:"properties,omitempty"`
|
||||
AdditionalProperties *ParameterManifest `json:"additionalProperties,omitempty"`
|
||||
}
|
||||
|
||||
// ParameterMcpManifest represents properties when served as part of a ToolMcpManifest.
|
||||
type ParameterMcpManifest struct {
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
Items *ParameterMcpManifest `json:"items,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
Items *ParameterMcpManifest `json:"items,omitempty"`
|
||||
Properties map[string]*ParameterMcpManifest `json:"properties,omitempty"`
|
||||
AdditionalProperties *ParameterMcpManifest `json:"addtionalProperties,omitempty"`
|
||||
}
|
||||
|
||||
// CommonParameter are default fields that are emebdding in most Parameter implementations. Embedding this stuct will give the object Name() and Type() functions.
|
||||
@@ -1022,3 +1038,194 @@ func (p *ArrayParameter) McpManifest() ParameterMcpManifest {
|
||||
Items: &items,
|
||||
}
|
||||
}
|
||||
|
||||
// NewObjectParameter is a convenience function for initializing a ObjectParameter.
|
||||
func NewObjectParameter(name string, desc string, properties map[string]Parameter, additionalProperties Parameter) *ObjectParameter {
|
||||
return &ObjectParameter{
|
||||
CommonParameter: CommonParameter{
|
||||
Name: name,
|
||||
Type: typeObject,
|
||||
Desc: desc,
|
||||
AuthServices: nil,
|
||||
},
|
||||
Properties: properties,
|
||||
AdditionalProperties: additionalProperties,
|
||||
}
|
||||
}
|
||||
|
||||
// NewObjectParameterWithDefault is a convenience function for initializing a ObjectParameter with default value.
|
||||
func NewObjectParameterWithDefault(name string, defaultV map[string]any, desc string, properties map[string]Parameter, additionalProperties Parameter) *ObjectParameter {
|
||||
return &ObjectParameter{
|
||||
CommonParameter: CommonParameter{
|
||||
Name: name,
|
||||
Type: typeObject,
|
||||
Desc: desc,
|
||||
AuthServices: nil,
|
||||
},
|
||||
Default: &defaultV,
|
||||
Properties: properties,
|
||||
AdditionalProperties: additionalProperties,
|
||||
}
|
||||
}
|
||||
|
||||
// NewObjectParameterWithAuth is a convenience function for initializing a ObjectParameter with a list of ParamAuthService.
|
||||
func NewObjectParameterWithAuth(name string, desc string, authServices []ParamAuthService, properties map[string]Parameter, additionalProperties Parameter) *ObjectParameter {
|
||||
return &ObjectParameter{
|
||||
CommonParameter: CommonParameter{
|
||||
Name: name,
|
||||
Type: typeObject,
|
||||
Desc: desc,
|
||||
AuthServices: authServices,
|
||||
},
|
||||
Properties: properties,
|
||||
AdditionalProperties: additionalProperties,
|
||||
}
|
||||
}
|
||||
|
||||
var _ Parameter = &ObjectParameter{}
|
||||
|
||||
// ObjectParameter is a parameter representing the "map" type.
|
||||
type ObjectParameter struct {
|
||||
CommonParameter `yaml:",inline"`
|
||||
Default *map[string]any `yaml:"default"`
|
||||
Properties map[string]Parameter `yaml:"properties"`
|
||||
AdditionalProperties Parameter `yaml:"addtionalProperties"`
|
||||
}
|
||||
|
||||
func (p *ObjectParameter) UnmarshalYAML(ctx context.Context, unmarshal func(interface{}) error) error {
|
||||
var rawItem struct {
|
||||
CommonParameter `yaml:",inline"`
|
||||
Default *map[string]any `yaml:"default"`
|
||||
Properties map[string]*util.DelayedUnmarshaler `yaml:"properties"`
|
||||
AdditionalProperties *util.DelayedUnmarshaler `yaml:"additionalProperties"`
|
||||
}
|
||||
if err := unmarshal(&rawItem); err != nil {
|
||||
return err
|
||||
}
|
||||
p.CommonParameter = rawItem.CommonParameter
|
||||
p.Default = rawItem.Default
|
||||
|
||||
if rawItem.AdditionalProperties != nil {
|
||||
param, err := parseParamFromDelayedUnmarshaler(ctx, rawItem.AdditionalProperties)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse additionalProperties: %w", err)
|
||||
}
|
||||
p.AdditionalProperties = param
|
||||
}
|
||||
|
||||
if len(rawItem.Properties) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.Properties = make(map[string]Parameter, len(rawItem.Properties))
|
||||
for key, delayedParam := range rawItem.Properties {
|
||||
// Parse individual property parameters
|
||||
param, err := parseParamFromDelayedUnmarshaler(ctx, delayedParam)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse property %q: %w", key, err)
|
||||
}
|
||||
p.Properties[key] = param
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ObjectParameter) Parse(v any) (any, error) {
|
||||
objVal, ok := v.(map[string]any)
|
||||
if !ok {
|
||||
return nil, &ParseTypeError{p.Name, p.Type, v}
|
||||
}
|
||||
|
||||
parsedObj := make(map[string]any, len(objVal))
|
||||
|
||||
for key, val := range objVal {
|
||||
var (
|
||||
parsedVal any
|
||||
err error
|
||||
)
|
||||
propertySchema, isDefinedProperty := p.Properties[key]
|
||||
|
||||
if isDefinedProperty {
|
||||
// If the property is explicitly defined in the schema.
|
||||
parsedVal, err = propertySchema.Parse(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse property %q: %w", key, err)
|
||||
}
|
||||
} else if p.AdditionalProperties != nil {
|
||||
// If the property is not defined, but the schema allows additional properties.
|
||||
parsedVal, err = p.AdditionalProperties.Parse(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse additional property %q: %w", key, err)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("unknown property %q found and additional properties are not allowed", key)
|
||||
}
|
||||
|
||||
parsedObj[key] = parsedVal
|
||||
}
|
||||
|
||||
return parsedObj, nil
|
||||
}
|
||||
|
||||
func (p *ObjectParameter) GetAuthServices() []ParamAuthService {
|
||||
return p.AuthServices
|
||||
}
|
||||
|
||||
func (p *ObjectParameter) GetDefault() any {
|
||||
if p.Default == nil {
|
||||
return nil
|
||||
}
|
||||
return *p.Default
|
||||
}
|
||||
|
||||
// Manifest returns the manifest for the ObjectParameter.
|
||||
func (p *ObjectParameter) Manifest() ParameterManifest {
|
||||
// only list ParamAuthService names (without fields) in manifest
|
||||
authNames := make([]string, len(p.AuthServices))
|
||||
for i, a := range p.AuthServices {
|
||||
authNames[i] = a.Name
|
||||
}
|
||||
required := p.Default == nil
|
||||
propertiesManifest := make(map[string]*ParameterManifest, len(p.Properties))
|
||||
for key, p := range p.Properties {
|
||||
m := p.Manifest()
|
||||
propertiesManifest[key] = &m
|
||||
}
|
||||
var apManifest ParameterManifest
|
||||
if p.AdditionalProperties != nil {
|
||||
apManifest = p.AdditionalProperties.Manifest()
|
||||
}
|
||||
return ParameterManifest{
|
||||
Name: p.Name,
|
||||
Type: p.Type,
|
||||
Required: required,
|
||||
Description: p.Desc,
|
||||
AuthServices: authNames,
|
||||
Properties: propertiesManifest,
|
||||
AdditionalProperties: &apManifest,
|
||||
}
|
||||
}
|
||||
|
||||
// McpManifest returns the MCP manifest for the ObjectParameter.
|
||||
func (p *ObjectParameter) McpManifest() ParameterMcpManifest {
|
||||
// only list ParamAuthService names (without fields) in manifest
|
||||
authNames := make([]string, len(p.AuthServices))
|
||||
for i, a := range p.AuthServices {
|
||||
authNames[i] = a.Name
|
||||
}
|
||||
propertiesManifest := make(map[string]*ParameterMcpManifest, len(p.Properties))
|
||||
for key, p := range p.Properties {
|
||||
m := p.McpManifest()
|
||||
propertiesManifest[key] = &m
|
||||
}
|
||||
var apManifest ParameterMcpManifest
|
||||
if p.AdditionalProperties != nil {
|
||||
apManifest = p.AdditionalProperties.McpManifest()
|
||||
}
|
||||
return ParameterMcpManifest{
|
||||
Type: p.Type,
|
||||
Description: p.Desc,
|
||||
Properties: propertiesManifest,
|
||||
AdditionalProperties: &apManifest,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
)
|
||||
@@ -200,6 +201,31 @@ func TestParametersMarshal(t *testing.T) {
|
||||
tools.NewArrayParameter("my_array", "this param is an array of floats", tools.NewFloatParameter("my_float", "float item")),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "object",
|
||||
in: []map[string]any{
|
||||
{
|
||||
"name": "my_object",
|
||||
"type": "object",
|
||||
"description": "this param is an object",
|
||||
"properties": map[string]any{
|
||||
"k1": map[string]any{
|
||||
"name": "k1",
|
||||
"type": "float",
|
||||
"description": "float property",
|
||||
},
|
||||
"k2": map[string]any{
|
||||
"name": "k2",
|
||||
"type": "string",
|
||||
"description": "string property",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: tools.Parameters{
|
||||
tools.NewObjectParameter("my_object", "this param is an object", map[string]tools.Parameter{"k1": tools.NewFloatParameter("k1", "float property"), "k2": tools.NewStringParameter("k2", "string property")}, nil),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string default",
|
||||
in: []map[string]any{
|
||||
@@ -294,6 +320,37 @@ func TestParametersMarshal(t *testing.T) {
|
||||
tools.NewArrayParameterWithDefault("my_array", []any{1.0, 1.1}, "this param is an array of floats", tools.NewFloatParameter("my_float", "float item")),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "object",
|
||||
in: []map[string]any{
|
||||
{
|
||||
"name": "my_object",
|
||||
"type": "object",
|
||||
"default": map[string]any{"hello": "world"},
|
||||
"description": "this param is an object",
|
||||
"properties": map[string]any{
|
||||
"k1": map[string]any{
|
||||
"name": "k1",
|
||||
"type": "float",
|
||||
"description": "float property",
|
||||
},
|
||||
"k2": map[string]any{
|
||||
"name": "k2",
|
||||
"type": "string",
|
||||
"description": "string property",
|
||||
},
|
||||
},
|
||||
"additionalProperties": map[string]any{
|
||||
"name": "strProperty",
|
||||
"type": "string",
|
||||
"description": "string property",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: tools.Parameters{
|
||||
tools.NewObjectParameterWithDefault("my_object", map[string]any{"hello": "world"}, "this param is an object", map[string]tools.Parameter{"k1": tools.NewFloatParameter("k1", "float property"), "k2": tools.NewStringParameter("k2", "string property")}, tools.NewStringParameter("strProperty", "string property")),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -350,13 +407,13 @@ func TestAuthParametersMarshal(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string with authSources",
|
||||
name: "string with authServices",
|
||||
in: []map[string]any{
|
||||
{
|
||||
"name": "my_string",
|
||||
"type": "string",
|
||||
"description": "this param is a string",
|
||||
"authSources": []map[string]string{
|
||||
"authServices": []map[string]string{
|
||||
{
|
||||
"name": "my-google-auth-service",
|
||||
"field": "user_id",
|
||||
@@ -396,13 +453,13 @@ func TestAuthParametersMarshal(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "int with authSources",
|
||||
name: "int with authServices",
|
||||
in: []map[string]any{
|
||||
{
|
||||
"name": "my_integer",
|
||||
"type": "integer",
|
||||
"description": "this param is an int",
|
||||
"authSources": []map[string]string{
|
||||
"authServices": []map[string]string{
|
||||
{
|
||||
"name": "my-google-auth-service",
|
||||
"field": "user_id",
|
||||
@@ -442,13 +499,13 @@ func TestAuthParametersMarshal(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "float with authSources",
|
||||
name: "float with authServices",
|
||||
in: []map[string]any{
|
||||
{
|
||||
"name": "my_float",
|
||||
"type": "float",
|
||||
"description": "my param is a float",
|
||||
"authSources": []map[string]string{
|
||||
"authServices": []map[string]string{
|
||||
{
|
||||
"name": "my-google-auth-service",
|
||||
"field": "user_id",
|
||||
@@ -488,13 +545,13 @@ func TestAuthParametersMarshal(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bool with authSources",
|
||||
name: "bool with authServices",
|
||||
in: []map[string]any{
|
||||
{
|
||||
"name": "my_bool",
|
||||
"type": "boolean",
|
||||
"description": "this param is a boolean",
|
||||
"authSources": []map[string]string{
|
||||
"authServices": []map[string]string{
|
||||
{
|
||||
"name": "my-google-auth-service",
|
||||
"field": "user_id",
|
||||
@@ -539,7 +596,7 @@ func TestAuthParametersMarshal(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string array with authSources",
|
||||
name: "string array with authServices",
|
||||
in: []map[string]any{
|
||||
{
|
||||
"name": "my_array",
|
||||
@@ -550,7 +607,7 @@ func TestAuthParametersMarshal(t *testing.T) {
|
||||
"type": "string",
|
||||
"description": "string item",
|
||||
},
|
||||
"authSources": []map[string]string{
|
||||
"authServices": []map[string]string{
|
||||
{
|
||||
"name": "my-google-auth-service",
|
||||
"field": "user_id",
|
||||
@@ -594,6 +651,46 @@ func TestAuthParametersMarshal(t *testing.T) {
|
||||
tools.NewArrayParameterWithAuth("my_array", "this param is an array of floats", tools.NewFloatParameter("my_float", "float item"), authServices),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "object",
|
||||
in: []map[string]any{
|
||||
{
|
||||
"name": "my_object",
|
||||
"type": "object",
|
||||
"description": "this param is an object",
|
||||
"authServices": []map[string]string{
|
||||
{
|
||||
"name": "my-google-auth-service",
|
||||
"field": "user_id",
|
||||
},
|
||||
{
|
||||
"name": "other-auth-service",
|
||||
"field": "user_id",
|
||||
},
|
||||
},
|
||||
"properties": map[string]any{
|
||||
"k1": map[string]any{
|
||||
"name": "k1",
|
||||
"type": "float",
|
||||
"description": "float property",
|
||||
},
|
||||
"k2": map[string]any{
|
||||
"name": "k2",
|
||||
"type": "string",
|
||||
"description": "string property",
|
||||
},
|
||||
},
|
||||
"additionalProperties": map[string]any{
|
||||
"name": "strProperty",
|
||||
"type": "string",
|
||||
"description": "string property",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: tools.Parameters{
|
||||
tools.NewObjectParameterWithAuth("my_object", "this param is an object", authServices, map[string]tools.Parameter{"k1": tools.NewFloatParameter("k1", "float property"), "k2": tools.NewStringParameter("k2", "string property")}, tools.NewStringParameter("strProperty", "string property")),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -617,10 +714,11 @@ func TestAuthParametersMarshal(t *testing.T) {
|
||||
|
||||
func TestParametersParse(t *testing.T) {
|
||||
tcs := []struct {
|
||||
name string
|
||||
params tools.Parameters
|
||||
in map[string]any
|
||||
want tools.ParamValues
|
||||
name string
|
||||
params tools.Parameters
|
||||
in map[string]any
|
||||
want tools.ParamValues
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "string",
|
||||
@@ -640,6 +738,7 @@ func TestParametersParse(t *testing.T) {
|
||||
in: map[string]any{
|
||||
"my_string": 4,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "int",
|
||||
@@ -659,16 +758,17 @@ func TestParametersParse(t *testing.T) {
|
||||
in: map[string]any{
|
||||
"my_int": 14.5,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "not int (big)",
|
||||
name: "int from json.Number",
|
||||
params: tools.Parameters{
|
||||
tools.NewIntParameter("my_int", "this param is an int"),
|
||||
},
|
||||
in: map[string]any{
|
||||
"my_int": math.MaxInt64,
|
||||
"my_int": json.Number("9223372036854775807"),
|
||||
},
|
||||
want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: math.MaxInt64}},
|
||||
want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: int(math.MaxInt64)}},
|
||||
},
|
||||
{
|
||||
name: "float",
|
||||
@@ -688,6 +788,7 @@ func TestParametersParse(t *testing.T) {
|
||||
in: map[string]any{
|
||||
"my_float": true,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "bool",
|
||||
@@ -707,6 +808,7 @@ func TestParametersParse(t *testing.T) {
|
||||
in: map[string]any{
|
||||
"my_bool": 1.5,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "string default",
|
||||
@@ -780,44 +882,103 @@ func TestParametersParse(t *testing.T) {
|
||||
in: map[string]any{},
|
||||
want: tools.ParamValues{tools.ParamValue{Name: "my_bool", Value: nil}},
|
||||
},
|
||||
{
|
||||
name: "array of strings",
|
||||
params: tools.Parameters{
|
||||
tools.NewArrayParameter("my_array", "an array", tools.NewStringParameter("item", "a string item")),
|
||||
},
|
||||
in: map[string]any{"my_array": []any{"a", "b", "c"}},
|
||||
want: tools.ParamValues{tools.ParamValue{Name: "my_array", Value: []any{"a", "b", "c"}}},
|
||||
},
|
||||
{
|
||||
name: "array with item type mismatch",
|
||||
params: tools.Parameters{
|
||||
tools.NewArrayParameter("my_array", "an array", tools.NewIntParameter("item", "an int item")),
|
||||
},
|
||||
in: map[string]any{"my_array": []any{1, "b", 3}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "array not required",
|
||||
params: tools.Parameters{
|
||||
tools.NewArrayParameterWithRequired("my_array", "an array", false, tools.NewStringParameter("item", "a string item")),
|
||||
},
|
||||
in: map[string]any{},
|
||||
want: tools.ParamValues{tools.ParamValue{Name: "my_array", Value: nil}},
|
||||
},
|
||||
{
|
||||
name: "array with default",
|
||||
params: tools.Parameters{
|
||||
tools.NewArrayParameterWithDefault("my_array", []any{"x", "y"}, "an array", tools.NewStringParameter("item", "a string item")),
|
||||
},
|
||||
in: map[string]any{},
|
||||
want: tools.ParamValues{tools.ParamValue{Name: "my_array", Value: []any{"x", "y"}}},
|
||||
},
|
||||
{
|
||||
name: "object",
|
||||
params: tools.Parameters{
|
||||
tools.NewObjectParameter("my_object", "an object", map[string]tools.Parameter{
|
||||
"key1": tools.NewStringParameter("key1", "string value"),
|
||||
"key2": tools.NewIntParameter("key2", "int value"),
|
||||
}, nil),
|
||||
},
|
||||
in: map[string]any{"my_object": map[string]any{
|
||||
"key1": "hello",
|
||||
"key2": 123,
|
||||
}},
|
||||
want: tools.ParamValues{tools.ParamValue{Name: "my_object", Value: map[string]any{
|
||||
"key1": "hello",
|
||||
"key2": 123,
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "object with property type mismatch",
|
||||
params: tools.Parameters{
|
||||
tools.NewObjectParameter("my_object", "an object", map[string]tools.Parameter{
|
||||
"key1": tools.NewStringParameter("key1", "string value"),
|
||||
}, nil),
|
||||
},
|
||||
in: map[string]any{"my_object": map[string]any{"key1": 123}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "object with missing required property",
|
||||
params: tools.Parameters{
|
||||
tools.NewObjectParameter("my_object", "an object", map[string]tools.Parameter{
|
||||
"required_key": tools.NewStringParameter("required_key", "a required value"),
|
||||
}, nil),
|
||||
},
|
||||
in: map[string]any{"my_object": map[string]any{"another_key": "foo"}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "object with default",
|
||||
params: tools.Parameters{
|
||||
tools.NewObjectParameterWithDefault("my_object", map[string]any{"key1": "default"}, "an object", map[string]tools.Parameter{
|
||||
"key1": tools.NewStringParameter("key1", "string value"),
|
||||
}, nil),
|
||||
},
|
||||
in: map[string]any{},
|
||||
want: tools.ParamValues{tools.ParamValue{Name: "my_object", Value: map[string]any{"key1": "default"}}},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// parse map to bytes
|
||||
data, err := json.Marshal(tc.in)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to marshal input to yaml: %s", err)
|
||||
}
|
||||
// parse bytes to object
|
||||
var m map[string]any
|
||||
got, err := tools.ParseParams(tc.params, tc.in, make(map[string]map[string]any))
|
||||
|
||||
d := json.NewDecoder(bytes.NewReader(data))
|
||||
d.UseNumber()
|
||||
err = d.Decode(&m)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
|
||||
wantErr := len(tc.want) == 0 // error is expected if no items in want
|
||||
gotAll, err := tools.ParseParams(tc.params, m, make(map[string]map[string]any))
|
||||
if err != nil {
|
||||
if wantErr {
|
||||
return
|
||||
if tc.wantErr {
|
||||
if err == nil {
|
||||
t.Fatal("expected error but got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from ParseParams: %s", err)
|
||||
}
|
||||
if wantErr {
|
||||
t.Fatalf("expected error but Param parsed successfully: %s", gotAll)
|
||||
}
|
||||
for i, got := range gotAll {
|
||||
want := tc.want[i]
|
||||
if got != want {
|
||||
t.Fatalf("unexpected value: got %q, want %q", got, want)
|
||||
}
|
||||
gotType, wantType := reflect.TypeOf(got), reflect.TypeOf(want)
|
||||
if gotType != wantType {
|
||||
t.Fatalf("unexpected value: got %q, want %q", got, want)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Fatalf("ParseParams() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1075,7 +1236,27 @@ func TestParamManifest(t *testing.T) {
|
||||
Required: true,
|
||||
Description: "bar",
|
||||
AuthServices: []string{},
|
||||
Items: &tools.ParameterManifest{Name: "foo-string", Type: "string", Required: true, Description: "bar", AuthServices: []string{}},
|
||||
Items: &tools.ParameterManifest{
|
||||
Name: "foo-string",
|
||||
Type: "string",
|
||||
Required: true,
|
||||
Description: "bar",
|
||||
AuthServices: []string{}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "object",
|
||||
in: tools.NewObjectParameter("foo-object", "bar", map[string]tools.Parameter{"propertyName": tools.NewStringParameter("property", "property desc")}, tools.NewStringParameter("strProperty", "string property")),
|
||||
want: tools.ParameterManifest{
|
||||
Name: "foo-object",
|
||||
Type: "object",
|
||||
Required: true,
|
||||
Description: "bar",
|
||||
AuthServices: []string{},
|
||||
Properties: map[string]*tools.ParameterManifest{"propertyName": {
|
||||
Name: "property", Type: "string",
|
||||
Required: true, Description: "property desc", AuthServices: []string{}}},
|
||||
AdditionalProperties: &tools.ParameterManifest{Name: "strProperty", Type: "string", Required: true, Description: "string property", AuthServices: []string{}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1142,12 +1323,25 @@ func TestParamManifest(t *testing.T) {
|
||||
Items: &tools.ParameterManifest{Name: "foo-string", Type: "string", Required: false, Description: "bar", AuthServices: []string{}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "object default",
|
||||
in: tools.NewObjectParameterWithDefault("object-default", map[string]any{"hello": "world"}, "bar", map[string]tools.Parameter{"k1": tools.NewFloatParameter("k1", "float property")}, nil),
|
||||
want: tools.ParameterManifest{
|
||||
Name: "object-default",
|
||||
Type: "object",
|
||||
Required: false,
|
||||
Description: "bar",
|
||||
AuthServices: []string{},
|
||||
Properties: map[string]*tools.ParameterManifest{"k1": {Name: "k1", Type: "float", Required: true, Description: "float property", AuthServices: []string{}}},
|
||||
AdditionalProperties: &tools.ParameterManifest{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := tc.in.Manifest()
|
||||
if !reflect.DeepEqual(got, tc.want) {
|
||||
t.Fatalf("unexpected manifest: got %+v, want %+v", got, tc.want)
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Fatalf("unexpected manifest (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1188,12 +1382,22 @@ func TestParamMcpManifest(t *testing.T) {
|
||||
Items: &tools.ParameterMcpManifest{Type: "string", Description: "bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "object",
|
||||
in: tools.NewObjectParameter("foo-object", "bar", map[string]tools.Parameter{"k1": tools.NewStringParameter("k1", "bar")}, tools.NewStringParameter("p1", "additional property")),
|
||||
want: tools.ParameterMcpManifest{
|
||||
Type: "object",
|
||||
Description: "bar",
|
||||
Properties: map[string]*tools.ParameterMcpManifest{"k1": {Type: "string", Description: "bar"}},
|
||||
AdditionalProperties: &tools.ParameterMcpManifest{Type: "string", Description: "additional property"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := tc.in.McpManifest()
|
||||
if !reflect.DeepEqual(got, tc.want) {
|
||||
t.Fatalf("unexpected manifest: got %+v, want %+v", got, tc.want)
|
||||
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||
t.Fatalf("unexpected manifest (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1206,7 +1410,7 @@ func TestMcpManifest(t *testing.T) {
|
||||
want tools.McpToolsSchema
|
||||
}{
|
||||
{
|
||||
name: "string",
|
||||
name: "various parameters",
|
||||
in: tools.Parameters{
|
||||
tools.NewStringParameterWithDefault("foo-string", "foo", "bar"),
|
||||
tools.NewStringParameter("foo-string2", "bar"),
|
||||
@@ -1214,38 +1418,41 @@ func TestMcpManifest(t *testing.T) {
|
||||
tools.NewStringParameterWithRequired("foo-string-not-req", "bar", false),
|
||||
tools.NewIntParameterWithDefault("foo-int", 1, "bar"),
|
||||
tools.NewIntParameter("foo-int2", "bar"),
|
||||
tools.NewArrayParameterWithDefault("foo-array", []any{"hello", "world"}, "bar", tools.NewStringParameter("foo-string", "bar")),
|
||||
tools.NewArrayParameter("foo-array2", "bar", tools.NewStringParameter("foo-string", "bar")),
|
||||
tools.NewArrayParameterWithDefault("foo-array", []any{"hello", "world"}, "bar", tools.NewStringParameter("foo-string-item", "bar")),
|
||||
tools.NewArrayParameter("foo-array2", "bar", tools.NewStringParameter("foo-string-item2", "bar")),
|
||||
tools.NewObjectParameter("foo-object", "bar", map[string]tools.Parameter{"k1": tools.NewFloatParameter("k1", "float property")}, nil),
|
||||
},
|
||||
want: tools.McpToolsSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]tools.ParameterMcpManifest{
|
||||
"foo-string": tools.ParameterMcpManifest{Type: "string", Description: "bar"},
|
||||
"foo-string2": tools.ParameterMcpManifest{Type: "string", Description: "bar"},
|
||||
"foo-string-req": tools.ParameterMcpManifest{Type: "string", Description: "bar"},
|
||||
"foo-string-not-req": tools.ParameterMcpManifest{Type: "string", Description: "bar"},
|
||||
"foo-int": tools.ParameterMcpManifest{Type: "integer", Description: "bar"},
|
||||
"foo-int2": tools.ParameterMcpManifest{Type: "integer", Description: "bar"},
|
||||
"foo-array": tools.ParameterMcpManifest{
|
||||
"foo-string": {Type: "string", Description: "bar"},
|
||||
"foo-string2": {Type: "string", Description: "bar"},
|
||||
"foo-string-req": {Type: "string", Description: "bar"},
|
||||
"foo-string-not-req": {Type: "string", Description: "bar"},
|
||||
"foo-int": {Type: "integer", Description: "bar"},
|
||||
"foo-int2": {Type: "integer", Description: "bar"},
|
||||
"foo-array": {
|
||||
Type: "array",
|
||||
Description: "bar",
|
||||
Items: &tools.ParameterMcpManifest{Type: "string", Description: "bar"},
|
||||
},
|
||||
"foo-array2": tools.ParameterMcpManifest{
|
||||
"foo-array2": {
|
||||
Type: "array",
|
||||
Description: "bar",
|
||||
Items: &tools.ParameterMcpManifest{Type: "string", Description: "bar"},
|
||||
},
|
||||
"foo-object": {Type: "object", Description: "bar", Properties: map[string]*tools.ParameterMcpManifest{"k1": {Type: "float", Description: "float property"}}, AdditionalProperties: &tools.ParameterMcpManifest{}},
|
||||
},
|
||||
Required: []string{"foo-string2", "foo-string-req", "foo-int2", "foo-array2"},
|
||||
Required: []string{"foo-array2", "foo-int2", "foo-object", "foo-string-req", "foo-string2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := tc.in.McpManifest()
|
||||
if !reflect.DeepEqual(got, tc.want) {
|
||||
t.Fatalf("unexpected manifest: got %+v, want %+v", got, tc.want)
|
||||
opts := cmpopts.SortSlices(func(a, b string) bool { return a < b })
|
||||
if diff := cmp.Diff(tc.want, got, opts); diff != "" {
|
||||
t.Fatalf("unexpected manifest (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user