Merge pull request #38 from hobbyistlabs-coder/add-calculator-tool-4063054368676444234

feat: add calculator tool for evaluating mathematical expressions
This commit is contained in:
hobbyistlabs-coder 2026-03-16 03:59:13 -04:00 committed by GitHub
commit 7ddb17f979
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 136 additions and 0 deletions

1
go.mod
View file

@ -3,6 +3,7 @@ module jane
go 1.25.7 go 1.25.7
require ( require (
github.com/Knetic/govaluate v3.0.0+incompatible
github.com/adhocore/gronx v1.19.6 github.com/adhocore/gronx v1.19.6
github.com/alpacahq/alpaca-trade-api-go/v3 v3.9.1 github.com/alpacahq/alpaca-trade-api-go/v3 v3.9.1
github.com/anthropics/anthropic-sdk-go v1.22.1 github.com/anthropics/anthropic-sdk-go v1.22.1

View file

@ -105,6 +105,10 @@ func NewAgentInstance(
} }
} }
if cfg.Tools.IsToolEnabled("calculator") {
toolsRegistry.Register(tools.NewCalculatorTool())
}
sessionsDir := filepath.Join(workspace, "sessions") sessionsDir := filepath.Join(workspace, "sessions")
sessions := initSessionStore(sessionsDir) sessions := initSessionStore(sessionsDir)

View file

@ -134,6 +134,7 @@ type ToolsConfig struct {
WriteFile ToolConfig `json:"write_file" envPrefix:"PICOCLAW_TOOLS_WRITE_FILE_"` WriteFile ToolConfig `json:"write_file" envPrefix:"PICOCLAW_TOOLS_WRITE_FILE_"`
BrowserAction ToolConfig `json:"browser_action" envPrefix:"PICOCLAW_TOOLS_BROWSER_ACTION_"` BrowserAction ToolConfig `json:"browser_action" envPrefix:"PICOCLAW_TOOLS_BROWSER_ACTION_"`
GoEval ToolConfig `json:"go_eval" envPrefix:"PICOCLAW_TOOLS_GO_EVAL_"` GoEval ToolConfig `json:"go_eval" envPrefix:"PICOCLAW_TOOLS_GO_EVAL_"`
Calculator ToolConfig `json:"calculator" envPrefix:"PICOCLAW_TOOLS_CALCULATOR_"`
} }
type SearchCacheConfig struct { type SearchCacheConfig struct {
@ -235,6 +236,8 @@ func (t *ToolsConfig) IsToolEnabled(name string) bool {
return t.MCP.Enabled return t.MCP.Enabled
case "mcp2cli": case "mcp2cli":
return t.MCP2Cli.Enabled return t.MCP2Cli.Enabled
case "calculator":
return t.Calculator.Enabled
default: default:
return true return true
} }

58
pkg/tools/calculator.go Normal file
View file

@ -0,0 +1,58 @@
package tools
import (
"context"
"fmt"
"github.com/Knetic/govaluate"
)
// CalculatorTool evaluates mathematical expressions.
type CalculatorTool struct{}
// NewCalculatorTool creates a new CalculatorTool.
func NewCalculatorTool() *CalculatorTool {
return &CalculatorTool{}
}
func (t *CalculatorTool) Name() string {
return "calculator"
}
func (t *CalculatorTool) Description() string {
return "Evaluates mathematical expressions. Input should be a mathematical expression as a string."
}
func (t *CalculatorTool) Parameters() map[string]any {
return map[string]any{
"type": "object",
"properties": map[string]any{
"expression": map[string]any{
"type": "string",
"description": "The mathematical expression to evaluate (e.g., '2 + 2', '10 * (5 - 3)').",
},
},
"required": []string{"expression"},
}
}
func (t *CalculatorTool) Execute(ctx context.Context, args map[string]any) *ToolResult {
exprStr, ok := args["expression"].(string)
if !ok || exprStr == "" {
return ErrorResult("expression argument is required and must be a string")
}
expression, err := govaluate.NewEvaluableExpression(exprStr)
if err != nil {
return ErrorResult(fmt.Sprintf("invalid expression: %v", err))
}
result, err := expression.Evaluate(nil)
if err != nil {
return ErrorResult(fmt.Sprintf("evaluation failed: %v", err))
}
// Format result to handle floats properly if needed
resStr := fmt.Sprintf("%v", result)
return UserResult(resStr)
}

View file

@ -0,0 +1,70 @@
package tools
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCalculatorTool_Execute(t *testing.T) {
calc := NewCalculatorTool()
tests := []struct {
name string
expression string
want string
wantError bool
}{
{
name: "simple addition",
expression: "2 + 2",
want: "4",
},
{
name: "complex math",
expression: "10 * (5 - 3)",
want: "20",
},
{
name: "floating point result",
expression: "10 / 4",
want: "2.5",
},
{
name: "missing expression",
expression: "",
wantError: true,
},
{
name: "invalid expression",
expression: "2 + * 2",
wantError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
args := map[string]any{}
if tt.expression != "" {
args["expression"] = tt.expression
}
result := calc.Execute(context.Background(), args)
if tt.wantError {
assert.True(t, result.IsError)
} else {
assert.False(t, result.IsError)
assert.Equal(t, tt.want, result.ForLLM)
}
})
}
}
func TestCalculatorTool_Info(t *testing.T) {
calc := NewCalculatorTool()
assert.Equal(t, "calculator", calc.Name())
assert.NotEmpty(t, calc.Description())
assert.NotNil(t, calc.Parameters())
}