mirror of
https://github.com/langgenius/dify-plugin-daemon.git
synced 2026-07-22 01:35:24 -04:00
225 lines
6.6 KiB
Go
225 lines
6.6 KiB
Go
package plugin_manager
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
lru "github.com/hashicorp/golang-lru/v2"
|
|
"github.com/langgenius/dify-cloud-kit/oss"
|
|
controlpanel "github.com/langgenius/dify-plugin-daemon/internal/core/control_panel"
|
|
"github.com/langgenius/dify-plugin-daemon/internal/core/dify_invocation"
|
|
"github.com/langgenius/dify-plugin-daemon/internal/core/dify_invocation/calldify"
|
|
"github.com/langgenius/dify-plugin-daemon/internal/core/plugin_manager/media_transport"
|
|
serverless "github.com/langgenius/dify-plugin-daemon/internal/core/serverless_connector"
|
|
"github.com/langgenius/dify-plugin-daemon/internal/service/install_service"
|
|
"github.com/langgenius/dify-plugin-daemon/internal/types/app"
|
|
"github.com/langgenius/dify-plugin-daemon/pkg/entities/plugin_entities"
|
|
"github.com/langgenius/dify-plugin-daemon/pkg/plugin_packager/decoder"
|
|
"github.com/langgenius/dify-plugin-daemon/pkg/utils/cache"
|
|
"github.com/langgenius/dify-plugin-daemon/pkg/utils/log"
|
|
)
|
|
|
|
type PluginManager struct {
|
|
// mediaBucket is used to manage media files like plugin icons, images, etc.
|
|
mediaBucket *media_transport.MediaBucket
|
|
|
|
// packageBucket can be considered as a collection of all uploaded plugin
|
|
// original packages, once a package was accepted by Dify,
|
|
// it should be stored here.
|
|
packageBucket *media_transport.PackageBucket
|
|
|
|
// installedBucket is used to manage installed plugins, all the installed plugins will be saved here
|
|
// `accepted` dose not means `installed`, a installed plugin
|
|
// will be scheduled by daemon, daemon copied and move the package
|
|
// from `packageBucket` to `installedBucket`
|
|
// the copy processing marks a plugin as `installed`
|
|
// as for `scheduling`, it's automatically done by control panel
|
|
// of course you may use `controlPanel.LaunchLocalPlugin` to start it manually
|
|
installedBucket *media_transport.InstalledBucket
|
|
|
|
// backwardsInvocation is a handle to invoke dify
|
|
backwardsInvocation dify_invocation.BackwardsInvocation
|
|
|
|
config *app.Config
|
|
|
|
// plugin lifecycle controller
|
|
//
|
|
// whatever it's local mode or serverless mode, all the signals and calls
|
|
// which related to plugin lifecycle should be handled by it.
|
|
// so that we can decouple lifetime control and thirdparty service like package management
|
|
controlPanel *controlpanel.ControlPanel
|
|
}
|
|
|
|
var (
|
|
manager *PluginManager
|
|
)
|
|
|
|
func InitGlobalManager(oss oss.OSS, config *app.Config) *PluginManager {
|
|
mediaBucket := media_transport.NewAssetsBucket(
|
|
oss,
|
|
config.PluginMediaCachePath,
|
|
config.PluginMediaCacheSize,
|
|
)
|
|
|
|
installedBucket := media_transport.NewInstalledBucket(
|
|
oss,
|
|
config.PluginInstalledPath,
|
|
)
|
|
|
|
packageBucket := media_transport.NewPackageBucket(
|
|
oss,
|
|
config.PluginPackageCachePath,
|
|
)
|
|
|
|
manager = &PluginManager{
|
|
mediaBucket: mediaBucket,
|
|
packageBucket: packageBucket,
|
|
installedBucket: installedBucket,
|
|
controlPanel: controlpanel.NewControlPanel(
|
|
config,
|
|
mediaBucket,
|
|
packageBucket,
|
|
installedBucket,
|
|
),
|
|
config: config,
|
|
}
|
|
|
|
// mount control panel notifiers
|
|
manager.controlPanel.AddNotifier(&controlpanel.StandardLogger{})
|
|
manager.controlPanel.AddNotifier(&install_service.InstallListener{})
|
|
|
|
return manager
|
|
}
|
|
|
|
func Manager() *PluginManager {
|
|
return manager
|
|
}
|
|
|
|
func (p *PluginManager) GetAsset(id string) ([]byte, error) {
|
|
return p.mediaBucket.Get(id)
|
|
}
|
|
|
|
func (p *PluginManager) Launch(configuration *app.Config) {
|
|
log.Info("start plugin manager daemon...")
|
|
|
|
// init redis client
|
|
if configuration.RedisUseSentinel {
|
|
// use Redis Sentinel
|
|
sentinels := strings.Split(configuration.RedisSentinels, ",")
|
|
if err := cache.InitRedisSentinelClient(
|
|
sentinels,
|
|
configuration.RedisSentinelServiceName,
|
|
configuration.RedisUser,
|
|
configuration.RedisPass,
|
|
configuration.RedisSentinelUsername,
|
|
configuration.RedisSentinelPassword,
|
|
configuration.RedisUseSsl,
|
|
configuration.RedisDB,
|
|
configuration.RedisSentinelSocketTimeout,
|
|
); err != nil {
|
|
log.Panic("init redis sentinel client failed: %s", err.Error())
|
|
}
|
|
} else {
|
|
if err := cache.InitRedisClient(
|
|
fmt.Sprintf("%s:%d", configuration.RedisHost, configuration.RedisPort),
|
|
configuration.RedisUser,
|
|
configuration.RedisPass,
|
|
configuration.RedisUseSsl,
|
|
configuration.RedisDB,
|
|
); err != nil {
|
|
log.Panic("init redis client failed: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
invocation, err := calldify.NewDifyInvocationDaemon(
|
|
calldify.NewDifyInvocationDaemonPayload{
|
|
BaseUrl: configuration.DifyInnerApiURL,
|
|
CallingKey: configuration.DifyInnerApiKey,
|
|
WriteTimeout: configuration.DifyInvocationWriteTimeout,
|
|
ReadTimeout: configuration.DifyInvocationReadTimeout,
|
|
},
|
|
)
|
|
if err != nil {
|
|
log.Panic("init dify invocation daemon failed: %s", err.Error())
|
|
}
|
|
p.backwardsInvocation = invocation
|
|
|
|
// start control panel
|
|
p.controlPanel.StartWatchDog()
|
|
|
|
// launch serverless connector
|
|
if configuration.Platform == app.PLATFORM_SERVERLESS {
|
|
serverless.Init(configuration)
|
|
}
|
|
}
|
|
|
|
func (p *PluginManager) BackwardsInvocation() dify_invocation.BackwardsInvocation {
|
|
return p.backwardsInvocation
|
|
}
|
|
|
|
// check if the plugin is already running on this node
|
|
func (c *PluginManager) NeedRedirecting(
|
|
identity plugin_entities.PluginUniqueIdentifier,
|
|
) (bool, error) {
|
|
// debugging runtime were stored in control panel
|
|
if identity.RemoteLike() {
|
|
_, err := c.controlPanel.GetPluginRuntime(identity)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
if c.config.Platform == app.PLATFORM_SERVERLESS {
|
|
// under serverless mode, it's no need to do redirecting
|
|
return false, nil
|
|
} else if c.config.Platform == app.PLATFORM_LOCAL {
|
|
// under local mode, check if the plugin is already running on this node
|
|
_, err := c.controlPanel.GetPluginRuntime(identity)
|
|
if err != nil {
|
|
// not found on this node, need to redirecting
|
|
return true, err
|
|
}
|
|
|
|
// found on current node
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
var (
|
|
pluginAssetCache *lru.Cache[string, []byte]
|
|
)
|
|
|
|
func pluginAssetCacheKey(
|
|
pluginUniqueIdentifier plugin_entities.PluginUniqueIdentifier,
|
|
path string,
|
|
) string {
|
|
return fmt.Sprintf("%s/%s", pluginUniqueIdentifier.String(), path)
|
|
}
|
|
func (p *PluginManager) ExtractPluginAsset(
|
|
pluginUniqueIdentifier plugin_entities.PluginUniqueIdentifier,
|
|
path string,
|
|
) ([]byte, error) {
|
|
key := pluginAssetCacheKey(pluginUniqueIdentifier, path)
|
|
cached, ok := pluginAssetCache.Get(key)
|
|
if ok {
|
|
return cached, nil
|
|
}
|
|
pkgBytes, err := manager.GetPackage(pluginUniqueIdentifier)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
zipDecoder, err := decoder.NewZipPluginDecoder(pkgBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
assets, err := zipDecoder.Assets()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pluginAssetCache.Add(key, assets[path])
|
|
return assets[path], nil
|
|
}
|