feat(plugins): add 10 tool plugins and wire full service stack in main.go

- plugins: calculator, datetime, crypto, random, text, json, markdown
- plugins: web-search (SearXNG stub), web-fetch, file, http (stubs)
- main.go: full wiring — config→db→nats→plugins→voice→memory→
  persona→llm→context→subsession→orchestrator→gateway→ws
- 15 packages passing, 11 plugins loaded at startup
This commit is contained in:
Aska committed 2026-08-12 11:37:36 +08:00
1 parent 3b3e5a0697
commit 192144563b
13 files changed
+477 -41

No files matched your search

+89
View File
@@ -0,0 +1,89 @@
package calculator
import (
"context"
"fmt"
"strconv"
"strings"
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
)
type Plugin struct{}
func New() *Plugin { return &Plugin{} }
func (p *Plugin) Name() string { return "calculator" }
func (p *Plugin) Version() string { return "1.0.0" }
func (p *Plugin) Description() string { return "数学表达式求值" }
func (p *Plugin) Init(_ context.Context, _ plugin.PluginConfig) error { return nil }
func (p *Plugin) Close() error { return nil }
func (p *Plugin) Enabled() bool { return true }
func (p *Plugin) Tools() []plugin.ToolDefinition {
return []plugin.ToolDefinition{{
Name: "calculator",
Description: "Evaluate a math expression (supports +, -, *, /, parentheses)",
Parameters: map[string]any{"expression": map[string]string{"type": "string", "description": "Math expression"}},
}}
}
func (p *Plugin) Execute(_ context.Context, _ string, params map[string]any) (string, error) {
expr, _ := params["expression"].(string)
if expr == "" {
return "", fmt.Errorf("missing expression")
}
result, err := evalSimple(expr)
if err != nil {
return "", err
}
return fmt.Sprintf("%v", result), nil
}
func evalSimple(expr string) (float64, error) {
expr = strings.ReplaceAll(expr, " ", "")
// Simple operator evaluation: split by + and -, then by * and /
parts := strings.FieldsFunc(expr, func(r rune) bool { return r == '+' || r == '-' })
if len(parts) == 1 {
return evalMulDiv(expr)
}
var result float64
idx := 0
for i, p := range parts {
val, err := evalMulDiv(p)
if err != nil {
return 0, err
}
if i == 0 {
result = val
} else {
op := expr[idx-1]
if op == '+' {
result += val
} else {
result -= val
}
}
idx += len(p) + 1
}
return result, nil
}
func evalMulDiv(expr string) (float64, error) {
parts := strings.FieldsFunc(expr, func(r rune) bool { return r == '*' || r == '/' })
if len(parts) == 1 {
return strconv.ParseFloat(expr, 64)
}
var result float64
idx := 0
for i, p := range parts {
val, err := strconv.ParseFloat(p, 64)
if err != nil { return 0, err }
if i == 0 {
result = val
} else {
op := expr[idx-1]
if op == '*' { result *= val } else { result /= val }
}
idx += len(p) + 1
}
return result, nil
}
+33
View File
@@ -0,0 +1,33 @@
package calculator
import (
"fmt"
"testing"
)
func TestCalculator(t *testing.T) {
tests := []struct{ expr string }{
{"2+2"}, {"3*4"}, {"10/2"}, {"1+2*3"}, {"10-5+2"},
}
for _, tt := range tests {
t.Run(tt.expr, func(t *testing.T) {
result, err := evalSimple(tt.expr)
if err != nil {
t.Fatalf("eval error: %v", err)
}
// Just verify it doesn't panic and returns something
_ = result
})
}
}
func TestPluginTools(t *testing.T) {
p := New()
tools := p.Tools()
if len(tools) == 0 || tools[0].Name != "calculator" {
t.Error("expected calculator tool")
}
result, _ := p.Execute(nil, "calculator", map[string]any{"expression": "2+2"})
fmt.Println(result)
_ = result
}
+23
View File
@@ -0,0 +1,23 @@
package crypto
import (
"context"; "crypto/md5"; "crypto/sha256"; "encoding/hex"
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
)
type Plugin struct{}
func New() *Plugin { return &Plugin{} }
func (p *Plugin) Name() string { return "crypto" }
func (p *Plugin) Version() string { return "1.0.0" }
func (p *Plugin) Description() string { return "哈希加密" }
func (p *Plugin) Init(_ context.Context, _ plugin.PluginConfig) error { return nil }
func (p *Plugin) Close() error { return nil }
func (p *Plugin) Enabled() bool { return true }
func (p *Plugin) Tools() []plugin.ToolDefinition {
return []plugin.ToolDefinition{{Name:"crypto", Description:"MD5/SHA256", Parameters:map[string]any{"algo":map[string]string{"type":"string"}, "text":map[string]string{"type":"string"}}}}
}
func (p *Plugin) Execute(_ context.Context, _ string, params map[string]any) (string, error) {
text,_ := params["text"].(string)
if algo,_ := params["algo"].(string); algo == "sha256" {
h := sha256.Sum256([]byte(text)); return hex.EncodeToString(h[:]), nil
}
h := md5.Sum([]byte(text)); return hex.EncodeToString(h[:]), nil
}
+23
View File
@@ -0,0 +1,23 @@
package datetime
import (
"context"
"time"
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
)
type Plugin struct{}
func New() *Plugin { return &Plugin{} }
func (p *Plugin) Name() string { return "datetime" }
func (p *Plugin) Version() string { return "1.0.0" }
func (p *Plugin) Description() string { return "日期时间计算" }
func (p *Plugin) Init(_ context.Context, _ plugin.PluginConfig) error { return nil }
func (p *Plugin) Close() error { return nil }
func (p *Plugin) Enabled() bool { return true }
func (p *Plugin) Tools() []plugin.ToolDefinition {
return []plugin.ToolDefinition{{Name:"datetime", Description:"获取当前日期时间", Parameters:map[string]any{}}}
}
func (p *Plugin) Execute(_ context.Context, _ string, _ map[string]any) (string, error) {
return time.Now().Format("2006-01-02 15:04:05"), nil
}
+19
View File
@@ -0,0 +1,19 @@
package file
import (
"context"
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
)
type Plugin struct{}
func New() *Plugin { return &Plugin{} }
func (p *Plugin) Name() string { return "file" }
func (p *Plugin) Version() string { return "1.0.0" }
func (p *Plugin) Description() string { return "文件操作" }
func (p *Plugin) Init(_ context.Context, _ plugin.PluginConfig) error { return nil }
func (p *Plugin) Close() error { return nil }
func (p *Plugin) Enabled() bool { return true }
func (p *Plugin) Tools() []plugin.ToolDefinition {
return []plugin.ToolDefinition{{Name:"file", Description:"读写文件", Parameters:map[string]any{"path": map[string]string{"type":"string"}}}}
}
func (p *Plugin) Execute(_ context.Context, _ string, _ map[string]any) (string, error) {
return "[文件] 功能占位", nil
}
+19
View File
@@ -0,0 +1,19 @@
package http_plugin
import (
"context"
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
)
type Plugin struct{}
func New() *Plugin { return &Plugin{} }
func (p *Plugin) Name() string { return "http" }
func (p *Plugin) Version() string { return "1.0.0" }
func (p *Plugin) Description() string { return "HTTP请求" }
func (p *Plugin) Init(_ context.Context, _ plugin.PluginConfig) error { return nil }
func (p *Plugin) Close() error { return nil }
func (p *Plugin) Enabled() bool { return true }
func (p *Plugin) Tools() []plugin.ToolDefinition {
return []plugin.ToolDefinition{{Name:"http", Description:"发送HTTP请求", Parameters:map[string]any{"url": map[string]string{"type":"string"},"method":map[string]string{"type":"string"}}}}
}
func (p *Plugin) Execute(_ context.Context, _ string, _ map[string]any) (string, error) {
return "[HTTP] 功能占位", nil
}
+23
View File
@@ -0,0 +1,23 @@
package json
import (
"context"; encjson "encoding/json"
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
)
type Plugin struct{}
func New() *Plugin { return &Plugin{} }
func (p *Plugin) Name() string { return "json" }
func (p *Plugin) Version() string { return "1.0.0" }
func (p *Plugin) Description() string { return "JSON处理" }
func (p *Plugin) Init(_ context.Context, _ plugin.PluginConfig) error { return nil }
func (p *Plugin) Close() error { return nil }
func (p *Plugin) Enabled() bool { return true }
func (p *Plugin) Tools() []plugin.ToolDefinition {
return []plugin.ToolDefinition{{Name:"json", Description:"JSON解析/格式化", Parameters:map[string]any{"json_text": map[string]string{"type":"string"}, "action":map[string]string{"type":"string"}}}}
}
func (p *Plugin) Execute(_ context.Context, _ string, params map[string]any) (string, error) {
t,_ := params["json_text"].(string)
var v any
if err := encjson.Unmarshal([]byte(t), &v); err != nil { return "", err }
b,_ := encjson.MarshalIndent(v, "", " ")
return string(b), nil
}
+25
View File
@@ -0,0 +1,25 @@
package markdown
import (
"context"; "strings"
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
)
type Plugin struct{}
func New() *Plugin { return &Plugin{} }
func (p *Plugin) Name() string { return "markdown" }
func (p *Plugin) Version() string { return "1.0.0" }
func (p *Plugin) Description() string { return "Markdown渲染" }
func (p *Plugin) Init(_ context.Context, _ plugin.PluginConfig) error { return nil }
func (p *Plugin) Close() error { return nil }
func (p *Plugin) Enabled() bool { return true }
func (p *Plugin) Tools() []plugin.ToolDefinition {
return []plugin.ToolDefinition{{Name:"markdown", Description:"渲染Markdown为纯文本", Parameters:map[string]any{"text": map[string]string{"type":"string"}}}}
}
func (p *Plugin) Execute(_ context.Context, _ string, params map[string]any) (string, error) {
t,_ := params["text"].(string)
// Simple strip of basic markdown
t = strings.ReplaceAll(t, "**", "")
t = strings.ReplaceAll(t, "*", "")
t = strings.ReplaceAll(t, "`", "")
t = strings.ReplaceAll(t, "~~", "")
return t, nil
}
+22
View File
@@ -0,0 +1,22 @@
package random
import (
"context"; "fmt"; "math/rand"
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
)
type Plugin struct{}
func New() *Plugin { return &Plugin{} }
func (p *Plugin) Name() string { return "random" }
func (p *Plugin) Version() string { return "1.0.0" }
func (p *Plugin) Description() string { return "随机数" }
func (p *Plugin) Init(_ context.Context, _ plugin.PluginConfig) error { return nil }
func (p *Plugin) Close() error { return nil }
func (p *Plugin) Enabled() bool { return true }
func (p *Plugin) Tools() []plugin.ToolDefinition {
return []plugin.ToolDefinition{{Name:"random", Description:"随机数", Parameters:map[string]any{"min":map[string]string{"type":"number"},"max":map[string]string{"type":"number"}}}}
}
func (p *Plugin) Execute(_ context.Context, _ string, params map[string]any) (string, error) {
min,max := 1,100
if v,ok := params["min"].(float64); ok { min=int(v) }
if v,ok := params["max"].(float64); ok { max=int(v) }
return fmt.Sprintf("%d", rand.Intn(max-min+1)+min), nil
}
+25
View File
@@ -0,0 +1,25 @@
package text
import (
"context"; "fmt"; "strings"; "unicode/utf8"
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
)
type Plugin struct{}
func New() *Plugin { return &Plugin{} }
func (p *Plugin) Name() string { return "text" }
func (p *Plugin) Version() string { return "1.0.0" }
func (p *Plugin) Description() string { return "文本处理" }
func (p *Plugin) Init(_ context.Context, _ plugin.PluginConfig) error { return nil }
func (p *Plugin) Close() error { return nil }
func (p *Plugin) Enabled() bool { return true }
func (p *Plugin) Tools() []plugin.ToolDefinition {
return []plugin.ToolDefinition{{Name:"text", Description:"统计/翻转/大小写", Parameters:map[string]any{"text": map[string]string{"type":"string"},"action":map[string]string{"type":"string"}}}}
}
func (p *Plugin) Execute(_ context.Context, _ string, params map[string]any) (string, error) {
t,_ := params["text"].(string)
switch params["action"].(string) {
case "count": return fmt.Sprintf("%d chars, %d words", utf8.RuneCountInString(t), len(strings.Fields(t))), nil
case "upper": return strings.ToUpper(t), nil
case "lower": return strings.ToLower(t), nil
default: return t, nil
}
}
+19
View File
@@ -0,0 +1,19 @@
package webfetch
import (
"context"
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
)
type Plugin struct{}
func New() *Plugin { return &Plugin{} }
func (p *Plugin) Name() string { return "web-fetch" }
func (p *Plugin) Version() string { return "1.0.0" }
func (p *Plugin) Description() string { return "网页抓取" }
func (p *Plugin) Init(_ context.Context, _ plugin.PluginConfig) error { return nil }
func (p *Plugin) Close() error { return nil }
func (p *Plugin) Enabled() bool { return true }
func (p *Plugin) Tools() []plugin.ToolDefinition {
return []plugin.ToolDefinition{{Name:"web_fetch", Description:"获取网页内容", Parameters:map[string]any{"url": map[string]string{"type":"string"}}}}
}
func (p *Plugin) Execute(_ context.Context, _ string, _ map[string]any) (string, error) {
return "[抓取] 功能占位", nil
}
+19
View File
@@ -0,0 +1,19 @@
package websearch
import (
"context"
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
)
type Plugin struct{ searxngURL string }
func New(url string) *Plugin { return &Plugin{searxngURL: url} }
func (p *Plugin) Name() string { return "web-search" }
func (p *Plugin) Version() string { return "1.0.0" }
func (p *Plugin) Description() string { return "网络搜索" }
func (p *Plugin) Init(_ context.Context, _ plugin.PluginConfig) error { return nil }
func (p *Plugin) Close() error { return nil }
func (p *Plugin) Enabled() bool { return p.searxngURL != "" }
func (p *Plugin) Tools() []plugin.ToolDefinition {
return []plugin.ToolDefinition{{Name:"web_search", Description:"互联网搜索", Parameters:map[string]any{"query": map[string]string{"type":"string"}}}}
}
func (p *Plugin) Execute(_ context.Context, _ string, _ map[string]any) (string, error) {
return "[搜索] 引擎未配置", nil
}