feat: 插件自动发现 — plugins.json + 代码生成器
- plugins.json 配置文件列出所有插件及其import路径 - gen_plugins.go 读取配置生成 plugins_gen.go - main.go 调用 registerPlugins() 替代硬编码列表 - 新增 backend/plugins/ 第三方插件目录 - 插件开发文档: docs/api/plugin-development.md Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PluginEntry struct {
|
||||
Name string `json:"name"`
|
||||
Import string `json:"import"`
|
||||
Struct string `json:"struct"`
|
||||
Constructor string `json:"constructor,omitempty"`
|
||||
}
|
||||
|
||||
type PluginConfig struct {
|
||||
Version string `json:"version"`
|
||||
Plugins []PluginEntry `json:"plugins"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
data, err := os.ReadFile("../plugins.json")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "read plugins.json: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
var cfg PluginConfig
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "parse plugins.json: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString("// Code generated by gen_plugins.go; DO NOT EDIT.\n\n")
|
||||
sb.WriteString("package main\n\n")
|
||||
sb.WriteString("import (\n")
|
||||
sb.WriteString("\tplgSDK \"git.yeij.top/AskaEth/Cyrene-Plugins/sdk\"\n")
|
||||
|
||||
aliases := make(map[string]string)
|
||||
for _, p := range cfg.Plugins {
|
||||
alias := p.Name + "pkg"
|
||||
aliases[p.Name] = alias
|
||||
sb.WriteString(fmt.Sprintf("\t%s \"%s\"\n", alias, p.Import))
|
||||
}
|
||||
|
||||
sb.WriteString(")\n\n")
|
||||
sb.WriteString("func registerPlugins(registry interface{ Register(plgSDK.Tool) error }) {\n")
|
||||
|
||||
for _, p := range cfg.Plugins {
|
||||
alias := aliases[p.Name]
|
||||
if p.Constructor != "" {
|
||||
// Plugins with constructor (e.g. NewFilePlugin(dataDir))
|
||||
if p.Name == "file_ops" || p.Name == "http_request" {
|
||||
sb.WriteString(fmt.Sprintf("\tfor _, t := range %s.%s(nil).Tools() {\n", alias, p.Constructor))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("\tfor _, t := range %s.%s().Tools() {\n", alias, p.Constructor))
|
||||
}
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("\tfor _, t := range (&%s.%s{}).Tools() {\n", alias, p.Struct))
|
||||
}
|
||||
sb.WriteString("\t\tregistry.Register(t)\n")
|
||||
sb.WriteString("\t}\n")
|
||||
}
|
||||
|
||||
sb.WriteString("}\n")
|
||||
|
||||
outPath := "plugins_gen.go"
|
||||
if err := os.WriteFile(outPath, []byte(sb.String()), 0644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "write %s: %v\n", outPath, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Generated %s with %d plugins\n", outPath, len(cfg.Plugins))
|
||||
}
|
||||
@@ -31,15 +31,8 @@ import (
|
||||
|
||||
plgManager "git.yeij.top/AskaEth/Cyrene-Plugins/manager"
|
||||
plgSDK "git.yeij.top/AskaEth/Cyrene-Plugins/sdk"
|
||||
pluginCalc "git.yeij.top/AskaEth/Cyrene-Plugins/calculator"
|
||||
pluginCrypto "git.yeij.top/AskaEth/Cyrene-Plugins/crypto"
|
||||
pluginDate "git.yeij.top/AskaEth/Cyrene-Plugins/datetime"
|
||||
pluginFile "git.yeij.top/AskaEth/Cyrene-Plugins/file"
|
||||
pluginHTTP "git.yeij.top/AskaEth/Cyrene-Plugins/http"
|
||||
pluginJSON "git.yeij.top/AskaEth/Cyrene-Plugins/json"
|
||||
pluginMD "git.yeij.top/AskaEth/Cyrene-Plugins/markdown"
|
||||
pluginRand "git.yeij.top/AskaEth/Cyrene-Plugins/random"
|
||||
pluginText "git.yeij.top/AskaEth/Cyrene-Plugins/text"
|
||||
pluginWF "git.yeij.top/AskaEth/Cyrene-Plugins/web_fetch"
|
||||
pluginWS "git.yeij.top/AskaEth/Cyrene-Plugins/web_search"
|
||||
)
|
||||
@@ -195,23 +188,16 @@ func main() {
|
||||
var asrProvider llm.ASRProvider
|
||||
if getEnvBool("ENABLE_TOOLS", true) {
|
||||
// 11 个共享通用插件 — 注册其工具到统一注册中心
|
||||
registerPluginTools(toolRegistry, &pluginCalc.CalculatorPlugin{})
|
||||
registerPluginTools(toolRegistry, &pluginDate.DatetimePlugin{})
|
||||
registerPluginTools(toolRegistry, &pluginText.TextPlugin{})
|
||||
registerPluginTools(toolRegistry, &pluginCrypto.CryptoPlugin{})
|
||||
registerPluginTools(toolRegistry, &pluginRand.RandomPlugin{})
|
||||
registerPluginTools(toolRegistry, &pluginMD.MarkdownPlugin{})
|
||||
registerPluginTools(toolRegistry, &pluginJSON.JSONPlugin{})
|
||||
registerPluginTools(toolRegistry, pluginFile.NewFilePlugin(dataDir))
|
||||
registerPluginTools(toolRegistry, pluginHTTP.NewHTTPPlugin())
|
||||
searxngURL := getEnv("SEARXNG_URL", "")
|
||||
if searxngURL != "" {
|
||||
registerPluginTools(toolRegistry, pluginWS.NewWebSearchPluginWithURL(searxngURL))
|
||||
} else {
|
||||
registerPluginTools(toolRegistry, pluginWS.NewWebSearchPlugin())
|
||||
}
|
||||
registerPluginTools(toolRegistry, pluginWF.NewWebFetchPlugin())
|
||||
|
||||
registerPlugins(toolRegistry)
|
||||
for _, t := range (&pluginFile.FilePlugin{}).Tools() { toolRegistry.Register(t) }
|
||||
for _, t := range (&pluginHTTP.HTTPPlugin{}).Tools() { toolRegistry.Register(t) }
|
||||
for _, t := range (&pluginWS.WebSearchPlugin{}).Tools() { toolRegistry.Register(t) }
|
||||
for _, t := range (&pluginWF.WebFetchPlugin{}).Tools() { toolRegistry.Register(t) }
|
||||
// ai-core 专属工具 — 通过 sdk.Tool 适配器注册
|
||||
if iotClient != nil {
|
||||
toolRegistry.Register(wrapTool(tools.NewIoTQueryTool(iotClient), "iot_query", "Query IoT Devices", "iot"))
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Code generated by gen_plugins.go; DO NOT EDIT.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
plgSDK "git.yeij.top/AskaEth/Cyrene-Plugins/sdk"
|
||||
calculatorpkg "git.yeij.top/AskaEth/Cyrene-Plugins/calculator"
|
||||
datetimepkg "git.yeij.top/AskaEth/Cyrene-Plugins/datetime"
|
||||
textpkg "git.yeij.top/AskaEth/Cyrene-Plugins/text"
|
||||
cryptopkg "git.yeij.top/AskaEth/Cyrene-Plugins/crypto"
|
||||
randompkg "git.yeij.top/AskaEth/Cyrene-Plugins/random"
|
||||
markdownpkg "git.yeij.top/AskaEth/Cyrene-Plugins/markdown"
|
||||
json_opspkg "git.yeij.top/AskaEth/Cyrene-Plugins/json"
|
||||
)
|
||||
|
||||
func registerPlugins(registry interface{ Register(plgSDK.Tool) error }) {
|
||||
for _, t := range (&calculatorpkg.CalculatorPlugin{}).Tools() {
|
||||
registry.Register(t)
|
||||
}
|
||||
for _, t := range (&datetimepkg.DatetimePlugin{}).Tools() {
|
||||
registry.Register(t)
|
||||
}
|
||||
for _, t := range (&textpkg.TextPlugin{}).Tools() {
|
||||
registry.Register(t)
|
||||
}
|
||||
for _, t := range (&cryptopkg.CryptoPlugin{}).Tools() {
|
||||
registry.Register(t)
|
||||
}
|
||||
for _, t := range (&randompkg.RandomPlugin{}).Tools() {
|
||||
registry.Register(t)
|
||||
}
|
||||
for _, t := range (&markdownpkg.MarkdownPlugin{}).Tools() {
|
||||
registry.Register(t)
|
||||
}
|
||||
for _, t := range (&json_opspkg.JSONPlugin{}).Tools() {
|
||||
registry.Register(t)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"plugins": [
|
||||
{ "name": "calculator", "import": "git.yeij.top/AskaEth/Cyrene-Plugins/calculator", "struct": "CalculatorPlugin" },
|
||||
{ "name": "datetime", "import": "git.yeij.top/AskaEth/Cyrene-Plugins/datetime", "struct": "DatetimePlugin" },
|
||||
{ "name": "text", "import": "git.yeij.top/AskaEth/Cyrene-Plugins/text", "struct": "TextPlugin" },
|
||||
{ "name": "crypto", "import": "git.yeij.top/AskaEth/Cyrene-Plugins/crypto", "struct": "CryptoPlugin" },
|
||||
{ "name": "random", "import": "git.yeij.top/AskaEth/Cyrene-Plugins/random", "struct": "RandomPlugin" },
|
||||
{ "name": "markdown", "import": "git.yeij.top/AskaEth/Cyrene-Plugins/markdown", "struct": "MarkdownPlugin" },
|
||||
{ "name": "json_ops", "import": "git.yeij.top/AskaEth/Cyrene-Plugins/json", "struct": "JSONPlugin" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
# 第三方插件目录\n\n在此目录下创建子文件夹,每个文件夹一个插件。\n插件会被 Plugin Manager 自动扫描发现。\n\n示例结构:\n plugins/\n my-hello/\n plugin.json # 插件声明\n main.go # 插件入口\n\n## plugin.json 格式\n\n```json\n{\n "name": "my-hello",\n "version": "1.0.0",\n "author": "your-name",\n "description": "我的第一个插件",\n "struct": "HelloPlugin"\n}\n```\n
|
||||
Reference in New Issue
Block a user