Skip to content

Commit 4589841

Browse files
MairuisStream29Yeuolygemini-code-assist[bot]
authored
feat: introduce trigger (#482)
* feat(plugins): add FetchPluginReadme endpoint and update launch configurations * feat: add PluginReadme database model * feat: implement readme extracting and storage * feat: implement readme endpoint * feat: add plugin asset extraction endpoint with caching support * Implement trigger functionality and clean up dynamic select code - Added new trigger-related access types and actions in access.go. - Introduced new HTTP routes for trigger operations in http_server.gen.go. - Updated plugin declaration to include triggers in plugin_entities. - Removed unused dynamic select service and controller files. - Enhanced message handling in debugging_runtime to support trigger registration. This update enhances the plugin system by integrating trigger capabilities while cleaning up legacy code. * Refactor trigger-related types and enhance dynamic select functionality - Updated TriggerProviderIdentity and TriggerProviderConfiguration to improve structure and validation. - Renamed TriggerConfiguration to TriggerDeclaration for clarity. - Added CredentialType to RequestDynamicParameterSelect for better request handling. - Enhanced PluginDecoderHelper to read and unmarshal trigger files correctly. These changes streamline the trigger system and improve the overall request handling in the plugin architecture. * Add trigger functionality and enhance database integration - Introduced TriggerInstallation model for managing trigger installations in the database. - Updated autoMigrate function to include trigger installations in the migration process. - Added new HTTP routes for listing and retrieving triggers in the HTTP server. - Implemented ListTriggers and GetTrigger controller functions for handling trigger requests. - Enhanced plugin management functions to create, update, and delete trigger installations during plugin lifecycle events. These changes integrate trigger capabilities into the system, improving the overall plugin functionality and management. * feat: add remapping for trigger icons in MediaBucket - Enhanced the RemapAssets function to include remapping of trigger identity icons and dark icons. - Added error handling for remapping failures to ensure robust asset management. These changes improve the handling of trigger assets within the plugin system, ensuring icons are correctly remapped as needed. * feat: add Multiple field to TriggerParameter for enhanced configuration - Introduced a new Multiple field in the TriggerParameter struct to allow for multiple values in trigger configurations. - This addition improves the flexibility of trigger parameters within the plugin system. These changes enhance the capability of trigger parameters, enabling more complex configurations. * feat: add Multiple field to ProviderConfig for enhanced configuration - Introduced a new Multiple field in the ProviderConfig struct to allow for multiple values in provider configurations. - This addition improves the flexibility of provider options within the plugin system. These changes enhance the capability of provider configurations, enabling more complex setups. * fix(plugin): update validation error messages in ManifestValidate method - Enhanced error messages in the ManifestValidate function to include 'trigger' in the validation checks for plugin declarations. - Updated logic to ensure that all relevant fields are considered when validating the presence of mutually exclusive parameters. * feat(trigger): add CHECKBOX parameter type to plugin entities and refactor the trigger provider strcuture - Introduced a new CHECKBOX parameter type in constant.go for plugin entities. - Updated tool_declaration.go and trigger_declaration.go to include TOOL_PARAMETER_TYPE_CHECKBOX and TRIGGER_PARAMETER_TYPE_CHECKBOX respectively. - Enhanced validation logic to accommodate the new CHECKBOX type in parameter checks. * fix(trigger): update SubscriptionSchema validation in TriggerProviderDeclaration - Changed SubscriptionSchema validation from 'omitempty' to 'required' in TriggerProviderDeclaration to ensure it is always provided. - Updated SubscriptionConstructor field to be a pointer to allow for optional inclusion in the trigger provider configuration. * fix(trigger): rename ParametersSchema to Parameters in SubscriptionConstructor - Updated the SubscriptionConstructor struct to rename the ParametersSchema field to Parameters for consistency. - Adjusted related JSON and YAML marshaling logic to reflect the new field name, ensuring proper handling of trigger parameters. * refactor(trigger): enhance YAML unmarshalling for SubscriptionConstructor and SubscriptionSchema - Introduced a new helper function to convert YAML nodes to ProviderConfig lists, improving the handling of subscription_schema and credentials_schema. - Updated the UnmarshalYAML method to utilize the new function, simplifying the logic for parsing different YAML formats. - Ensured proper initialization of SubscriptionConstructor fields to prevent nil pointer dereferences. * fix(trigger): update SubscriptionConstructor validation in TriggerProviderDeclaration - Changed the validation for SubscriptionConstructor in TriggerProviderDeclaration from 'omitempty,dive' to 'omitempty' to simplify the validation logic. - Ensured that the SubscriptionConstructor field remains optional while maintaining its intended functionality. * refactor(trigger): rename Trigger to Event in plugin entities and related structures - Updated the naming conventions in trigger_declaration.go to replace 'Trigger' with 'Event' for better clarity and consistency. - Adjusted related types, validation functions, and unmarshalling logic to reflect the new 'Event' terminology. - Ensured that all references to triggers in the codebase are updated to events, including in the SubscriptionConstructor and response structures. * refactor(trigger): rename TriggerInvoke to TriggerInvokeEvent and update related structures - Renamed TriggerInvoke function and associated request/response types to TriggerInvokeEvent for improved clarity. - Updated routing and controller methods to reflect the new naming convention. - Ensured all references to the trigger invoke functionality are consistent with the new event terminology. * refactor(trigger): remove Subscription struct from trigger_declaration.go and update TriggerDispatchEventRequest - Removed the Subscription struct from trigger_declaration.go to streamline the codebase. - Added Credentials field to TriggerDispatchEventRequest for enhanced functionality and clarity. - Ensured that the changes maintain consistency with the existing naming conventions and structures. * fix(trigger): improve nil checks for SubscriptionConstructor in TriggerProviderDeclaration - Added nil checks for SubscriptionConstructor before accessing its fields to prevent potential nil pointer dereferences. - Ensured that Parameters and CredentialsSchema are initialized only if SubscriptionConstructor is not nil, enhancing code robustness. * fix(plugin): add recovery mechanism in OnTraffic to handle panics - Introduced a deferred function in OnTraffic to recover from panics, logging the error and stack trace for better debugging. - This enhancement improves the stability of the DifyServer by preventing crashes due to unexpected runtime errors. * feat(trigger): add Subscription field to TriggerInvokeEventRequest - Introduced a new Subscription field in the TriggerInvokeEventRequest struct to accommodate subscription data. - Ensured the field is marked as required, enhancing the request's functionality and validation requirements. * refactor(event): simplify EventDescription structure in EventDeclaration - Removed the EventDescription struct and replaced it with a direct I18nObject field in EventDeclaration. - This change streamlines the event configuration by reducing complexity while maintaining required validation for the description. * feat(trigger): add UserID field to TriggerDispatchEventResponse - Introduced a new UserID field in the TriggerDispatchEventResponse struct to include user identification in the response. - The field is marked as optional, enhancing the response's flexibility while maintaining existing functionality. * feat: add payload to TriggerDispatchEventResponse * fix * feat(trigger): update TriggerDispatchEventResponse structure * fix: avoid path collusion * fix: missing ) * fix: query param * fix: form param * fix: remove redundant dynamic parameter access type * fix: remove dynamic parameter access type from validation * Update internal/server/controllers/plugins.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: Stream <[email protected]> Co-authored-by: Yeuoly <[email protected]> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 1f910b8 commit 4589841

