mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 01:48:04 +08:00
feat(trigger): add trigger provider management and webhook handling functionality
This commit is contained in:
@ -68,6 +68,19 @@ class ToolProviderCredentialsCache(ProviderCredentialsCache):
|
||||
return f"tool_credentials:tenant_id:{tenant_id}:provider:{provider}:credential_id:{credential_id}"
|
||||
|
||||
|
||||
class TriggerProviderCredentialCache(ProviderCredentialsCache):
|
||||
"""Cache for trigger provider credentials"""
|
||||
|
||||
def __init__(self, tenant_id: str, provider: str, credential_id: str):
|
||||
super().__init__(tenant_id=tenant_id, provider=provider, credential_id=credential_id)
|
||||
|
||||
def _generate_cache_key(self, **kwargs) -> str:
|
||||
tenant_id = kwargs["tenant_id"]
|
||||
provider = kwargs["provider"]
|
||||
credential_id = kwargs["credential_id"]
|
||||
return f"trigger_credentials:tenant_id:{tenant_id}:provider:{provider}:credential_id:{credential_id}"
|
||||
|
||||
|
||||
class NoOpProviderCredentialCache:
|
||||
"""No-op provider credential cache"""
|
||||
|
||||
|
||||
@ -184,6 +184,10 @@ class ToolProviderID(GenericProviderID):
|
||||
self.plugin_name = f"{self.provider_name}_tool"
|
||||
|
||||
|
||||
class TriggerProviderID(GenericProviderID):
|
||||
pass
|
||||
|
||||
|
||||
class PluginDependency(BaseModel):
|
||||
class Type(enum.StrEnum):
|
||||
Github = PluginInstallationSource.Github.value
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import enum
|
||||
from collections.abc import Mapping, Sequence
|
||||
from datetime import datetime
|
||||
from enum import StrEnum
|
||||
@ -13,6 +14,7 @@ from core.plugin.entities.parameters import PluginParameterOption
|
||||
from core.plugin.entities.plugin import PluginDeclaration, PluginEntity
|
||||
from core.tools.entities.common_entities import I18nObject
|
||||
from core.tools.entities.tool_entities import ToolProviderEntityWithPlugin
|
||||
from core.trigger.entities import TriggerProviderEntity
|
||||
|
||||
T = TypeVar("T", bound=(BaseModel | dict | list | bool | str))
|
||||
|
||||
@ -196,3 +198,43 @@ class PluginListResponse(BaseModel):
|
||||
|
||||
class PluginDynamicSelectOptionsResponse(BaseModel):
|
||||
options: Sequence[PluginParameterOption] = Field(description="The options of the dynamic select.")
|
||||
|
||||
|
||||
class PluginTriggerProviderEntity(BaseModel):
|
||||
provider: str
|
||||
plugin_unique_identifier: str
|
||||
plugin_id: str
|
||||
declaration: TriggerProviderEntity
|
||||
|
||||
|
||||
class CredentialType(enum.StrEnum):
|
||||
API_KEY = "api-key"
|
||||
OAUTH2 = "oauth2"
|
||||
|
||||
def get_name(self):
|
||||
if self == CredentialType.API_KEY:
|
||||
return "API KEY"
|
||||
elif self == CredentialType.OAUTH2:
|
||||
return "AUTH"
|
||||
else:
|
||||
return self.value.replace("-", " ").upper()
|
||||
|
||||
def is_editable(self):
|
||||
return self == CredentialType.API_KEY
|
||||
|
||||
def is_validate_allowed(self):
|
||||
return self == CredentialType.API_KEY
|
||||
|
||||
@classmethod
|
||||
def values(cls):
|
||||
return [item.value for item in cls]
|
||||
|
||||
@classmethod
|
||||
def of(cls, credential_type: str) -> "CredentialType":
|
||||
type_name = credential_type.lower()
|
||||
if type_name == "api-key":
|
||||
return cls.API_KEY
|
||||
elif type_name == "oauth2":
|
||||
return cls.OAUTH2
|
||||
else:
|
||||
raise ValueError(f"Invalid credential type: {credential_type}")
|
||||
|
||||
@ -4,10 +4,10 @@ from typing import Any, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from core.plugin.entities.plugin import GenericProviderID, ToolProviderID
|
||||
from core.plugin.entities.plugin_daemon import PluginBasicBooleanResponse, PluginToolProviderEntity
|
||||
from core.plugin.entities.plugin_daemon import CredentialType, PluginBasicBooleanResponse, PluginToolProviderEntity
|
||||
from core.plugin.impl.base import BasePluginClient
|
||||
from core.plugin.utils.chunk_merger import merge_blob_chunks
|
||||
from core.tools.entities.tool_entities import CredentialType, ToolInvokeMessage, ToolParameter
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage, ToolParameter
|
||||
|
||||
|
||||
class PluginToolManager(BasePluginClient):
|
||||
|
||||
68
api/core/plugin/impl/trigger.py
Normal file
68
api/core/plugin/impl/trigger.py
Normal file
@ -0,0 +1,68 @@
|
||||
from typing import Any
|
||||
|
||||
from core.plugin.entities.plugin import ToolProviderID
|
||||
from core.plugin.entities.plugin_daemon import PluginToolProviderEntity, PluginTriggerProviderEntity
|
||||
from core.plugin.impl.base import BasePluginClient
|
||||
|
||||
|
||||
class PluginTriggerManager(BasePluginClient):
|
||||
def fetch_trigger_providers(self, tenant_id: str) -> list[PluginTriggerProviderEntity]:
|
||||
"""
|
||||
Fetch tool providers for the given tenant.
|
||||
"""
|
||||
|
||||
def transformer(json_response: dict[str, Any]) -> dict:
|
||||
for provider in json_response.get("data", []):
|
||||
declaration = provider.get("declaration", {}) or {}
|
||||
provider_name = declaration.get("identity", {}).get("name")
|
||||
for tool in declaration.get("tools", []):
|
||||
tool["identity"]["provider"] = provider_name
|
||||
|
||||
return json_response
|
||||
|
||||
response = self._request_with_plugin_daemon_response(
|
||||
"GET",
|
||||
f"plugin/{tenant_id}/management/tools",
|
||||
list[PluginToolProviderEntity],
|
||||
params={"page": 1, "page_size": 256},
|
||||
transformer=transformer,
|
||||
)
|
||||
|
||||
for provider in response:
|
||||
provider.declaration.identity.name = f"{provider.plugin_id}/{provider.declaration.identity.name}"
|
||||
|
||||
# override the provider name for each tool to plugin_id/provider_name
|
||||
for tool in provider.declaration.tools:
|
||||
tool.identity.provider = provider.declaration.identity.name
|
||||
|
||||
return response
|
||||
|
||||
def fetch_tool_provider(self, tenant_id: str, provider: str) -> PluginToolProviderEntity:
|
||||
"""
|
||||
Fetch tool provider for the given tenant and plugin.
|
||||
"""
|
||||
tool_provider_id = ToolProviderID(provider)
|
||||
|
||||
def transformer(json_response: dict[str, Any]) -> dict:
|
||||
data = json_response.get("data")
|
||||
if data:
|
||||
for tool in data.get("declaration", {}).get("tools", []):
|
||||
tool["identity"]["provider"] = tool_provider_id.provider_name
|
||||
|
||||
return json_response
|
||||
|
||||
response = self._request_with_plugin_daemon_response(
|
||||
"GET",
|
||||
f"plugin/{tenant_id}/management/tool",
|
||||
PluginToolProviderEntity,
|
||||
params={"provider": tool_provider_id.provider_name, "plugin_id": tool_provider_id.plugin_id},
|
||||
transformer=transformer,
|
||||
)
|
||||
|
||||
response.declaration.identity.name = f"{response.plugin_id}/{response.declaration.identity.name}"
|
||||
|
||||
# override the provider name for each tool to plugin_id/provider_name
|
||||
for tool in response.declaration.tools:
|
||||
tool.identity.provider = response.declaration.identity.name
|
||||
|
||||
return response
|
||||
@ -4,7 +4,8 @@ from openai import BaseModel
|
||||
from pydantic import Field
|
||||
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from core.tools.entities.tool_entities import CredentialType, ToolInvokeFrom
|
||||
from core.plugin.entities.plugin_daemon import CredentialType
|
||||
from core.tools.entities.tool_entities import ToolInvokeFrom
|
||||
|
||||
|
||||
class ToolRuntime(BaseModel):
|
||||
|
||||
@ -4,11 +4,11 @@ from typing import Any
|
||||
|
||||
from core.entities.provider_entities import ProviderConfig
|
||||
from core.helper.module_import_helper import load_single_subclass_from_source
|
||||
from core.plugin.entities.plugin_daemon import CredentialType
|
||||
from core.tools.__base.tool_provider import ToolProviderController
|
||||
from core.tools.__base.tool_runtime import ToolRuntime
|
||||
from core.tools.builtin_tool.tool import BuiltinTool
|
||||
from core.tools.entities.tool_entities import (
|
||||
CredentialType,
|
||||
OAuthSchema,
|
||||
ToolEntity,
|
||||
ToolProviderEntity,
|
||||
|
||||
@ -4,9 +4,10 @@ from typing import Any, Literal, Optional
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||
from core.plugin.entities.plugin_daemon import CredentialType
|
||||
from core.tools.__base.tool import ToolParameter
|
||||
from core.tools.entities.common_entities import I18nObject
|
||||
from core.tools.entities.tool_entities import CredentialType, ToolProviderType
|
||||
from core.tools.entities.tool_entities import ToolProviderType
|
||||
|
||||
|
||||
class ToolApiEntity(BaseModel):
|
||||
|
||||
@ -476,36 +476,3 @@ class ToolSelector(BaseModel):
|
||||
|
||||
def to_plugin_parameter(self) -> dict[str, Any]:
|
||||
return self.model_dump()
|
||||
|
||||
|
||||
class CredentialType(enum.StrEnum):
|
||||
API_KEY = "api-key"
|
||||
OAUTH2 = "oauth2"
|
||||
|
||||
def get_name(self):
|
||||
if self == CredentialType.API_KEY:
|
||||
return "API KEY"
|
||||
elif self == CredentialType.OAUTH2:
|
||||
return "AUTH"
|
||||
else:
|
||||
return self.value.replace("-", " ").upper()
|
||||
|
||||
def is_editable(self):
|
||||
return self == CredentialType.API_KEY
|
||||
|
||||
def is_validate_allowed(self):
|
||||
return self == CredentialType.API_KEY
|
||||
|
||||
@classmethod
|
||||
def values(cls):
|
||||
return [item.value for item in cls]
|
||||
|
||||
@classmethod
|
||||
def of(cls, credential_type: str) -> "CredentialType":
|
||||
type_name = credential_type.lower()
|
||||
if type_name == "api-key":
|
||||
return cls.API_KEY
|
||||
elif type_name == "oauth2":
|
||||
return cls.OAUTH2
|
||||
else:
|
||||
raise ValueError(f"Invalid credential type: {credential_type}")
|
||||
|
||||
@ -37,6 +37,7 @@ from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from core.helper.module_import_helper import load_single_subclass_from_source
|
||||
from core.helper.position_helper import is_filtered
|
||||
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||
from core.plugin.entities.plugin_daemon import CredentialType
|
||||
from core.tools.__base.tool import Tool
|
||||
from core.tools.builtin_tool.provider import BuiltinToolProviderController
|
||||
from core.tools.builtin_tool.providers._positions import BuiltinToolProviderSort
|
||||
@ -47,7 +48,6 @@ from core.tools.entities.api_entities import ToolProviderApiEntity, ToolProvider
|
||||
from core.tools.entities.common_entities import I18nObject
|
||||
from core.tools.entities.tool_entities import (
|
||||
ApiProviderAuthType,
|
||||
CredentialType,
|
||||
ToolInvokeFrom,
|
||||
ToolParameter,
|
||||
ToolProviderType,
|
||||
|
||||
1
api/core/trigger/__init__.py
Normal file
1
api/core/trigger/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# Core trigger module initialization
|
||||
244
api/core/trigger/entities.py
Normal file
244
api/core/trigger/entities.py
Normal file
@ -0,0 +1,244 @@
|
||||
from collections.abc import Mapping
|
||||
from enum import StrEnum
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from core.tools.entities.common_entities import I18nObject
|
||||
|
||||
|
||||
class TriggerParameterOption(BaseModel):
|
||||
"""
|
||||
The option of the trigger parameter
|
||||
"""
|
||||
|
||||
value: str = Field(..., description="The value of the option")
|
||||
label: I18nObject = Field(..., description="The label of the option")
|
||||
|
||||
|
||||
class TriggerParameterType(StrEnum):
|
||||
"""The type of the parameter"""
|
||||
|
||||
STRING = "string"
|
||||
NUMBER = "number"
|
||||
BOOLEAN = "boolean"
|
||||
SELECT = "select"
|
||||
FILE = "file"
|
||||
FILES = "files"
|
||||
MODEL_SELECTOR = "model-selector"
|
||||
APP_SELECTOR = "app-selector"
|
||||
OBJECT = "object"
|
||||
ARRAY = "array"
|
||||
DYNAMIC_SELECT = "dynamic-select"
|
||||
|
||||
|
||||
class ParameterAutoGenerate(BaseModel):
|
||||
"""Auto generation configuration for parameters"""
|
||||
|
||||
enabled: bool = Field(default=False, description="Whether auto generation is enabled")
|
||||
template: Optional[str] = Field(default=None, description="Template for auto generation")
|
||||
|
||||
|
||||
class ParameterTemplate(BaseModel):
|
||||
"""Template configuration for parameters"""
|
||||
|
||||
value: str = Field(..., description="Template value")
|
||||
type: str = Field(default="jinja2", description="Template type")
|
||||
|
||||
|
||||
class TriggerParameter(BaseModel):
|
||||
"""
|
||||
The parameter of the trigger
|
||||
"""
|
||||
|
||||
name: str = Field(..., description="The name of the parameter")
|
||||
label: I18nObject = Field(..., description="The label presented to the user")
|
||||
type: TriggerParameterType = Field(..., description="The type of the parameter")
|
||||
auto_generate: Optional[ParameterAutoGenerate] = Field(
|
||||
default=None, description="The auto generate of the parameter"
|
||||
)
|
||||
template: Optional[ParameterTemplate] = Field(default=None, description="The template of the parameter")
|
||||
scope: Optional[str] = None
|
||||
required: Optional[bool] = False
|
||||
default: Union[int, float, str, None] = None
|
||||
min: Union[float, int, None] = None
|
||||
max: Union[float, int, None] = None
|
||||
precision: Optional[int] = None
|
||||
options: Optional[list[TriggerParameterOption]] = None
|
||||
description: Optional[I18nObject] = None
|
||||
|
||||
|
||||
class TriggerProviderIdentity(BaseModel):
|
||||
"""
|
||||
The identity of the trigger provider
|
||||
"""
|
||||
|
||||
author: str = Field(..., description="The author of the trigger provider")
|
||||
name: str = Field(..., description="The name of the trigger provider")
|
||||
label: I18nObject = Field(..., description="The label of the trigger provider")
|
||||
description: I18nObject = Field(..., description="The description of the trigger provider")
|
||||
icon: Optional[str] = Field(default=None, description="The icon of the trigger provider")
|
||||
tags: list[str] = Field(default_factory=list, description="The tags of the trigger provider")
|
||||
|
||||
|
||||
class TriggerIdentity(BaseModel):
|
||||
"""
|
||||
The identity of the trigger
|
||||
"""
|
||||
|
||||
author: str = Field(..., description="The author of the trigger")
|
||||
name: str = Field(..., description="The name of the trigger")
|
||||
label: I18nObject = Field(..., description="The label of the trigger")
|
||||
|
||||
|
||||
class TriggerDescription(BaseModel):
|
||||
"""
|
||||
The description of the trigger
|
||||
"""
|
||||
|
||||
human: I18nObject = Field(..., description="Human readable description")
|
||||
llm: I18nObject = Field(..., description="LLM readable description")
|
||||
|
||||
|
||||
class TriggerConfigurationExtraPython(BaseModel):
|
||||
"""Python configuration for trigger"""
|
||||
|
||||
source: str = Field(..., description="The source file path for the trigger implementation")
|
||||
|
||||
|
||||
class TriggerConfigurationExtra(BaseModel):
|
||||
"""
|
||||
The extra configuration for trigger
|
||||
"""
|
||||
|
||||
|
||||
class TriggerEntity(BaseModel):
|
||||
"""
|
||||
The configuration of a trigger
|
||||
"""
|
||||
|
||||
python: TriggerConfigurationExtraPython
|
||||
identity: TriggerIdentity = Field(..., description="The identity of the trigger")
|
||||
parameters: list[TriggerParameter] = Field(default=[], description="The parameters of the trigger")
|
||||
description: TriggerDescription = Field(..., description="The description of the trigger")
|
||||
extra: TriggerConfigurationExtra = Field(..., description="The extra configuration of the trigger")
|
||||
output_schema: Optional[Mapping[str, Any]] = Field(
|
||||
default=None, description="The output schema that this trigger produces"
|
||||
)
|
||||
|
||||
|
||||
class TriggerProviderConfigurationExtraPython(BaseModel):
|
||||
"""Python configuration for trigger provider"""
|
||||
|
||||
source: str = Field(..., description="The source file path for the trigger provider implementation")
|
||||
|
||||
|
||||
class TriggerProviderConfigurationExtra(BaseModel):
|
||||
"""
|
||||
The extra configuration for trigger provider
|
||||
"""
|
||||
|
||||
python: TriggerProviderConfigurationExtraPython
|
||||
|
||||
|
||||
class OAuthSchema(BaseModel):
|
||||
"""OAuth configuration schema"""
|
||||
|
||||
authorization_url: str = Field(..., description="OAuth authorization URL")
|
||||
token_url: str = Field(..., description="OAuth token URL")
|
||||
client_id: str = Field(..., description="OAuth client ID")
|
||||
client_secret: str = Field(..., description="OAuth client secret")
|
||||
redirect_uri: Optional[str] = Field(default=None, description="OAuth redirect URI")
|
||||
scope: Optional[str] = Field(default=None, description="OAuth scope")
|
||||
|
||||
|
||||
class ProviderConfig(BaseModel):
|
||||
"""Provider configuration item"""
|
||||
|
||||
name: str = Field(..., description="Configuration field name")
|
||||
type: str = Field(..., description="Configuration field type")
|
||||
required: bool = Field(default=False, description="Whether this field is required")
|
||||
default: Any = Field(default=None, description="Default value")
|
||||
label: Optional[I18nObject] = Field(default=None, description="Field label")
|
||||
description: Optional[I18nObject] = Field(default=None, description="Field description")
|
||||
options: Optional[list[dict[str, Any]]] = Field(default=None, description="Options for select type")
|
||||
|
||||
|
||||
class TriggerProviderEntity(BaseModel):
|
||||
"""
|
||||
The configuration of a trigger provider
|
||||
"""
|
||||
|
||||
identity: TriggerProviderIdentity = Field(..., description="The identity of the trigger provider")
|
||||
credentials_schema: list[ProviderConfig] = Field(
|
||||
default_factory=list,
|
||||
description="The credentials schema of the trigger provider",
|
||||
)
|
||||
oauth_schema: Optional[OAuthSchema] = Field(
|
||||
default=None,
|
||||
description="The OAuth schema of the trigger provider if OAuth is supported",
|
||||
)
|
||||
subscription_schema: list[ProviderConfig] = Field(
|
||||
default_factory=list,
|
||||
description="The subscription schema for trigger(webhook, polling, etc.) subscription parameters",
|
||||
)
|
||||
triggers: list[TriggerEntity] = Field(default=[], description="The triggers of the trigger provider")
|
||||
extra: TriggerProviderConfigurationExtra = Field(..., description="The extra configuration of the trigger provider")
|
||||
|
||||
|
||||
class Subscription(BaseModel):
|
||||
"""
|
||||
Result of a successful trigger subscription operation.
|
||||
|
||||
Contains all information needed to manage the subscription lifecycle.
|
||||
"""
|
||||
|
||||
expire_at: int = Field(
|
||||
..., description="The timestamp when the subscription will expire, this for refresh the subscription"
|
||||
)
|
||||
|
||||
metadata: dict[str, Any] = Field(
|
||||
..., description="Metadata about the subscription in the external service, defined in subscription_schema"
|
||||
)
|
||||
|
||||
|
||||
class Unsubscription(BaseModel):
|
||||
"""
|
||||
Result of a trigger unsubscription operation.
|
||||
|
||||
Provides detailed information about the unsubscription attempt,
|
||||
including success status and error details if failed.
|
||||
"""
|
||||
|
||||
success: bool = Field(..., description="Whether the unsubscription was successful")
|
||||
|
||||
message: Optional[str] = Field(
|
||||
None,
|
||||
description="Human-readable message about the operation result. "
|
||||
"Success message for successful operations, "
|
||||
"detailed error information for failures.",
|
||||
)
|
||||
|
||||
|
||||
# Export all entities
|
||||
__all__ = [
|
||||
"OAuthSchema",
|
||||
"ParameterAutoGenerate",
|
||||
"ParameterTemplate",
|
||||
"ProviderConfig",
|
||||
"Subscription",
|
||||
"TriggerConfigurationExtra",
|
||||
"TriggerConfigurationExtraPython",
|
||||
"TriggerDescription",
|
||||
"TriggerEntity",
|
||||
"TriggerEntity",
|
||||
"TriggerIdentity",
|
||||
"TriggerParameter",
|
||||
"TriggerParameterOption",
|
||||
"TriggerParameterType",
|
||||
"TriggerProviderConfigurationExtra",
|
||||
"TriggerProviderConfigurationExtraPython",
|
||||
"TriggerProviderEntity",
|
||||
"TriggerProviderIdentity",
|
||||
"Unsubscription",
|
||||
]
|
||||
199
api/core/trigger/provider.py
Normal file
199
api/core/trigger/provider.py
Normal file
@ -0,0 +1,199 @@
|
||||
"""
|
||||
Trigger Provider Controller for managing trigger providers
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from core.plugin.entities.plugin_daemon import CredentialType
|
||||
from core.trigger.entities import (
|
||||
ProviderConfig,
|
||||
Subscription,
|
||||
TriggerEntity,
|
||||
TriggerProviderEntity,
|
||||
TriggerProviderIdentity,
|
||||
Unsubscription,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TriggerProviderController:
|
||||
"""
|
||||
Controller for plugin trigger providers
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
entity: TriggerProviderEntity,
|
||||
plugin_id: str,
|
||||
plugin_unique_identifier: str,
|
||||
tenant_id: str,
|
||||
):
|
||||
"""
|
||||
Initialize plugin trigger provider controller
|
||||
|
||||
:param entity: Trigger provider entity
|
||||
:param plugin_id: Plugin ID
|
||||
:param plugin_unique_identifier: Plugin unique identifier
|
||||
:param tenant_id: Tenant ID
|
||||
"""
|
||||
self.entity = entity
|
||||
self.tenant_id = tenant_id
|
||||
self.plugin_id = plugin_id
|
||||
self.plugin_unique_identifier = plugin_unique_identifier
|
||||
|
||||
@property
|
||||
def identity(self) -> TriggerProviderIdentity:
|
||||
"""Get provider identity"""
|
||||
return self.entity.identity
|
||||
|
||||
def get_triggers(self) -> list[TriggerEntity]:
|
||||
"""
|
||||
Get all triggers for this provider
|
||||
|
||||
:return: List of trigger entities
|
||||
"""
|
||||
return self.entity.triggers
|
||||
|
||||
def get_trigger(self, trigger_name: str) -> Optional[TriggerEntity]:
|
||||
"""
|
||||
Get a specific trigger by name
|
||||
|
||||
:param trigger_name: Trigger name
|
||||
:return: Trigger entity or None
|
||||
"""
|
||||
for trigger in self.entity.triggers:
|
||||
if trigger.identity.name == trigger_name:
|
||||
return trigger
|
||||
return None
|
||||
|
||||
def get_credentials_schema(self) -> list[ProviderConfig]:
|
||||
"""
|
||||
Get credentials schema for this provider
|
||||
|
||||
:return: List of provider config schemas
|
||||
"""
|
||||
return self.entity.credentials_schema
|
||||
|
||||
def get_subscription_schema(self) -> list[ProviderConfig]:
|
||||
"""
|
||||
Get subscription schema for this provider
|
||||
|
||||
:return: List of subscription config schemas
|
||||
"""
|
||||
return self.entity.subscription_schema
|
||||
|
||||
def validate_credentials(self, credentials: dict) -> None:
|
||||
"""
|
||||
Validate credentials against schema
|
||||
|
||||
:param credentials: Credentials to validate
|
||||
:raises ValueError: If credentials are invalid
|
||||
"""
|
||||
for config in self.entity.credentials_schema:
|
||||
if config.required and config.name not in credentials:
|
||||
raise ValueError(f"Missing required credential field: {config.name}")
|
||||
|
||||
def get_supported_credential_types(self) -> list[CredentialType]:
|
||||
"""
|
||||
Get supported credential types for this provider.
|
||||
|
||||
:return: List of supported credential types
|
||||
"""
|
||||
types = []
|
||||
if self.entity.oauth_schema:
|
||||
types.append(CredentialType.OAUTH2)
|
||||
if self.entity.credentials_schema:
|
||||
types.append(CredentialType.API_KEY)
|
||||
return types
|
||||
|
||||
def get_credentials_schema_by_type(self, credential_type: str) -> list[ProviderConfig]:
|
||||
"""
|
||||
Get credentials schema by credential type
|
||||
|
||||
:param credential_type: The type of credential (oauth or api_key)
|
||||
:return: List of provider config schemas
|
||||
"""
|
||||
if credential_type == CredentialType.OAUTH2.value:
|
||||
return self.entity.oauth_schema.credentials_schema.copy() if self.entity.oauth_schema else []
|
||||
if credential_type == CredentialType.API_KEY.value:
|
||||
return self.entity.credentials_schema.copy() if self.entity.credentials_schema else []
|
||||
raise ValueError(f"Invalid credential type: {credential_type}")
|
||||
|
||||
def get_oauth_client_schema(self) -> list[ProviderConfig]:
|
||||
"""
|
||||
Get OAuth client schema for this provider
|
||||
|
||||
:return: List of OAuth client config schemas
|
||||
"""
|
||||
return self.entity.oauth_schema.client_schema.copy() if self.entity.oauth_schema else []
|
||||
|
||||
@property
|
||||
def need_credentials(self) -> bool:
|
||||
"""Check if this provider needs credentials"""
|
||||
return len(self.get_supported_credential_types()) > 0
|
||||
|
||||
def execute_trigger(self, trigger_name: str, parameters: dict, credentials: dict) -> dict:
|
||||
"""
|
||||
Execute a trigger through plugin runtime
|
||||
|
||||
:param trigger_name: Trigger name
|
||||
:param parameters: Trigger parameters
|
||||
:param credentials: Provider credentials
|
||||
:return: Execution result
|
||||
"""
|
||||
logger.info("Executing trigger %s for plugin %s", trigger_name, self.plugin_id)
|
||||
return {
|
||||
"success": True,
|
||||
"trigger": trigger_name,
|
||||
"plugin": self.plugin_id,
|
||||
"result": "Trigger executed successfully",
|
||||
}
|
||||
|
||||
def subscribe_trigger(self, trigger_name: str, subscription_params: dict, credentials: dict) -> Subscription:
|
||||
"""
|
||||
Subscribe to a trigger through plugin runtime
|
||||
|
||||
:param trigger_name: Trigger name
|
||||
:param subscription_params: Subscription parameters
|
||||
:param credentials: Provider credentials
|
||||
:return: Subscription result
|
||||
"""
|
||||
logger.info("Subscribing to trigger %s for plugin %s", trigger_name, self.plugin_id)
|
||||
return Subscription(
|
||||
expire_at=int(time.time()) + 86400, # 24 hours from now
|
||||
metadata={
|
||||
"subscription_id": f"{self.plugin_id}_{trigger_name}_{time.time()}",
|
||||
"webhook_url": f"/triggers/webhook/{self.plugin_id}/{trigger_name}",
|
||||
**subscription_params,
|
||||
},
|
||||
)
|
||||
|
||||
def unsubscribe_trigger(self, trigger_name: str, subscription_metadata: dict, credentials: dict) -> Unsubscription:
|
||||
"""
|
||||
Unsubscribe from a trigger through plugin runtime
|
||||
|
||||
:param trigger_name: Trigger name
|
||||
:param subscription_metadata: Subscription metadata
|
||||
:param credentials: Provider credentials
|
||||
:return: Unsubscription result
|
||||
"""
|
||||
logger.info("Unsubscribing from trigger %s for plugin %s", trigger_name, self.plugin_id)
|
||||
return Unsubscription(success=True, message=f"Successfully unsubscribed from trigger {trigger_name}")
|
||||
|
||||
def handle_webhook(self, webhook_path: str, request_data: dict, credentials: dict) -> dict:
|
||||
"""
|
||||
Handle incoming webhook through plugin runtime
|
||||
|
||||
:param webhook_path: Webhook path
|
||||
:param request_data: Request data
|
||||
:param credentials: Provider credentials
|
||||
:return: Webhook handling result
|
||||
"""
|
||||
logger.info("Handling webhook for path %s for plugin %s", webhook_path, self.plugin_id)
|
||||
return {"success": True, "path": webhook_path, "plugin": self.plugin_id, "data_received": request_data}
|
||||
|
||||
|
||||
__all__ = ["TriggerProviderController"]
|
||||
360
api/core/trigger/trigger_manager.py
Normal file
360
api/core/trigger/trigger_manager.py
Normal file
@ -0,0 +1,360 @@
|
||||
"""
|
||||
Trigger Manager for loading and managing trigger providers and triggers
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from core.trigger.entities import (
|
||||
ProviderConfig,
|
||||
TriggerEntity,
|
||||
)
|
||||
from core.trigger.plugin_trigger import PluginTriggerController
|
||||
from core.trigger.provider import PluginTriggerProviderController
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TriggerManager:
|
||||
"""
|
||||
Manager for trigger providers and triggers
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def list_plugin_trigger_providers(cls, tenant_id: str) -> list[PluginTriggerProviderController]:
|
||||
"""
|
||||
List all plugin trigger providers for a tenant
|
||||
|
||||
:param tenant_id: Tenant ID
|
||||
:return: List of trigger provider controllers
|
||||
"""
|
||||
manager = PluginTriggerController()
|
||||
provider_entities = manager.fetch_trigger_providers(tenant_id)
|
||||
|
||||
controllers = []
|
||||
for provider in provider_entities:
|
||||
try:
|
||||
controller = PluginTriggerProviderController(
|
||||
entity=provider.declaration,
|
||||
plugin_id=provider.plugin_id,
|
||||
plugin_unique_identifier=provider.plugin_unique_identifier,
|
||||
tenant_id=tenant_id,
|
||||
)
|
||||
controllers.append(controller)
|
||||
except Exception as e:
|
||||
logger.exception("Failed to load trigger provider {provider.plugin_id}")
|
||||
continue
|
||||
|
||||
return controllers
|
||||
|
||||
@classmethod
|
||||
def get_plugin_trigger_provider(
|
||||
cls, tenant_id: str, plugin_id: str, provider_name: str
|
||||
) -> Optional[PluginTriggerProviderController]:
|
||||
"""
|
||||
Get a specific plugin trigger provider
|
||||
|
||||
:param tenant_id: Tenant ID
|
||||
:param plugin_id: Plugin ID
|
||||
:param provider_name: Provider name
|
||||
:return: Trigger provider controller or None
|
||||
"""
|
||||
manager = PluginTriggerManager()
|
||||
provider = manager.fetch_trigger_provider(tenant_id, plugin_id, provider_name)
|
||||
|
||||
if not provider:
|
||||
return None
|
||||
|
||||
try:
|
||||
return PluginTriggerProviderController(
|
||||
entity=provider.declaration,
|
||||
plugin_id=provider.plugin_id,
|
||||
plugin_unique_identifier=provider.plugin_unique_identifier,
|
||||
tenant_id=tenant_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("Failed to load trigger provider")
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def list_all_trigger_providers(cls, tenant_id: str) -> list[PluginTriggerProviderController]:
|
||||
"""
|
||||
List all trigger providers (plugin and builtin)
|
||||
|
||||
:param tenant_id: Tenant ID
|
||||
:return: List of all trigger provider controllers
|
||||
"""
|
||||
providers = []
|
||||
|
||||
# Get plugin providers
|
||||
plugin_providers = cls.list_plugin_trigger_providers(tenant_id)
|
||||
providers.extend(plugin_providers)
|
||||
|
||||
# TODO: Add builtin providers when implemented
|
||||
# builtin_providers = cls.list_builtin_trigger_providers(tenant_id)
|
||||
# providers.extend(builtin_providers)
|
||||
|
||||
return providers
|
||||
|
||||
@classmethod
|
||||
def list_triggers_by_provider(cls, tenant_id: str, plugin_id: str, provider_name: str) -> list[TriggerEntity]:
|
||||
"""
|
||||
List all triggers for a specific provider
|
||||
|
||||
:param tenant_id: Tenant ID
|
||||
:param plugin_id: Plugin ID
|
||||
:param provider_name: Provider name
|
||||
:return: List of trigger entities
|
||||
"""
|
||||
provider = cls.get_plugin_trigger_provider(tenant_id, plugin_id, provider_name)
|
||||
|
||||
if not provider:
|
||||
return []
|
||||
|
||||
return provider.get_triggers()
|
||||
|
||||
@classmethod
|
||||
def get_trigger(
|
||||
cls, tenant_id: str, plugin_id: str, provider_name: str, trigger_name: str
|
||||
) -> Optional[TriggerEntity]:
|
||||
"""
|
||||
Get a specific trigger
|
||||
|
||||
:param tenant_id: Tenant ID
|
||||
:param plugin_id: Plugin ID
|
||||
:param provider_name: Provider name
|
||||
:param trigger_name: Trigger name
|
||||
:return: Trigger entity or None
|
||||
"""
|
||||
provider = cls.get_plugin_trigger_provider(tenant_id, plugin_id, provider_name)
|
||||
|
||||
if not provider:
|
||||
return None
|
||||
|
||||
return provider.get_trigger(trigger_name)
|
||||
|
||||
@classmethod
|
||||
def validate_trigger_credentials(
|
||||
cls, tenant_id: str, plugin_id: str, provider_name: str, credentials: dict
|
||||
) -> tuple[bool, str]:
|
||||
"""
|
||||
Validate trigger provider credentials
|
||||
|
||||
:param tenant_id: Tenant ID
|
||||
:param plugin_id: Plugin ID
|
||||
:param provider_name: Provider name
|
||||
:param credentials: Credentials to validate
|
||||
:return: Tuple of (is_valid, error_message)
|
||||
"""
|
||||
provider = cls.get_plugin_trigger_provider(tenant_id, plugin_id, provider_name)
|
||||
|
||||
if not provider:
|
||||
return False, "Provider not found"
|
||||
|
||||
try:
|
||||
provider.validate_credentials(credentials)
|
||||
return True, ""
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
@classmethod
|
||||
def execute_trigger(
|
||||
cls, tenant_id: str, plugin_id: str, provider_name: str, trigger_name: str, parameters: dict, credentials: dict
|
||||
) -> dict:
|
||||
"""
|
||||
Execute a trigger
|
||||
|
||||
:param tenant_id: Tenant ID
|
||||
:param plugin_id: Plugin ID
|
||||
:param provider_name: Provider name
|
||||
:param trigger_name: Trigger name
|
||||
:param parameters: Trigger parameters
|
||||
:param credentials: Provider credentials
|
||||
:return: Trigger execution result
|
||||
"""
|
||||
provider = cls.get_plugin_trigger_provider(tenant_id, plugin_id, provider_name)
|
||||
|
||||
if not provider:
|
||||
raise ValueError(f"Provider {plugin_id}/{provider_name} not found")
|
||||
|
||||
trigger = provider.get_trigger(trigger_name)
|
||||
if not trigger:
|
||||
raise ValueError(f"Trigger {trigger_name} not found in provider {provider_name}")
|
||||
|
||||
return provider.execute_trigger(trigger_name, parameters, credentials)
|
||||
|
||||
@classmethod
|
||||
def subscribe_trigger(
|
||||
cls,
|
||||
tenant_id: str,
|
||||
plugin_id: str,
|
||||
provider_name: str,
|
||||
trigger_name: str,
|
||||
subscription_params: dict,
|
||||
credentials: dict,
|
||||
) -> dict:
|
||||
"""
|
||||
Subscribe to a trigger (e.g., register webhook)
|
||||
|
||||
:param tenant_id: Tenant ID
|
||||
:param plugin_id: Plugin ID
|
||||
:param provider_name: Provider name
|
||||
:param trigger_name: Trigger name
|
||||
:param subscription_params: Subscription parameters
|
||||
:param credentials: Provider credentials
|
||||
:return: Subscription result
|
||||
"""
|
||||
provider = cls.get_plugin_trigger_provider(tenant_id, plugin_id, provider_name)
|
||||
|
||||
if not provider:
|
||||
raise ValueError(f"Provider {plugin_id}/{provider_name} not found")
|
||||
|
||||
return provider.subscribe_trigger(trigger_name, subscription_params, credentials)
|
||||
|
||||
@classmethod
|
||||
def unsubscribe_trigger(
|
||||
cls,
|
||||
tenant_id: str,
|
||||
plugin_id: str,
|
||||
provider_name: str,
|
||||
trigger_name: str,
|
||||
subscription_metadata: dict,
|
||||
credentials: dict,
|
||||
) -> dict:
|
||||
"""
|
||||
Unsubscribe from a trigger
|
||||
|
||||
:param tenant_id: Tenant ID
|
||||
:param plugin_id: Plugin ID
|
||||
:param provider_name: Provider name
|
||||
:param trigger_name: Trigger name
|
||||
:param subscription_metadata: Subscription metadata from subscribe operation
|
||||
:param credentials: Provider credentials
|
||||
:return: Unsubscription result
|
||||
"""
|
||||
provider = cls.get_plugin_trigger_provider(tenant_id, plugin_id, provider_name)
|
||||
|
||||
if not provider:
|
||||
raise ValueError(f"Provider {plugin_id}/{provider_name} not found")
|
||||
|
||||
return provider.unsubscribe_trigger(trigger_name, subscription_metadata, credentials)
|
||||
|
||||
@classmethod
|
||||
def handle_webhook(
|
||||
cls,
|
||||
tenant_id: str,
|
||||
plugin_id: str,
|
||||
provider_name: str,
|
||||
webhook_path: str,
|
||||
request_data: dict,
|
||||
credentials: dict,
|
||||
) -> dict:
|
||||
"""
|
||||
Handle incoming webhook for a trigger
|
||||
|
||||
:param tenant_id: Tenant ID
|
||||
:param plugin_id: Plugin ID
|
||||
:param provider_name: Provider name
|
||||
:param webhook_path: Webhook path
|
||||
:param request_data: Webhook request data
|
||||
:param credentials: Provider credentials
|
||||
:return: Webhook handling result
|
||||
"""
|
||||
provider = cls.get_plugin_trigger_provider(tenant_id, plugin_id, provider_name)
|
||||
|
||||
if not provider:
|
||||
raise ValueError(f"Provider {plugin_id}/{provider_name} not found")
|
||||
|
||||
return provider.handle_webhook(webhook_path, request_data, credentials)
|
||||
|
||||
@classmethod
|
||||
def get_provider_credentials_schema(
|
||||
cls, tenant_id: str, plugin_id: str, provider_name: str
|
||||
) -> list[ProviderConfig]:
|
||||
"""
|
||||
Get provider credentials schema
|
||||
|
||||
:param tenant_id: Tenant ID
|
||||
:param plugin_id: Plugin ID
|
||||
:param provider_name: Provider name
|
||||
:return: List of provider config schemas
|
||||
"""
|
||||
provider = cls.get_plugin_trigger_provider(tenant_id, plugin_id, provider_name)
|
||||
|
||||
if not provider:
|
||||
return []
|
||||
|
||||
return provider.get_credentials_schema()
|
||||
|
||||
@classmethod
|
||||
def get_provider_subscription_schema(
|
||||
cls, tenant_id: str, plugin_id: str, provider_name: str
|
||||
) -> list[ProviderConfig]:
|
||||
"""
|
||||
Get provider subscription schema
|
||||
|
||||
:param tenant_id: Tenant ID
|
||||
:param plugin_id: Plugin ID
|
||||
:param provider_name: Provider name
|
||||
:return: List of subscription config schemas
|
||||
"""
|
||||
provider = cls.get_plugin_trigger_provider(tenant_id, plugin_id, provider_name)
|
||||
|
||||
if not provider:
|
||||
return []
|
||||
|
||||
return provider.get_subscription_schema()
|
||||
|
||||
@classmethod
|
||||
def get_provider_info(cls, tenant_id: str, plugin_id: str, provider_name: str) -> Optional[dict]:
|
||||
"""
|
||||
Get provider information
|
||||
|
||||
:param tenant_id: Tenant ID
|
||||
:param plugin_id: Plugin ID
|
||||
:param provider_name: Provider name
|
||||
:return: Provider info dict or None
|
||||
"""
|
||||
provider = cls.get_plugin_trigger_provider(tenant_id, plugin_id, provider_name)
|
||||
|
||||
if not provider:
|
||||
return None
|
||||
|
||||
return {
|
||||
"plugin_id": plugin_id,
|
||||
"provider_name": provider_name,
|
||||
"identity": provider.entity.identity.model_dump() if provider.entity.identity else {},
|
||||
"credentials_schema": [c.model_dump() for c in provider.entity.credentials_schema],
|
||||
"subscription_schema": [s.model_dump() for s in provider.entity.subscription_schema],
|
||||
"oauth_enabled": provider.entity.oauth_schema is not None,
|
||||
"trigger_count": len(provider.entity.triggers),
|
||||
"triggers": [t.identity.model_dump() for t in provider.entity.triggers],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def list_providers_for_workflow(cls, tenant_id: str) -> list[dict]:
|
||||
"""
|
||||
List trigger providers suitable for workflow usage
|
||||
|
||||
:param tenant_id: Tenant ID
|
||||
:return: List of provider info dicts
|
||||
"""
|
||||
providers = cls.list_all_trigger_providers(tenant_id)
|
||||
|
||||
result = []
|
||||
for provider in providers:
|
||||
info = {
|
||||
"plugin_id": provider.plugin_id,
|
||||
"provider_name": provider.entity.identity.name,
|
||||
"label": provider.entity.identity.label.model_dump(),
|
||||
"description": provider.entity.identity.description.model_dump(),
|
||||
"icon": provider.entity.identity.icon,
|
||||
"trigger_count": len(provider.entity.triggers),
|
||||
}
|
||||
result.append(info)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# Export
|
||||
__all__ = ["TriggerManager"]
|
||||
Reference in New Issue
Block a user