fix: use short config keys in RegisterDef, remove generics helpers

- qq, a2a, bili, files, rss: RegisterDef keys changed from
  namespaced (e.g. "plugin.qq.listen") to short flat keys ("listen")
- a2a, bili: Get() calls updated to match short keys
- rss: replaced readCfg generics with getSetting, added SetAutoRestart(true)
- files: already uses short key Get, only RegisterDef needed fixing
This commit is contained in:
root
2026-07-21 13:20:13 +08:00
parent 52dc22f86f
commit 1a3e72e899
7 changed files with 124 additions and 156 deletions

View File

@ -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)
}

View File

@ -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)

View File

@ -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
}

View File

@ -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: "文件系统根目录",

View File

@ -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()

View File

@ -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
}

View File

@ -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
}