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:
1 parent
3b3e5a0697
commit
192144563b
13 files changed
+477
-41
No files matched your search
+138
-41
@@ -1,5 +1,5 @@
|
|||||||
// Cyrene v2 — LLM-powered AI companion platform.
|
// Cyrene v2 — LLM-powered AI companion platform.
|
||||||
// Single binary entry point for all services.
|
// Single binary entry point that wires all services together.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -15,103 +15,200 @@ import (
|
|||||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/db"
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/db"
|
||||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/nats"
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/nats"
|
||||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/platform"
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/platform"
|
||||||
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/plugin"
|
||||||
|
|
||||||
|
"git.yeij.top/AskaEth/Cyrene_v2/plugins/calculator"
|
||||||
|
"git.yeij.top/AskaEth/Cyrene_v2/plugins/crypto"
|
||||||
|
"git.yeij.top/AskaEth/Cyrene_v2/plugins/datetime"
|
||||||
|
"git.yeij.top/AskaEth/Cyrene_v2/plugins/file"
|
||||||
|
httpplugin "git.yeij.top/AskaEth/Cyrene_v2/plugins/http"
|
||||||
|
iotmock "git.yeij.top/AskaEth/Cyrene_v2/plugins/iot-mock"
|
||||||
|
"git.yeij.top/AskaEth/Cyrene_v2/plugins/json"
|
||||||
|
"git.yeij.top/AskaEth/Cyrene_v2/plugins/markdown"
|
||||||
|
"git.yeij.top/AskaEth/Cyrene_v2/plugins/random"
|
||||||
|
"git.yeij.top/AskaEth/Cyrene_v2/plugins/text"
|
||||||
|
webfetch "git.yeij.top/AskaEth/Cyrene_v2/plugins/web-fetch"
|
||||||
|
websearch "git.yeij.top/AskaEth/Cyrene_v2/plugins/web-search"
|
||||||
|
|
||||||
|
"git.yeij.top/AskaEth/Cyrene_v2/services/ai-core/llm"
|
||||||
|
aiCoreCtx "git.yeij.top/AskaEth/Cyrene_v2/services/ai-core/context"
|
||||||
|
"git.yeij.top/AskaEth/Cyrene_v2/services/ai-core/orchestrator"
|
||||||
|
"git.yeij.top/AskaEth/Cyrene_v2/services/ai-core/persona"
|
||||||
|
"git.yeij.top/AskaEth/Cyrene_v2/services/ai-core/subsession"
|
||||||
"git.yeij.top/AskaEth/Cyrene_v2/services/gateway/api"
|
"git.yeij.top/AskaEth/Cyrene_v2/services/gateway/api"
|
||||||
"git.yeij.top/AskaEth/Cyrene_v2/services/gateway/ws"
|
"git.yeij.top/AskaEth/Cyrene_v2/services/gateway/ws"
|
||||||
"git.yeij.top/AskaEth/Cyrene_v2/services/memory"
|
"git.yeij.top/AskaEth/Cyrene_v2/services/memory"
|
||||||
"git.yeij.top/AskaEth/Cyrene_v2/services/voice"
|
voicesvc "git.yeij.top/AskaEth/Cyrene_v2/services/voice"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
log.Println("🧠 Cyrene v2 启动中...")
|
log.Println("🧠 Cyrene v2 启动中...")
|
||||||
|
|
||||||
// 1. Platform detection
|
// === 1. Platform & Config ===
|
||||||
plat := platform.Detect()
|
plat := platform.Detect()
|
||||||
log.Printf("[platform] %s", plat.Summary())
|
log.Printf("[platform] %s", plat.Summary())
|
||||||
|
|
||||||
// 2. Load config
|
|
||||||
cfg, err := config.Load()
|
cfg, err := config.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[config] .env load failed (using defaults): %v", err)
|
log.Printf("[config] .env load failed: %v", err)
|
||||||
}
|
}
|
||||||
cfg.ValidateOrFatal()
|
cfg.ValidateOrFatal()
|
||||||
cfg.LogSanitized()
|
cfg.LogSanitized()
|
||||||
|
plat.EnsureDirs()
|
||||||
|
|
||||||
// 3. Ensure directories
|
// === 2. Database ===
|
||||||
if err := plat.EnsureDirs(); err != nil {
|
|
||||||
log.Printf("[platform] directory creation warning: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Connect database
|
|
||||||
database, err := db.Connect(db.Config{
|
database, err := db.Connect(db.Config{
|
||||||
Host: cfg.PostgresHost,
|
Host: cfg.PostgresHost, Port: cfg.PostgresPort,
|
||||||
Port: cfg.PostgresPort,
|
User: cfg.PostgresUser, Password: cfg.PostgresPassword,
|
||||||
User: cfg.PostgresUser,
|
DBName: cfg.PostgresDB, SSLMode: cfg.PostgresSSLMode,
|
||||||
Password: cfg.PostgresPassword,
|
MaxOpenConns: 25, MaxIdleConns: 5,
|
||||||
DBName: cfg.PostgresDB,
|
|
||||||
SSLMode: cfg.PostgresSSLMode,
|
|
||||||
MaxOpenConns: 25,
|
|
||||||
MaxIdleConns: 5,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[db] warning: database connection failed (running without persistence): %v", err)
|
log.Printf("[db] warning: %v (running without persistence)", err)
|
||||||
} else {
|
} else {
|
||||||
defer database.Close()
|
defer database.Close()
|
||||||
// Run migrations
|
db.RunMigration(context.Background(), database, "memories", db.SplitSQL(memory.CreateTableSQL()))
|
||||||
if err := db.RunMigration(context.Background(), database, "memories", db.SplitSQL(memory.CreateTableSQL())); err != nil {
|
|
||||||
log.Printf("[db] migration warning: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Initialize NATS (in-process for dev)
|
// === 3. NATS (in-process) ===
|
||||||
natsConn := nats.NewInProcess()
|
natsConn := nats.NewInProcess()
|
||||||
log.Printf("[nats] in-process connection ready")
|
log.Printf("[nats] ready")
|
||||||
|
_ = natsConn
|
||||||
|
|
||||||
// 6. Initialize services
|
// === 4. Plugin Registry ===
|
||||||
voiceSvc := voice.New(voice.Config{
|
reg := plugin.NewRegistry()
|
||||||
|
pluginCfg := plugin.PluginConfig{
|
||||||
|
DataDir: cfg.DataDir,
|
||||||
|
NATSURL: cfg.NATSUrl,
|
||||||
|
DatabaseURL: cfg.GetDatabaseURL(),
|
||||||
|
LogLevel: cfg.LogLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load IoT plugin
|
||||||
|
if cfg.IoTProvider == "mock" || cfg.IoTProvider == "" {
|
||||||
|
reg.Register(iotmock.New(), pluginCfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load tool plugins
|
||||||
|
reg.Register(calculator.New(), pluginCfg)
|
||||||
|
reg.Register(datetime.New(), pluginCfg)
|
||||||
|
reg.Register(crypto.New(), pluginCfg)
|
||||||
|
reg.Register(random.New(), pluginCfg)
|
||||||
|
reg.Register(text.New(), pluginCfg)
|
||||||
|
reg.Register(json.New(), pluginCfg)
|
||||||
|
reg.Register(markdown.New(), pluginCfg)
|
||||||
|
reg.Register(file.New(), pluginCfg)
|
||||||
|
reg.Register(httpplugin.New(), pluginCfg)
|
||||||
|
reg.Register(webfetch.New(), pluginCfg)
|
||||||
|
if cfg.SearXNGURL != "" {
|
||||||
|
reg.Register(websearch.New(cfg.SearXNGURL), pluginCfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[plugin] loaded %d plugins: %v", reg.Count(), reg.ListNames())
|
||||||
|
|
||||||
|
// === 5. Voice Service ===
|
||||||
|
voiceSvc := voicesvc.New(voicesvc.Config{
|
||||||
STTProvider: cfg.ASRProvider,
|
STTProvider: cfg.ASRProvider,
|
||||||
TTSProviders: []string{cfg.TTSProvider},
|
TTSProviders: []string{cfg.TTSProvider},
|
||||||
DashScopeKey: cfg.DashScopeAPIKey,
|
DashScopeKey: cfg.DashScopeAPIKey,
|
||||||
})
|
})
|
||||||
log.Printf("[voice] service ready")
|
log.Printf("[voice] ready")
|
||||||
|
|
||||||
// 7. Set up Gateway
|
// === 6. Memory Service ===
|
||||||
|
var memSvc *memory.Service
|
||||||
|
if database != nil {
|
||||||
|
memRepo := memory.NewRepository(database)
|
||||||
|
memSvc = memory.NewService(memRepo)
|
||||||
|
log.Printf("[memory] connected")
|
||||||
|
} else {
|
||||||
|
log.Printf("[memory] disabled (no database)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 7. AI-Core: Persona ===
|
||||||
|
personaLoader, err := persona.NewLoader(cfg.PersonaDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[persona] load error: %v (using builtin)", err)
|
||||||
|
}
|
||||||
|
log.Printf("[persona] loaded %d persona(s): %v", personaLoader.Count(), personaLoader.List())
|
||||||
|
personaInjector := persona.NewInjector(personaLoader.Default())
|
||||||
|
|
||||||
|
// === 8. AI-Core: LLM Adapter ===
|
||||||
|
llmCfg := llm.DefaultConfig()
|
||||||
|
llmCfg.BaseURL = cfg.LLMBaseURL
|
||||||
|
llmCfg.APIKey = cfg.LLMAPIKey
|
||||||
|
llmCfg.Model = cfg.LLMModel
|
||||||
|
llmCfg.FallbackModel = cfg.LLMFallbackModel
|
||||||
|
llmSelector := llm.NewModelSelector(llmCfg)
|
||||||
|
log.Printf("[llm] adapter ready (model=%s)", cfg.LLMModel)
|
||||||
|
|
||||||
|
// === 9. AI-Core: Context Store ===
|
||||||
|
ctxStore := aiCoreCtx.NewStore(4000, 50)
|
||||||
|
log.Printf("[context] sliding window ready (4000 tokens, 50 turns)")
|
||||||
|
|
||||||
|
// === 10. AI-Core: Sub-session Manager ===
|
||||||
|
subMgr := subsession.NewManager(
|
||||||
|
&subsession.GeneralProvider{},
|
||||||
|
&subsession.MemoryProvider{},
|
||||||
|
&subsession.IoTProvider{
|
||||||
|
GetDeviceState: func(ctx context.Context) (string, error) {
|
||||||
|
iot := reg.GetIoTProvider()
|
||||||
|
if iot == nil {
|
||||||
|
return "", fmt.Errorf("no iot provider")
|
||||||
|
}
|
||||||
|
devices, err := iot.ListDevices(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
states := make([]orchestrator.DeviceState, len(devices))
|
||||||
|
for i, d := range devices {
|
||||||
|
states[i] = orchestrator.DeviceState{ID: d.ID, Name: d.Name, State: d.State}
|
||||||
|
}
|
||||||
|
return orchestrator.BuildIoTContext(states), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
log.Printf("[subsession] 4 providers ready")
|
||||||
|
|
||||||
|
// === 11. AI-Core: Orchestrator ===
|
||||||
|
semanticCache := llm.NewSemanticCache()
|
||||||
|
orch := orchestrator.NewPipeline(llmSelector, semanticCache, ctxStore, subMgr, personaInjector)
|
||||||
|
log.Printf("[orchestrator] pipeline ready")
|
||||||
|
|
||||||
|
// === 12. Gateway ===
|
||||||
authMgr := api.NewStubAuthManager()
|
authMgr := api.NewStubAuthManager()
|
||||||
apiRouter := api.New(authMgr)
|
apiRouter := api.New(authMgr)
|
||||||
|
|
||||||
// 8. WebSocket Hub
|
|
||||||
wsHub := ws.NewHub()
|
wsHub := ws.NewHub()
|
||||||
wsMux := http.NewServeMux()
|
wsMux := http.NewServeMux()
|
||||||
wsMux.HandleFunc("/ws/chat", wsHub.UpgradeHandler())
|
wsMux.HandleFunc("/ws/chat", wsHub.UpgradeHandler())
|
||||||
|
|
||||||
// 9. Combined HTTP server (API + WS on same port)
|
|
||||||
mainMux := http.NewServeMux()
|
mainMux := http.NewServeMux()
|
||||||
mainMux.Handle("/api/", apiRouter)
|
mainMux.Handle("/api/", apiRouter)
|
||||||
mainMux.Handle("/ws/", wsMux)
|
mainMux.Handle("/ws/", wsMux)
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
|
addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
|
||||||
srv := &http.Server{
|
srv := &http.Server{Addr: addr, Handler: mainMux}
|
||||||
Addr: addr,
|
|
||||||
Handler: mainMux,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 10. Start server
|
// === 13. Start ===
|
||||||
go func() {
|
go func() {
|
||||||
log.Printf("[gateway] listening on %s", addr)
|
log.Printf("[gateway] listening on %s", addr)
|
||||||
|
log.Printf("[gateway] plugins=%d tools=%d", reg.Count(), len(reg.GetAllToolDefinitions()))
|
||||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
log.Fatalf("[gateway] failed: %v", err)
|
log.Fatalf("[gateway] %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 11. Wait for shutdown signal
|
|
||||||
quit := make(chan os.Signal, 1)
|
quit := make(chan os.Signal, 1)
|
||||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
<-quit
|
<-quit
|
||||||
|
|
||||||
log.Println("[gateway] shutting down...")
|
log.Println("[gateway] shutting down...")
|
||||||
|
reg.Shutdown()
|
||||||
srv.Close()
|
srv.Close()
|
||||||
log.Println("Cyrene v2 已安全退出")
|
log.Println("Cyrene v2 已安全退出")
|
||||||
|
|
||||||
_ = natsConn
|
|
||||||
_ = voiceSvc
|
_ = voiceSvc
|
||||||
}
|
_ = memSvc
|
||||||
|
_ = orch
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in new issue
Block a user