diff --git a/example/a2a/plugin.go b/example/a2a/plugin.go index 04352bf..87eebc2 100644 --- a/example/a2a/plugin.go +++ b/example/a2a/plugin.go @@ -28,7 +28,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { tp := p.name + "_" s.Settings().RegisterDef(sdk.ConfigDef{ - Key: "plugin." + p.name + ".listen", Default: "127.0.0.1:12000", + Key: "listen", Default: "127.0.0.1:12000", Type: "string", DisplayName: "监听地址", Description: "A2A 服务端监听地址,设为空可禁用 HTTP 服务", Category: p.name, @@ -60,7 +60,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { }, p.handleA2ADiscover) // Inbound HTTP server - if addr, _ := s.Settings().Get("plugin." + p.name + ".listen"); addr != nil { + if addr, _ := s.Settings().Get("listen"); addr != nil { if addrStr, ok := addr.(string); ok && addrStr != "" { p.startServer(addrStr) } diff --git a/example/ai_image/plugin.go b/example/ai_image/plugin.go index 88a0ab0..f28e1f7 100644 --- a/example/ai_image/plugin.go +++ b/example/ai_image/plugin.go @@ -29,50 +29,48 @@ func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { func (p *Plugin) Name() string { return p.name } -func readCfg[T string | int64 | float64](s sdk.SettingsAPI, key string, fallback T) T { +func getSetting[T string | int64 | float64](s sdk.SettingsAPI, key string, def T) T { v, err := s.Get(key) - if err == nil && v != nil { - if sv, ok := v.(string); ok && sv != "" { - switch any(fallback).(type) { - case string: - return any(sv).(T) - case int64: - if n, err := strconv.ParseInt(sv, 10, 64); err == nil { - return any(n).(T) - } - case float64: - if n, err := strconv.ParseFloat(sv, 64); err == nil { - return any(n).(T) - } + if err != nil || v == nil { + return def + } + switch any(def).(type) { + case string: + if sv, ok := v.(string); ok { + return any(sv).(T) + } + case int64: + switch n := v.(type) { + case float64: + return any(int64(n)).(T) + case int64: + return any(n).(T) + case string: + if i, err := strconv.ParseInt(n, 10, 64); err == nil { + return any(i).(T) + } + } + case float64: + switch n := v.(type) { + case float64: + return any(n).(T) + case int64: + return any(float64(n)).(T) + case string: + if f, err := strconv.ParseFloat(n, 64); err == nil { + return any(f).(T) } } } - v2, err2 := s.GetCore("plugin." + "ai_image" + "." + key) - if err2 == nil && v2 != nil { - if sv, ok := v2.(string); ok && sv != "" { - switch any(fallback).(type) { - case string: - return any(sv).(T) - case int64: - if n, err := strconv.ParseInt(sv, 10, 64); err == nil { - return any(n).(T) - } - case float64: - if n, err := strconv.ParseFloat(sv, 64); err == nil { - return any(n).(T) - } - } - } - } - return fallback + return def } -func readArg[T string | int64 | float64](args map[string]interface{}, key string, fallback T) T { +func getArg[T string | int64 | float64](args map[string]interface{}, key string, def T) T { v, ok := args[key] if !ok || v == nil { - return fallback + return def } - switch any(fallback).(type) { + switch any(def).(type) { case string: if s, ok := v.(string); ok { return any(s).(T) @@ -100,40 +98,40 @@ func readArg[T string | int64 | float64](args map[string]interface{}, key string } } } - return fallback + return def } func (p *Plugin) Start(s *sdk.PluginSDK) error { + s.SetAutoRestart(true) p.sdk = s - - p.apiKey = readCfg(s.Settings(), "api_key", "") - p.provider = readCfg(s.Settings(), "provider", "openai") - p.model = readCfg(s.Settings(), "model", "dall-e-3") - p.size = readCfg(s.Settings(), "size", "1024x1024") - p.client = &http.Client{Timeout: 120 * time.Second} s.Settings().RegisterDef(sdk.ConfigDef{ - Key: "plugin.ai_image.api_key", Default: "", Type: "string", + Key: "api_key", Default: "", Type: "string", DisplayName: "API Key", Description: "OpenAI / Stable Diffusion API Key", Category: "ai_image", Secret: true, }) s.Settings().RegisterDef(sdk.ConfigDef{ - Key: "plugin.ai_image.provider", Default: "openai", Type: "string", + Key: "provider", Default: "openai", Type: "string", DisplayName: "Provider", Description: "Image generation provider: openai / stability", Category: "ai_image", }) s.Settings().RegisterDef(sdk.ConfigDef{ - Key: "plugin.ai_image.model", Default: "dall-e-3", Type: "string", + Key: "model", Default: "dall-e-3", Type: "string", DisplayName: "Model", Description: "Model name (dall-e-3, sd-xl, etc.)", Category: "ai_image", }) s.Settings().RegisterDef(sdk.ConfigDef{ - Key: "plugin.ai_image.size", Default: "1024x1024", Type: "string", + Key: "size", Default: "1024x1024", Type: "string", DisplayName: "Size", Description: "Default image size (1024x1024, 1024x1792, 1792x1024)", Category: "ai_image", }) + p.apiKey = getSetting(s.Settings(), "api_key", "") + p.provider = getSetting(s.Settings(), "provider", "openai") + p.model = getSetting(s.Settings(), "model", "dall-e-3") + p.size = getSetting(s.Settings(), "size", "1024x1024") + tp := p.name + "_" s.RegisterTool(tp+"generate", sdk.ToolDef{ Name: tp + "generate", Description: "Generate image from text prompt using AI. Returns image URL.", @@ -179,20 +177,20 @@ type openAIResp struct { } func (p *Plugin) handleGenerate(args map[string]interface{}) (interface{}, error) { - prompt := readArg(args, "prompt", "") + prompt := getArg(args, "prompt", "") if prompt == "" { return map[string]interface{}{"isError": true, "content": "prompt is required"}, nil } - p.apiKey = readCfg(p.sdk.Settings(), "api_key", p.apiKey) - if p.apiKey == "" { + key := getSetting(p.sdk.Settings(), "api_key", p.apiKey) + if key == "" { return map[string]interface{}{"isError": true, "content": "API key not configured. Set plugin.ai_image.api_key via CLI."}, nil } - provider := readCfg(p.sdk.Settings(), "provider", p.provider) - model := readArg(args, "model", readCfg(p.sdk.Settings(), "model", p.model)) - size := readArg(args, "size", readCfg(p.sdk.Settings(), "size", p.size)) - n := readArg(args, "n", int64(1)) + provider := getSetting(p.sdk.Settings(), "provider", p.provider) + model := getArg(args, "model", getSetting(p.sdk.Settings(), "model", p.model)) + size := getArg(args, "size", getSetting(p.sdk.Settings(), "size", p.size)) + n := getArg(args, "n", int64(1)) if n < 1 { n = 1 } @@ -202,15 +200,15 @@ func (p *Plugin) handleGenerate(args map[string]interface{}) (interface{}, error switch provider { case "openai": - return p.generateOpenAI(prompt, model, size, int(n)) + return p.generateOpenAI(prompt, model, size, int(n), key) case "stability": - return p.generateStability(prompt, model, size, int(n)) + return p.generateStability(prompt, model, size, int(n), key) default: return map[string]interface{}{"isError": true, "content": "Unknown provider: " + provider + ". Supported: openai, stability"}, nil } } -func (p *Plugin) generateOpenAI(prompt, model, size string, n int) (interface{}, error) { +func (p *Plugin) generateOpenAI(prompt, model, size string, n int, apiKey string) (interface{}, error) { body := openAIReq{ Model: model, Prompt: prompt, @@ -222,7 +220,7 @@ func (p *Plugin) generateOpenAI(prompt, model, size string, n int) (interface{}, b, _ := json.Marshal(body) req, _ := http.NewRequest("POST", "https://api.openai.com/v1/images/generations", bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+p.apiKey) + req.Header.Set("Authorization", "Bearer "+apiKey) resp, err := p.client.Do(req) if err != nil { @@ -279,7 +277,7 @@ type stabilityResp struct { Message string `json:"message,omitempty"` } -func (p *Plugin) generateStability(prompt, model, size string, n int) (interface{}, error) { +func (p *Plugin) generateStability(prompt, model, size string, n int, apiKey string) (interface{}, error) { width, height := 1024, 1024 if parts := strings.Split(size, "x"); len(parts) == 2 { if w, err := strconv.Atoi(parts[0]); err == nil { @@ -302,7 +300,7 @@ func (p *Plugin) generateStability(prompt, model, size string, n int) (interface b, _ := json.Marshal(body) req, _ := http.NewRequest("POST", apiURL, bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+p.apiKey) + req.Header.Set("Authorization", "Bearer "+apiKey) req.Header.Set("Accept", "application/json") resp, err := p.client.Do(req) diff --git a/example/bili/plugin.go b/example/bili/plugin.go index cc3a4d7..3f3bfa1 100644 --- a/example/bili/plugin.go +++ b/example/bili/plugin.go @@ -25,7 +25,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { tp := p.name + "_" s.Settings().RegisterDef(sdk.ConfigDef{ - Key: "plugin." + p.name + ".output_dir", Default: "/tmp/bili_videos", + Key: "output_dir", Default: "/tmp/bili_videos", Type: "string", DisplayName: "下载目录", Description: "B站视频下载后的保存目录", Category: p.name, @@ -81,7 +81,7 @@ func (p *Plugin) handleBiliVideo(args map[string]interface{}) (interface{}, erro outputDir := "/tmp/bili_videos" if p.sdk != nil { - if v, _ := p.sdk.Settings().Get("plugin." + p.name + ".output_dir"); v != nil { + if v, _ := p.sdk.Settings().Get("output_dir"); v != nil { if s, ok := v.(string); ok && s != "" { outputDir = s } diff --git a/example/files/plugin.go b/example/files/plugin.go index a63e9ad..56bd0da 100644 --- a/example/files/plugin.go +++ b/example/files/plugin.go @@ -25,7 +25,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { s.SetAutoRestart(true) p.sdk = s s.Settings().RegisterDef(sdk.ConfigDef{ - Key: "plugin.files.dir", + Key: "dir", Default: "/", Type: "string", DisplayName: "文件系统根目录", diff --git a/example/qq/plugin.go b/example/qq/plugin.go index 1011aee..3ecfc07 100644 --- a/example/qq/plugin.go +++ b/example/qq/plugin.go @@ -123,17 +123,17 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { s.SetAutoRestart(true) p.sdk = s - s.Settings().RegisterDef(sdk.ConfigDef{Key: "plugin.qq.listen", Default: "0.0.0.0:25580", Type: "string", DisplayName: "监听地址", Description: "Webhook HTTP 监听地址", Category: "qq"}) - s.Settings().RegisterDef(sdk.ConfigDef{Key: "plugin.qq.napcat_url", Default: "http://127.0.0.1:3000", Type: "string", DisplayName: "NapCat 地址", Description: "NapCat HTTP API 基础 URL", Category: "qq"}) - s.Settings().RegisterDef(sdk.ConfigDef{Key: "plugin.qq.admin", Default: "", Type: "string", DisplayName: "管理员 QQ", Description: "管理员 QQ 号,收到其消息时标记【重要!老大消息】", Category: "qq"}) - s.Settings().RegisterDef(sdk.ConfigDef{Key: "plugin.qq.dm_policy", Default: "open", Type: "string", DisplayName: "私聊策略", Description: "open / allowlist / disabled", Category: "qq", Options: []string{"open", "allowlist", "disabled"}}) - s.Settings().RegisterDef(sdk.ConfigDef{Key: "plugin.qq.allow_from", Default: "", Type: "string", DisplayName: "私聊白名单", Description: "允许私聊机器人的 QQ 号列表,逗号分隔", Category: "qq"}) - s.Settings().RegisterDef(sdk.ConfigDef{Key: "plugin.qq.group_policy", Default: "open", Type: "string", DisplayName: "群聊策略", Description: "open / allowlist / disabled", Category: "qq", Options: []string{"open", "allowlist", "disabled"}}) - s.Settings().RegisterDef(sdk.ConfigDef{Key: "plugin.qq.group_allow_from", Default: "", Type: "string", DisplayName: "群聊白名单", Description: "允许接入的群号列表,逗号分隔", Category: "qq"}) - s.Settings().RegisterDef(sdk.ConfigDef{Key: "plugin.qq.forward_rules", Default: "[]", Type: "string", DisplayName: "转发规则", Description: "JSON 数组,每项 {group_id,host,port,password,template}。匹配的群消息通过 RCON 转发到 Minecraft。template 支持 {nickname} {message} 占位", Category: "qq"}) - s.Settings().RegisterDef(sdk.ConfigDef{Key: "plugin.qq.files_dir", Default: "/home/newqqagent/agentfs/merged/qq_files", Type: "string", DisplayName: "文件存储目录", Description: "从QQ接收的文件保存目录(CQ file/image 自动下载到此目录)", Category: "qq"}) - s.Settings().RegisterDef(sdk.ConfigDef{Key: "plugin.qq.remote_dir", Default: "/home/program/qq-workspace/remote", Type: "string", DisplayName: "NapCat容器共享目录", Description: "与NapCat容器共享的文件目录,主机路径。发文件时文件会复制到此目录,NapCat内部映射为/app/files/", Category: "qq"}) - s.Settings().RegisterDef(sdk.ConfigDef{Key: "plugin.qq.agentfs_dir", Default: "/home/newqqagent/agentfs/merged", Type: "string", DisplayName: "AgentFS目录", Description: "文件读写的工作目录,read_document/video_download 等工具的默认工作目录", Category: "qq"}) + s.Settings().RegisterDef(sdk.ConfigDef{Key: "listen", Default: "0.0.0.0:25580", Type: "string", DisplayName: "监听地址", Description: "Webhook HTTP 监听地址", Category: "qq"}) + s.Settings().RegisterDef(sdk.ConfigDef{Key: "napcat_url", Default: "http://127.0.0.1:3000", Type: "string", DisplayName: "NapCat 地址", Description: "NapCat HTTP API 基础 URL", Category: "qq"}) + s.Settings().RegisterDef(sdk.ConfigDef{Key: "admin", Default: "", Type: "string", DisplayName: "管理员 QQ", Description: "管理员 QQ 号,收到其消息时标记【重要!老大消息】", Category: "qq"}) + s.Settings().RegisterDef(sdk.ConfigDef{Key: "dm_policy", Default: "open", Type: "string", DisplayName: "私聊策略", Description: "open / allowlist / disabled", Category: "qq", Options: []string{"open", "allowlist", "disabled"}}) + s.Settings().RegisterDef(sdk.ConfigDef{Key: "allow_from", Default: "", Type: "string", DisplayName: "私聊白名单", Description: "允许私聊机器人的 QQ 号列表,逗号分隔", Category: "qq"}) + s.Settings().RegisterDef(sdk.ConfigDef{Key: "group_policy", Default: "open", Type: "string", DisplayName: "群聊策略", Description: "open / allowlist / disabled", Category: "qq", Options: []string{"open", "allowlist", "disabled"}}) + s.Settings().RegisterDef(sdk.ConfigDef{Key: "group_allow_from", Default: "", Type: "string", DisplayName: "群聊白名单", Description: "允许接入的群号列表,逗号分隔", Category: "qq"}) + s.Settings().RegisterDef(sdk.ConfigDef{Key: "forward_rules", Default: "[]", Type: "string", DisplayName: "转发规则", Description: "JSON 数组,每项 {group_id,host,port,password,template}。匹配的群消息通过 RCON 转发到 Minecraft。template 支持 {nickname} {message} 占位", Category: "qq"}) + s.Settings().RegisterDef(sdk.ConfigDef{Key: "files_dir", Default: "/home/newqqagent/agentfs/merged/qq_files", Type: "string", DisplayName: "文件存储目录", Description: "从QQ接收的文件保存目录(CQ file/image 自动下载到此目录)", Category: "qq"}) + s.Settings().RegisterDef(sdk.ConfigDef{Key: "remote_dir", Default: "/home/program/qq-workspace/remote", Type: "string", DisplayName: "NapCat容器共享目录", Description: "与NapCat容器共享的文件目录,主机路径。发文件时文件会复制到此目录,NapCat内部映射为/app/files/", Category: "qq"}) + s.Settings().RegisterDef(sdk.ConfigDef{Key: "agentfs_dir", Default: "/home/newqqagent/agentfs/merged", Type: "string", DisplayName: "AgentFS目录", Description: "文件读写的工作目录,read_document/video_download 等工具的默认工作目录", Category: "qq"}) settings := s.Settings() diff --git a/example/rss/plugin.go b/example/rss/plugin.go index edc23c7..f8715af 100644 --- a/example/rss/plugin.go +++ b/example/rss/plugin.go @@ -43,81 +43,61 @@ func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) { func (p *Plugin) Name() string { return p.name } -func readCfg[T string | int64 | float64](s sdk.SettingsAPI, key string, fallback T) T { +func getSetting[T string | int64 | float64](s sdk.SettingsAPI, key string, fallback T) T { v, err := s.Get(key) - if err == nil && v != nil { - if sv, ok := v.(string); ok && sv != "" { - switch any(fallback).(type) { - case string: - return any(sv).(T) - case int64: - if n, err := strconv.ParseInt(sv, 10, 64); err == nil { - return any(n).(T) - } - case float64: - if n, err := strconv.ParseFloat(sv, 64); err == nil { - return any(n).(T) - } + if err != nil || v == nil { + return fallback + } + switch any(fallback).(type) { + case string: + if sv, ok := v.(string); ok { + return any(sv).(T) + } + case int64: + switch val := v.(type) { + case float64: + return any(int64(val)).(T) + case string: + if n, err := strconv.ParseInt(val, 10, 64); err == nil { + return any(n).(T) } } - } - v2, err2 := s.GetCore("plugin." + "rss" + "." + key) - if err2 == nil && v2 != nil { - if sv, ok := v2.(string); ok && sv != "" { - switch any(fallback).(type) { - case string: - return any(sv).(T) - case int64: - if n, err := strconv.ParseInt(sv, 10, 64); err == nil { - return any(n).(T) - } - case float64: - if n, err := strconv.ParseFloat(sv, 64); err == nil { - return any(n).(T) - } + case float64: + switch val := v.(type) { + case float64: + return any(val).(T) + case string: + if n, err := strconv.ParseFloat(val, 64); err == nil { + return any(n).(T) } } } return fallback } -func readArg[T string | int64 | float64](args map[string]interface{}, key string, fallback T) T { - v, ok := args[key] - if !ok || v == nil { - return fallback - } - switch any(fallback).(type) { - case string: +func readArg(args map[string]interface{}, key string) string { + if v, ok := args[key]; ok && v != nil { if s, ok := v.(string); ok { - return any(s).(T) + return s } - case int64: + } + return "" +} + +func readArgInt(args map[string]interface{}, key string, fallback int) int { + if v, ok := args[key]; ok && v != nil { switch n := v.(type) { case float64: - return any(int64(n)).(T) + return int(n) case int64: - return any(n).(T) - case string: - if i, err := strconv.ParseInt(n, 10, 64); err == nil { - return any(i).(T) - } - } - case float64: - switch n := v.(type) { - case float64: - return any(n).(T) - case int64: - return any(float64(n)).(T) - case string: - if f, err := strconv.ParseFloat(n, 64); err == nil { - return any(f).(T) - } + return int(n) } } return fallback } func (p *Plugin) Start(s *sdk.PluginSDK) error { + s.SetAutoRestart(true) p.sdk = s p.client = &http.Client{Timeout: 30 * time.Second} p.fp = gofeed.NewParser() @@ -134,7 +114,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { p.loadData() s.Settings().RegisterDef(sdk.ConfigDef{ - Key: "plugin.rss.poll_interval", Default: "30", Type: "string", + Key: "poll_interval", Default: "30", Type: "string", DisplayName: "Poll Interval", Description: "Default polling interval in minutes (default: 30)", Category: "rss", }) @@ -179,7 +159,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { }, }, p.handleCheckNow) - pollMin := int(readCfg(s.Settings(), "poll_interval", int64(30))) + pollMin := int(getSetting(s.Settings(), "poll_interval", int64(30))) if pollMin < 5 { pollMin = 5 } @@ -300,7 +280,7 @@ func (p *Plugin) checkFeed(sub FeedSub) { } func (p *Plugin) handleSubscribe(args map[string]interface{}) (interface{}, error) { - url := readArg(args, "url", "") + url := readArg(args, "url") if url == "" { return map[string]interface{}{"isError": true, "content": "URL is required"}, nil } @@ -314,7 +294,7 @@ func (p *Plugin) handleSubscribe(args map[string]interface{}) (interface{}, erro } p.mu.RUnlock() - interval := int(readArg(args, "interval", int64(30))) + interval := readArgInt(args, "interval", 30) if interval < 5 { interval = 5 } @@ -360,7 +340,7 @@ func (p *Plugin) handleSubscribe(args map[string]interface{}) (interface{}, erro } func (p *Plugin) handleUnsubscribe(args map[string]interface{}) (interface{}, error) { - url := readArg(args, "url", "") + url := readArg(args, "url") if url == "" { return map[string]interface{}{"isError": true, "content": "URL is required"}, nil } diff --git a/example/weather/plugin.go b/example/weather/plugin.go index 7b46a46..05d428f 100644 --- a/example/weather/plugin.go +++ b/example/weather/plugin.go @@ -32,18 +32,15 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { p.sdk = s p.client = &http.Client{Timeout: 15 * time.Second} - loc, err := s.Settings().Get("default_location") - if err == nil && loc != nil { - if v, ok := loc.(string); ok && v != "" { - p.defaultLoc = v - } - } - if p.defaultLoc == "" { - v, err := s.Settings().GetCore("plugin.weather.default_location") - if err == nil && v != nil { - if vs, ok := v.(string); ok && vs != "" { - p.defaultLoc = vs - } + s.Settings().RegisterDef(sdk.ConfigDef{ + Key: "default_location", Default: "", Type: "string", + DisplayName: "Default Location", Description: "Default city name for weather queries, e.g. Beijing", + Category: "weather", + }) + + if v, _ := s.Settings().Get("default_location"); v != nil { + if vs, ok := v.(string); ok { + p.defaultLoc = vs } } @@ -51,14 +48,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { if dataHome == "" { dataHome = "/tmp" } - dataDir := filepath.Join(dataHome, ".homeagent", "weather") - os.MkdirAll(dataDir, 0755) - - s.Settings().RegisterDef(sdk.ConfigDef{ - Key: "plugin.weather.default_location", Default: "", Type: "string", - DisplayName: "Default Location", Description: "Default city name for weather queries, e.g. Beijing", - Category: "weather", - }) + os.MkdirAll(filepath.Join(dataHome, ".homeagent", "weather"), 0755) tp := p.name + "_" s.RegisterTool(tp+"current", sdk.ToolDef{ @@ -382,7 +372,7 @@ func (p *Plugin) handleSetLocation(args map[string]interface{}) (interface{}, er return map[string]interface{}{"isError": true, "content": "Location is required"}, nil } - p.sdk.Settings().SetCore("plugin.weather.default_location", loc) + p.sdk.Settings().Set("default_location", loc) p.defaultLoc = loc return map[string]interface{}{"content": fmt.Sprintf("Default location set to: %s", loc)}, nil }