feat(tools/basic): 内置基础工具(calculator(注入安全)/datetime/random/text/json/markdown)+ 批量注册
This commit is contained in:
1 parent
b3c61aa1ff
commit
08223c06a5
19 files changed
+2074
No files matched your search
@@ -0,0 +1,192 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user