File tree

35 files changed

+1540
-23
lines changed

35 files changed

+1540
-23
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ working
1414
cwd/
1515
bin/
1616
.venv/
17-
integration_test_cwd/
17+
integration_test_cwd/

internal/core/plugin_daemon/access_types/access.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ package access_types
33
type PluginAccessType string
44

55
const (
6-
PLUGIN_ACCESS_TYPE_TOOL PluginAccessType = "tool"
7-
PLUGIN_ACCESS_TYPE_MODEL PluginAccessType = "model"
8-
PLUGIN_ACCESS_TYPE_ENDPOINT PluginAccessType = "endpoint"
9-
PLUGIN_ACCESS_TYPE_AGENT_STRATEGY PluginAccessType = "agent_strategy"
10-
PLUGIN_ACCESS_TYPE_OAUTH PluginAccessType = "oauth"
11-
PLUGIN_ACCESS_TYPE_DATASOURCE PluginAccessType = "datasource"
6+
PLUGIN_ACCESS_TYPE_TOOL PluginAccessType = "tool"
7+
PLUGIN_ACCESS_TYPE_MODEL PluginAccessType = "model"
8+
PLUGIN_ACCESS_TYPE_ENDPOINT PluginAccessType = "endpoint"
9+
PLUGIN_ACCESS_TYPE_AGENT_STRATEGY PluginAccessType = "agent_strategy"
10+
PLUGIN_ACCESS_TYPE_OAUTH PluginAccessType = "oauth"
11+
PLUGIN_ACCESS_TYPE_DATASOURCE PluginAccessType = "datasource"
1212
PLUGIN_ACCESS_TYPE_DYNAMIC_PARAMETER PluginAccessType = "dynamic_parameter"
13+
PLUGIN_ACCESS_TYPE_TRIGGER PluginAccessType = "trigger"
1314
)
1415

