feat: Phase 3 插件与工具系统 — Plugin SDK + Plugin Manager + 13内置插件 (40文件, 3293行)
- Plugin SDK: Plugin/Tool/ComplexTool/HostAPI 标准化接口 - Plugin Manager: 插件生命周期管理 (Install/Enable/Disable/Uninstall/Reload) - Tool Registry: 聚合工具注册表 (Register/Execute/Dispatch) - 13 个内置插件: 将原有硬编码工具迁移为标准插件格式 - REST API: 11 个端点 (net/http, 零外部依赖) - ai-core 集成: PluginManagerClient 替代本地工具调用 - plugin.json 元数据: 每个插件含完整 author/version/category/permissions Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
package random
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
mathrand "math/rand"
|
||||
"strings"
|
||||
|
||||
"github.com/yourname/cyrene-ai/plugin-manager/internal/sdk"
|
||||
)
|
||||
|
||||
type RandomPlugin struct{ sdk.BasePlugin }
|
||||
|
||||
func (p *RandomPlugin) Metadata() sdk.PluginMetadata {
|
||||
return sdk.PluginMetadata{
|
||||
Name: "random", DisplayName: "Random Generator", Version: "1.0.0",
|
||||
Description: "Random generation: numbers, UUIDs, secure passwords, pick/shuffle",
|
||||
Category: "utility", Author: sdk.PluginAuthor{Name: "Cyrene Team"},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *RandomPlugin) Tools() []sdk.Tool { return []sdk.Tool{&RandomTool{}} }
|
||||
|
||||
type RandomTool struct{ sdk.BaseTool }
|
||||
|
||||
func (t *RandomTool) Definition() sdk.ToolDefinition {
|
||||
return sdk.ToolDefinition{
|
||||
ID: "random", Name: "random", DisplayName: "Random Generator",
|
||||
Description: "Random generation. Random numbers, UUID v4, secure passwords, pick from list, shuffle list.",
|
||||
Category: "utility", Complexity: sdk.ComplexitySimple,
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"action": map[string]interface{}{"type": "string", "enum": []string{"number", "uuid", "password", "pick", "shuffle"}},
|
||||
"min": map[string]interface{}{"type": "number"},
|
||||
"max": map[string]interface{}{"type": "number"},
|
||||
"length": map[string]interface{}{"type": "number"},
|
||||
"items": map[string]interface{}{"type": "array", "items": map[string]interface{}{"type": "string"}},
|
||||
"count": map[string]interface{}{"type": "number"},
|
||||
},
|
||||
"required": []string{"action"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *RandomTool) Validate(args map[string]interface{}) error {
|
||||
if _, ok := args["action"]; !ok {
|
||||
return fmt.Errorf("missing required parameter: action")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *RandomTool) Execute(_ context.Context, args map[string]interface{}) (*sdk.ToolResult, error) {
|
||||
action, _ := args["action"].(string)
|
||||
|
||||
switch action {
|
||||
case "number":
|
||||
min := getIntArg(args, "min", 0)
|
||||
max := getIntArg(args, "max", 100)
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(int64(max-min+1)))
|
||||
if err != nil {
|
||||
return &sdk.ToolResult{ToolName: "random", Success: false, Error: err.Error()}, nil
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "random", Success: true,
|
||||
Output: fmt.Sprintf("%d", int(n.Int64())+min)}, nil
|
||||
|
||||
case "uuid":
|
||||
uuid := make([]byte, 16)
|
||||
rand.Read(uuid)
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80
|
||||
return &sdk.ToolResult{ToolName: "random", Success: true,
|
||||
Output: fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:])}, nil
|
||||
|
||||
case "password":
|
||||
length := getIntArg(args, "length", 16)
|
||||
if length < 4 {
|
||||
length = 4
|
||||
}
|
||||
if length > 128 {
|
||||
length = 128
|
||||
}
|
||||
upper := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
lower := "abcdefghijklmnopqrstuvwxyz"
|
||||
digits := "0123456789"
|
||||
symbols := "!@#$%^&*()_+-=[]{}|;:,.<>?"
|
||||
all := upper + lower + digits + symbols
|
||||
bytes := make([]byte, length)
|
||||
for i := range bytes {
|
||||
idx, _ := rand.Int(rand.Reader, big.NewInt(int64(len(all))))
|
||||
bytes[i] = all[idx.Int64()]
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "random", Success: true, Output: string(bytes)}, nil
|
||||
|
||||
case "pick":
|
||||
items := getStringSliceArg(args, "items")
|
||||
if len(items) == 0 {
|
||||
return &sdk.ToolResult{ToolName: "random", Success: false, Error: "items list is empty"}, nil
|
||||
}
|
||||
count := getIntArg(args, "count", 1)
|
||||
if count > len(items) {
|
||||
count = len(items)
|
||||
}
|
||||
indices := shuffledIndices(len(items))
|
||||
picked := make([]string, count)
|
||||
for i := 0; i < count; i++ {
|
||||
picked[i] = items[indices[i]]
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "random", Success: true, Output: strings.Join(picked, ", ")}, nil
|
||||
|
||||
case "shuffle":
|
||||
items := getStringSliceArg(args, "items")
|
||||
indices := shuffledIndices(len(items))
|
||||
shuffled := make([]string, len(items))
|
||||
for i, idx := range indices {
|
||||
shuffled[i] = items[idx]
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "random", Success: true, Output: strings.Join(shuffled, ", ")}, nil
|
||||
}
|
||||
return &sdk.ToolResult{ToolName: "random", Success: false, Error: "unknown action: " + action}, nil
|
||||
}
|
||||
|
||||
func getIntArg(args map[string]interface{}, key string, defaultVal int) int {
|
||||
v, ok := args[key]
|
||||
if !ok {
|
||||
return defaultVal
|
||||
}
|
||||
switch n := v.(type) {
|
||||
case float64:
|
||||
return int(n)
|
||||
case int:
|
||||
return n
|
||||
case int64:
|
||||
return int(n)
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
func getStringSliceArg(args map[string]interface{}, key string) []string {
|
||||
v, ok := args[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
switch s := v.(type) {
|
||||
case []string:
|
||||
return s
|
||||
case []interface{}:
|
||||
result := make([]string, len(s))
|
||||
for i, item := range s {
|
||||
result[i] = fmt.Sprint(item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func shuffledIndices(n int) []int {
|
||||
indices := make([]int, n)
|
||||
for i := range indices {
|
||||
indices[i] = i
|
||||
}
|
||||
for i := n - 1; i > 0; i-- {
|
||||
jBig, err := rand.Int(rand.Reader, big.NewInt(int64(i+1)))
|
||||
if err != nil {
|
||||
j := mathrand.Intn(i + 1)
|
||||
indices[i], indices[j] = indices[j], indices[i]
|
||||
continue
|
||||
}
|
||||
j := int(jBig.Int64())
|
||||
indices[i], indices[j] = indices[j], indices[i]
|
||||
}
|
||||
return indices
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "random",
|
||||
"displayName": "Random Generator",
|
||||
"version": "1.0.0",
|
||||
"minCyreneVersion": "1.0.0",
|
||||
"author": { "name": "Cyrene Team" },
|
||||
"description": "Random generation: numbers, UUIDs, secure passwords, pick/shuffle",
|
||||
"license": "MIT",
|
||||
"keywords": ["random", "uuid", "password", "shuffle"],
|
||||
"category": "utility"
|
||||
}
|
||||
Reference in New Issue
Block a user