193 lines
5.5 KiB
Go
193 lines
5.5 KiB
Go
package basic
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
)
|
|
|
|
// Sentinel errors returned by the basic tools. Use errors.Is to test for them.
|
|
var (
|
|
// ErrInvalidArgument is returned when a tool is given a missing required
|
|
// argument or an argument of the wrong type.
|
|
ErrInvalidArgument = errors.New("basic: invalid argument")
|
|
// ErrUnsupported is returned when an operation is not in the tool allowlist.
|
|
ErrUnsupported = errors.New("basic: unsupported operation")
|
|
// ErrEvaluation is returned when a calculator expression cannot be parsed or
|
|
// evaluated.
|
|
ErrEvaluation = errors.New("basic: evaluation failed")
|
|
)
|
|
|
|
// asMap asserts args is a nil value or a map[string]any — the shape the
|
|
// dispatcher passes when a tool's Parameters declares an object schema.
|
|
func asMap(args any) (map[string]any, error) {
|
|
if args == nil {
|
|
return map[string]any{}, nil
|
|
}
|
|
m, ok := args.(map[string]any)
|
|
if !ok {
|
|
return nil, fmt.Errorf("basic: arguments must be an object: %w", ErrInvalidArgument)
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// hasKey reports whether m contains key.
|
|
func hasKey(m map[string]any, key string) bool {
|
|
_, ok := m[key]
|
|
return ok
|
|
}
|
|
|
|
// reqString returns the string argument under key, erroring when it is absent or
|
|
// not a string.
|
|
func reqString(m map[string]any, key string) (string, error) {
|
|
v, ok := m[key]
|
|
if !ok {
|
|
return "", fmt.Errorf("basic: missing required argument %q: %w", key, ErrInvalidArgument)
|
|
}
|
|
s, ok := v.(string)
|
|
if !ok {
|
|
return "", fmt.Errorf("basic: argument %q must be a string: %w", key, ErrInvalidArgument)
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// optString returns the string argument under key, or def when absent. A present
|
|
// non-string is an error.
|
|
func optString(m map[string]any, key, def string) (string, error) {
|
|
if !hasKey(m, key) {
|
|
return def, nil
|
|
}
|
|
return reqString(m, key)
|
|
}
|
|
|
|
// reqInt returns the integer argument under key, erroring when it is absent, not
|
|
// a number, or not integral.
|
|
func reqInt(m map[string]any, key string) (int, error) {
|
|
v, ok := m[key]
|
|
if !ok {
|
|
return 0, fmt.Errorf("basic: missing required argument %q: %w", key, ErrInvalidArgument)
|
|
}
|
|
n, err := asInt(v)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("basic: argument %q: %w", key, err)
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
// optInt returns the integer argument under key, or def when absent.
|
|
func optInt(m map[string]any, key string, def int) (int, error) {
|
|
if !hasKey(m, key) {
|
|
return def, nil
|
|
}
|
|
return reqInt(m, key)
|
|
}
|
|
|
|
// optBool returns the boolean argument under key, or def when absent.
|
|
func optBool(m map[string]any, key string, def bool) (bool, error) {
|
|
if !hasKey(m, key) {
|
|
return def, nil
|
|
}
|
|
v, ok := m[key].(bool)
|
|
if !ok {
|
|
return false, fmt.Errorf("basic: argument %q must be a boolean: %w", key, ErrInvalidArgument)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
// reqStringMap returns the object argument under key coerced to map[string]string,
|
|
// erroring when a value is not a string.
|
|
func reqStringMap(m map[string]any, key string) (map[string]string, error) {
|
|
v, ok := m[key]
|
|
if !ok {
|
|
return nil, fmt.Errorf("basic: missing required argument %q: %w", key, ErrInvalidArgument)
|
|
}
|
|
raw, ok := v.(map[string]any)
|
|
if !ok {
|
|
return nil, fmt.Errorf("basic: argument %q must be an object: %w", key, ErrInvalidArgument)
|
|
}
|
|
out := make(map[string]string, len(raw))
|
|
for k, val := range raw {
|
|
s, ok := val.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("basic: value %q must be a string: %w", k, ErrInvalidArgument)
|
|
}
|
|
out[k] = s
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// strSliceArg returns the array-of-strings argument under key.
|
|
func strSliceArg(m map[string]any, key string) ([]string, error) {
|
|
v, ok := m[key]
|
|
if !ok {
|
|
return nil, fmt.Errorf("basic: missing required argument %q: %w", key, ErrInvalidArgument)
|
|
}
|
|
raw, ok := v.([]any)
|
|
if !ok {
|
|
return nil, fmt.Errorf("basic: argument %q must be an array: %w", key, ErrInvalidArgument)
|
|
}
|
|
out := make([]string, len(raw))
|
|
for i, item := range raw {
|
|
s, ok := item.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("basic: argument %q item %d must be a string: %w", key, i, ErrInvalidArgument)
|
|
}
|
|
out[i] = s
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// strSliceSliceArg returns the array-of-arrays-of-strings argument under key.
|
|
func strSliceSliceArg(m map[string]any, key string) ([][]string, error) {
|
|
v, ok := m[key]
|
|
if !ok {
|
|
return nil, fmt.Errorf("basic: missing required argument %q: %w", key, ErrInvalidArgument)
|
|
}
|
|
raw, ok := v.([]any)
|
|
if !ok {
|
|
return nil, fmt.Errorf("basic: argument %q must be an array: %w", key, ErrInvalidArgument)
|
|
}
|
|
out := make([][]string, len(raw))
|
|
for i, item := range raw {
|
|
row, ok := item.([]any)
|
|
if !ok {
|
|
return nil, fmt.Errorf("basic: argument %q item %d must be an array: %w", key, i, ErrInvalidArgument)
|
|
}
|
|
cells := make([]string, len(row))
|
|
for j, c := range row {
|
|
s, ok := c.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("basic: argument %q item %d cell %d must be a string: %w", key, i, j, ErrInvalidArgument)
|
|
}
|
|
cells[j] = s
|
|
}
|
|
out[i] = cells
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// asInt converts a numeric-typed value (the types the dispatcher decodes JSON
|
|
// into) to an int, rejecting non-integral floats.
|
|
func asInt(v any) (int, error) {
|
|
switch x := v.(type) {
|
|
case int:
|
|
return x, nil
|
|
case int64:
|
|
return int(x), nil
|
|
case float64:
|
|
if x != math.Trunc(x) || x < math.MinInt64 || x > math.MaxInt64 {
|
|
return 0, fmt.Errorf("must be an integer: %w", ErrInvalidArgument)
|
|
}
|
|
return int(x), nil
|
|
case json.Number:
|
|
n, err := x.Int64()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("must be an integer: %w", ErrInvalidArgument)
|
|
}
|
|
return int(n), nil
|
|
default:
|
|
return 0, fmt.Errorf("must be a number: %w", ErrInvalidArgument)
|
|
}
|
|
}
|