1516
func (p PluginAccessType) IsValid() bool {
@@ -18,8 +19,9 @@ func (p PluginAccessType) IsValid() bool {
1819
p == PLUGIN_ACCESS_TYPE_ENDPOINT ||
1920
p == PLUGIN_ACCESS_TYPE_AGENT_STRATEGY ||
2021
p == PLUGIN_ACCESS_TYPE_OAUTH ||
22+
p == PLUGIN_ACCESS_TYPE_DATASOURCE ||
2123
p == PLUGIN_ACCESS_TYPE_DYNAMIC_PARAMETER ||
22-
p == PLUGIN_ACCESS_TYPE_DATASOURCE
24+
p == PLUGIN_ACCESS_TYPE_TRIGGER
2325
}
2426

2527
type PluginAccessAction string
@@ -51,7 +53,13 @@ const (
5153
PLUGIN_ACCESS_ACTION_INVOKE_ONLINE_DOCUMENT_DATASOURCE_GET_PAGE_CONTENT PluginAccessAction = "invoke_online_document_datasource_get_page_content"
5254
PLUGIN_ACCESS_ACTION_INVOKE_ONLINE_DRIVE_BROWSE_FILES PluginAccessAction = "invoke_online_drive_browse_files"
5355
PLUGIN_ACCESS_ACTION_INVOKE_ONLINE_DRIVE_DOWNLOAD_FILE PluginAccessAction = "invoke_online_drive_download_file"
54-
PLUGIN_ACCESS_ACTION_DYNAMIC_PARAMETER_FETCH_OPTIONS PluginAccessAction = "fetch_parameter_options"
56+
PLUGIN_ACCESS_ACTION_DYNAMIC_PARAMETER_FETCH_OPTIONS PluginAccessAction = "fetch_parameter_options"
57+
PLUGIN_ACCESS_ACTION_INVOKE_TRIGGER_EVENT PluginAccessAction = "invoke_trigger_event"
58+
PLUGIN_ACCESS_ACTION_DISPATCH_TRIGGER_EVENT PluginAccessAction = "dispatch_trigger_event"
59+
PLUGIN_ACCESS_ACTION_SUBSCRIBE_TRIGGER PluginAccessAction = "subscribe_trigger"
60+
PLUGIN_ACCESS_ACTION_UNSUBSCRIBE_TRIGGER PluginAccessAction = "unsubscribe_trigger"
61+
PLUGIN_ACCESS_ACTION_REFRESH_TRIGGER PluginAccessAction = "refresh_trigger"
62+
PLUGIN_ACCESS_ACTION_VALIDATE_TRIGGER_CREDENTIALS PluginAccessAction = "validate_trigger_credentials"
5563
)
5664

5765
func (p PluginAccessAction) IsValid() bool {
@@ -81,5 +89,11 @@ func (p PluginAccessAction) IsValid() bool {
8189
p == PLUGIN_ACCESS_ACTION_INVOKE_ONLINE_DOCUMENT_DATASOURCE_GET_PAGES ||
8290
p == PLUGIN_ACCESS_ACTION_INVOKE_ONLINE_DOCUMENT_DATASOURCE_GET_PAGE_CONTENT ||
8391
p == PLUGIN_ACCESS_ACTION_INVOKE_ONLINE_DRIVE_BROWSE_FILES ||
84-
p == PLUGIN_ACCESS_ACTION_INVOKE_ONLINE_DRIVE_DOWNLOAD_FILE
92+
p == PLUGIN_ACCESS_ACTION_INVOKE_ONLINE_DRIVE_DOWNLOAD_FILE ||
93+
p == PLUGIN_ACCESS_ACTION_INVOKE_TRIGGER_EVENT ||
94+
p == PLUGIN_ACCESS_ACTION_DISPATCH_TRIGGER_EVENT ||
95+
p == PLUGIN_ACCESS_ACTION_SUBSCRIBE_TRIGGER ||
96+
p == PLUGIN_ACCESS_ACTION_UNSUBSCRIBE_TRIGGER ||
97+
p == PLUGIN_ACCESS_ACTION_REFRESH_TRIGGER ||
98+
p == PLUGIN_ACCESS_ACTION_VALIDATE_TRIGGER_CREDENTIALS
8599
}

internal/core/plugin_daemon/trigger.gen.go

Lines changed: 87 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/core/plugin_manager/debugging_runtime/hooks.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"encoding/base64"
66
"fmt"
7+
"runtime/debug"
78
"sync"
89
"sync/atomic"
910
"time"
@@ -155,6 +156,13 @@ func (s *DifyServer) OnShutdown(c gnet.Engine) {
155156
}
156157

157158
func (s *DifyServer) OnTraffic(c gnet.Conn) (action gnet.Action) {
159+
defer func() {
160+
if r := recover(); r != nil {
161+
traceback := string(debug.Stack())
162+
log.Error("panic in OnTraffic: %v\n%s", r, traceback)
163+
}
164+
}()
165+
158166
codec := c.Context().(*codec)
159167
messages, err := codec.Decode(c)
160168
if err != nil {
@@ -276,7 +284,8 @@ func (s *DifyServer) onMessage(runtime *RemotePluginRuntime, message []byte) {
276284
!runtime.endpointsRegistrationTransferred &&
277285
!runtime.toolsRegistrationTransferred &&
278286
!runtime.agentStrategyRegistrationTransferred &&
279-
!runtime.datasourceRegistrationTransferred {
287+
!runtime.datasourceRegistrationTransferred &&
288+
!runtime.triggersRegistrationTransferred {
280289
closeConn([]byte("no registration transferred, cannot initialize\n"))
281290
return
282291
}
@@ -442,6 +451,24 @@ func (s *DifyServer) onMessage(runtime *RemotePluginRuntime, message []byte) {
442451
declaration.Datasource = &datasources[0]
443452
runtime.Config = declaration
444453
}
454+
} else if registerPayload.Type == plugin_entities.REGISTER_EVENT_TYPE_TRIGGER_DECLARATION {
455+
if runtime.triggersRegistrationTransferred {
456+
return
457+
}
458+
459+
triggers, err := parser.UnmarshalJsonBytes2Slice[plugin_entities.TriggerProviderDeclaration](registerPayload.Data)
460+
if err != nil {
461+
closeConn([]byte(fmt.Sprintf("triggers register failed, invalid triggers declaration: %v\n", err)))
462+
return
463+
}
464+
465+
runtime.triggersRegistrationTransferred = true
466+
467+
if len(triggers) > 0 {
468+
declaration := runtime.Config
469+
declaration.Trigger = &triggers[0]
470+
runtime.Config = declaration
471+
}
445472
}
446473
} else {
447474
// continue handle messages if handshake completed

internal/core/plugin_manager/debugging_runtime/type.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type RemotePluginRuntime struct {
5959
endpointsRegistrationTransferred bool
6060
agentStrategyRegistrationTransferred bool
6161
datasourceRegistrationTransferred bool
62+
triggersRegistrationTransferred bool
6263
assetsTransferred bool
6364

6465
// tenant id

internal/core/plugin_manager/manager.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"strings"
88

9+
lru "github.com/hashicorp/golang-lru/v2"
910
"github.com/langgenius/dify-cloud-kit/oss"
1011
"github.com/langgenius/dify-plugin-daemon/internal/core/dify_invocation"
1112
"github.com/langgenius/dify-plugin-daemon/internal/core/dify_invocation/real"
@@ -79,6 +80,15 @@ func InitGlobalManager(oss oss.OSS, configuration *app.Config) *PluginManager {
7980
config: configuration,
8081
}
8182

83+
// initialize plugin asset cache
84+
if pluginAssetCache == nil {
85+
c, err := lru.New[string, []byte](256)
86+
if err != nil {
87+
log.Panic("init plugin asset cache failed: %s", err.Error())
88+
}
89+
pluginAssetCache = c
90+
}
91+
8292
return manager
8393
}
8494

@@ -259,3 +269,38 @@ func (p *PluginManager) GetDeclaration(
259269
plugin_unique_identifier, runtime_type,
260270
)
261271
}
272+
273+
var (
274+
pluginAssetCache *lru.Cache[string, []byte]
275+
)
276+
277+
func pluginAssetCacheKey(
278+
pluginUniqueIdentifier plugin_entities.PluginUniqueIdentifier,
279+
path string,
280+
) string {
281+
return fmt.Sprintf("%s/%s", pluginUniqueIdentifier.String(), path)
282+
}
283+
func (p *PluginManager) ExtractPluginAsset(
284+
pluginUniqueIdentifier plugin_entities.PluginUniqueIdentifier,
285+
path string,
286+
) ([]byte, error) {
287+
key := pluginAssetCacheKey(pluginUniqueIdentifier, path)
288+
cached, ok := pluginAssetCache.Get(key)
289+
if ok {
290+
return cached, nil
291+
}
292+
pkgBytes, err := manager.GetPackage(pluginUniqueIdentifier)
293+
if err != nil {
294+
return nil, err
295+
}
296+
zipDecoder, err := decoder.NewZipPluginDecoder(pkgBytes)
297+
if err != nil {
298+
return nil, err
299+
}
300+
assets, err := zipDecoder.Assets()
301+
if err != nil {
302+
return nil, err
303+
}
304+
pluginAssetCache.Add(key, assets[path])
305+
return assets[path], nil
306+
}

internal/core/plugin_manager/media_transport/assets.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,22 @@ func (m *MediaBucket) RemapAssets(declaration *plugin_entities.PluginDeclaration
124124
}
125125
}
126126

127+
if declaration.Trigger != nil {
128+
if declaration.Trigger.Identity.Icon != "" {
129+
declaration.Trigger.Identity.Icon, err = remap(declaration.Trigger.Identity.Icon)
130+
if err != nil {
131+
return nil, errors.Join(err, fmt.Errorf("failed to remap trigger icon"))
132+
}
133+
}
134+
135+
if declaration.Trigger.Identity.IconDark != "" {
136+
declaration.Trigger.Identity.IconDark, err = remap(declaration.Trigger.Identity.IconDark)
137+
if err != nil {
138+
return nil, errors.Join(err, fmt.Errorf("failed to remap trigger icon dark"))
139+
}
140+
}
141+
}
142+
127143
if declaration.Icon != "" {
128144
declaration.Icon, err = remap(declaration.Icon)
129145
if err != nil {

internal/db/init.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ func autoMigrate() error {
2121
models.InstallTask{},
2222
models.TenantStorage{},
2323
models.AgentStrategyInstallation{},
24+
models.TriggerInstallation{},
25+
models.PluginReadme{},
2426
)
2527

2628
if err != nil {
@@ -45,6 +47,7 @@ func autoMigrate() error {
4547
"tool_installations",
4648
"ai_model_installations",
4749
"agent_strategy_installations",
50+
"trigger_installations",
4851
}
4952

5053
for _, table := range tables {

0 commit comments

Comments
 (0)