Initial commit: Cyrene Plugins SDK + Plugin Manager
Extracted from Cyrene main repo (backend/pkg/plugins + backend/plugin-manager). Contains SDK interfaces (Plugin/Tool/HostAPI), 13 built-in plugins, ToolRegistry with call log ring buffer, and Plugin Manager REST API service. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
package calculator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene-Plugins/sdk"
|
||||
)
|
||||
|
||||
type CalculatorPlugin struct {
|
||||
sdk.BasePlugin
|
||||
}
|
||||
|
||||
func (p *CalculatorPlugin) Metadata() sdk.PluginMetadata {
|
||||
return sdk.PluginMetadata{
|
||||
Name: "calculator", DisplayName: "Calculator", Version: "1.0.0",
|
||||
Description: "Safe mathematical expression evaluation with custom parser",
|
||||
Category: "utility", Author: sdk.PluginAuthor{Name: "Cyrene Team"},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *CalculatorPlugin) Tools() []sdk.Tool {
|
||||
return []sdk.Tool{&CalculatorTool{}}
|
||||
}
|
||||
|
||||
type CalculatorTool struct {
|
||||
sdk.BaseTool
|
||||
}
|
||||
|
||||
func (t *CalculatorTool) Definition() sdk.ToolDefinition {
|
||||
return sdk.ToolDefinition{
|
||||
ID: "calculator", Name: "calculator", DisplayName: "Calculator",
|
||||
Description: "Execute mathematical calculations. Supports arithmetic, trig, logs, powers.",
|
||||
Category: "utility", Complexity: sdk.ComplexitySimple,
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object", "properties": map[string]interface{}{"expression": map[string]interface{}{"type": "string"}},
|
||||
"required": []string{"expression"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *CalculatorTool) Validate(args map[string]interface{}) error {
|
||||
if _, ok := args["expression"]; !ok {
|
||||
return fmt.Errorf("missing required parameter: expression")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *CalculatorTool) Execute(_ context.Context, args map[string]interface{}) (*sdk.ToolResult, error) {
|
||||
expr, _ := args["expression"].(string)
|
||||
result, err := evalExpression(expr)
|
||||
if err != nil {
|
||||
return &sdk.ToolResult{ToolName: "calculator", Success: false, Error: err.Error()}, nil
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "calculator", Success: true, Output: fmt.Sprintf("%v", result)}, nil
|
||||
}
|
||||
|
||||
// Expression parser supporting +, -, *, /, %, ^, functions, constants.
|
||||
type exprParser struct {
|
||||
s string
|
||||
pos int
|
||||
}
|
||||
|
||||
func evalExpression(s string) (float64, error) {
|
||||
p := &exprParser{s: strings.TrimSpace(s)}
|
||||
result, err := p.parseAddSub()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if p.pos < len(p.s) {
|
||||
return 0, fmt.Errorf("unexpected character at position %d: %c", p.pos, p.s[p.pos])
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *exprParser) peek() byte {
|
||||
if p.pos < len(p.s) {
|
||||
return p.s[p.pos]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p *exprParser) skipSpaces() {
|
||||
for p.pos < len(p.s) && p.s[p.pos] == ' ' {
|
||||
p.pos++
|
||||
}
|
||||
}
|
||||
|
||||
func (p *exprParser) parseAddSub() (float64, error) {
|
||||
left, err := p.parseMulDiv()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for {
|
||||
p.skipSpaces()
|
||||
op := p.peek()
|
||||
if op != '+' && op != '-' {
|
||||
break
|
||||
}
|
||||
p.pos++
|
||||
right, err := p.parseMulDiv()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if op == '+' {
|
||||
left += right
|
||||
} else {
|
||||
left -= right
|
||||
}
|
||||
}
|
||||
return left, nil
|
||||
}
|
||||
|
||||
func (p *exprParser) parseMulDiv() (float64, error) {
|
||||
left, err := p.parsePower()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for {
|
||||
p.skipSpaces()
|
||||
op := p.peek()
|
||||
if op != '*' && op != '/' && op != '%' {
|
||||
break
|
||||
}
|
||||
p.pos++
|
||||
right, err := p.parsePower()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch op {
|
||||
case '*':
|
||||
left *= right
|
||||
case '/':
|
||||
if right == 0 {
|
||||
return 0, fmt.Errorf("division by zero")
|
||||
}
|
||||
left /= right
|
||||
case '%':
|
||||
left = math.Mod(left, right)
|
||||
}
|
||||
}
|
||||
return left, nil
|
||||
}
|
||||
|
||||
func (p *exprParser) parsePower() (float64, error) {
|
||||
left, err := p.parseUnary()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
p.skipSpaces()
|
||||
if p.peek() == '^' {
|
||||
p.pos++
|
||||
right, err := p.parseUnary()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return math.Pow(left, right), nil
|
||||
}
|
||||
return left, nil
|
||||
}
|
||||
|
||||
func (p *exprParser) parseUnary() (float64, error) {
|
||||
p.skipSpaces()
|
||||
if p.peek() == '-' {
|
||||
p.pos++
|
||||
val, err := p.parseAtom()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return -val, nil
|
||||
}
|
||||
if p.peek() == '+' {
|
||||
p.pos++
|
||||
}
|
||||
return p.parseAtom()
|
||||
}
|
||||
|
||||
func (p *exprParser) parseAtom() (float64, error) {
|
||||
p.skipSpaces()
|
||||
if p.peek() == '(' {
|
||||
p.pos++
|
||||
result, err := p.parseAddSub()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
p.skipSpaces()
|
||||
if p.peek() != ')' {
|
||||
return 0, fmt.Errorf("missing closing parenthesis")
|
||||
}
|
||||
p.pos++
|
||||
return result, nil
|
||||
}
|
||||
if p.peek() == 0 {
|
||||
return 0, fmt.Errorf("unexpected end of expression")
|
||||
}
|
||||
if unicode.IsDigit(rune(p.peek())) || p.peek() == '.' {
|
||||
return p.parseNumber()
|
||||
}
|
||||
return p.parseFuncOrConst()
|
||||
}
|
||||
|
||||
func (p *exprParser) parseNumber() (float64, error) {
|
||||
start := p.pos
|
||||
for p.pos < len(p.s) && (unicode.IsDigit(rune(p.s[p.pos])) || p.s[p.pos] == '.') {
|
||||
p.pos++
|
||||
}
|
||||
return strconv.ParseFloat(p.s[start:p.pos], 64)
|
||||
}
|
||||
|
||||
func (p *exprParser) parseFuncOrConst() (float64, error) {
|
||||
start := p.pos
|
||||
for p.pos < len(p.s) && (unicode.IsLetter(rune(p.s[p.pos])) || p.s[p.pos] == '_') {
|
||||
p.pos++
|
||||
}
|
||||
name := p.s[start:p.pos]
|
||||
p.skipSpaces()
|
||||
|
||||
switch name {
|
||||
case "pi":
|
||||
return math.Pi, nil
|
||||
case "e":
|
||||
return math.E, nil
|
||||
case "sqrt", "sin", "cos", "tan", "abs", "floor", "ceil", "round", "log", "ln":
|
||||
if p.peek() != '(' {
|
||||
return 0, fmt.Errorf("expected '(' after function %s", name)
|
||||
}
|
||||
p.pos++
|
||||
arg, err := p.parseAddSub()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if p.peek() != ')' {
|
||||
return 0, fmt.Errorf("missing ')' after function argument")
|
||||
}
|
||||
p.pos++
|
||||
return applyFunc(name, arg)
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown function or constant: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func applyFunc(name string, x float64) (float64, error) {
|
||||
switch name {
|
||||
case "sqrt":
|
||||
if x < 0 {
|
||||
return 0, fmt.Errorf("square root of negative number")
|
||||
}
|
||||
return math.Sqrt(x), nil
|
||||
case "sin":
|
||||
return math.Sin(x), nil
|
||||
case "cos":
|
||||
return math.Cos(x), nil
|
||||
case "tan":
|
||||
return math.Tan(x), nil
|
||||
case "abs":
|
||||
return math.Abs(x), nil
|
||||
case "floor":
|
||||
return math.Floor(x), nil
|
||||
case "ceil":
|
||||
return math.Ceil(x), nil
|
||||
case "round":
|
||||
return math.Round(x), nil
|
||||
case "log":
|
||||
if x <= 0 {
|
||||
return 0, fmt.Errorf("log of non-positive number")
|
||||
}
|
||||
return math.Log10(x), nil
|
||||
case "ln":
|
||||
if x <= 0 {
|
||||
return 0, fmt.Errorf("ln of non-positive number")
|
||||
}
|
||||
return math.Log(x), nil
|
||||
}
|
||||
return 0, fmt.Errorf("unknown function: %s", name)
|
||||
}
|
||||
Reference in New Issue
Block a user