dify/api/core/trigger/trigger_manager.py

361 lines
12 KiB
Python

"""
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"]