mirror of
https://github.com/langgenius/dify-docs.git
synced 2026-03-26 13:18:34 +07:00
* draft * draft * draft * refine dev docs * almost * update * remove sys.file and sys.query; add sys.timestamp * update the end node to output * modify the introduction section of variable * fix typo * adjust image size * remove unnecessary list * feedback fix * remove example * feedback fix & add en/ja dev docs * correct description * fix typos * replace UI text * refinements & add zh-ja translation * feedback fixes * fix punctuation * refine heading-reference * typo * adjust casing & remove sys.timestamp from chatflow * Docs tools: 2 succeeded, some failed Rename operation failed: - Lang 'zh': File 'dify-docs/plugin-dev-zh/0211-getting-started-dify-tool.mdx' - Renaming error: Unexpected error: 'str' object has no attribute 'get' - Lang 'zh': File 'dify-docs/plugin-dev-zh/0222-datasource-plugin.mdx' - Skipped (non-compliant): Missing/empty critical frontmatter fields for renaming: dimensions.type.primary, dimensions.type.detail, dimensions.level, standard_title, language (expected for metadata, though not used in filename suffix anymore). - Lang 'zh': File 'dify-docs/plugin-dev-zh/0222-tool-oauth.mdx' - Skipped (non-compliant): Missing/empty critical frontmatter fields for renaming: dimensions.type.primary, dimensions.type.detail, dimensions.level, standard_title, language (expected for metadata, though not used in filename suffix anymore). - Lang 'zh': File 'dify-docs/plugin-dev-zh/0222-trigger-plugin.mdx' - Skipped (non-compliant): Missing/empty critical frontmatter fields for renaming: dimensions.type.primary, dimensions.type.detail, dimensions.level, standard_title, language (expected for metadata, though not used in filename suffix anymore). - Lang 'en': File 'dify-docs/plugin-dev-en/0211-getting-started-dify-tool.mdx' - Renaming error: Unexpected error: 'str' object has no attribute 'get' - Lang 'en': File 'dify-docs/plugin-dev-en/0222-datasource-plugin.mdx' - Skipped (non-compliant): Missing/empty critical frontmatter fields for renaming: dimensions.type.primary, dimensions.type.detail, dimensions.level, standard_title, language (expected for metadata, though not used in filename suffix anymore). - Lang 'en': File 'dify-docs/plugin-dev-en/0222-tool-oauth.mdx' - Skipped (non-compliant): Missing/empty critical frontmatter fields for renaming: dimensions.type.primary, dimensions.type.detail, dimensions.level, standard_title, language (expected for metadata, though not used in filename suffix anymore). - Lang 'en': File 'dify-docs/plugin-dev-en/0222-trigger-plugin.mdx' - Skipped (non-compliant): Missing/empty critical frontmatter fields for renaming: dimensions.type.primary, dimensions.type.detail, dimensions.level, standard_title, language (expected for metadata, though not used in filename suffix anymore). - Lang 'ja': File 'dify-docs/plugin-dev-ja/0211-getting-started-dify-tool.mdx' - Renaming error: Unexpected error: 'str' object has no attribute 'get' - Lang 'ja': File 'dify-docs/plugin-dev-ja/0222-datasource-plugin.mdx' - Skipped (non-compliant): Missing/empty critical frontmatter fields for renaming: dimensions.type.primary, dimensions.type.detail, dimensions.level, standard_title, language (expected for metadata, though not used in filename suffix anymore). - Lang 'ja': File 'dify-docs/plugin-dev-ja/0222-trigger-plugin.mdx' - Skipped (non-compliant): Missing/empty critical frontmatter fields for renaming: dimensions.type.primary, dimensions.type.detail, dimensions.level, standard_title, language (expected for metadata, though not used in filename suffix anymore). --------- Co-authored-by: Riskey <riskey47@dify.ai> Co-authored-by: alterxyz <88554920+alterxyz@users.noreply.github.com>
661 lines
28 KiB
Plaintext
661 lines
28 KiB
Plaintext
---
|
||
title: "触发器插件"
|
||
---
|
||
|
||
## 触发器插件是什么?
|
||
|
||
Dify v1.10.0 引入了触发器(Trigger)类型的开始节点。区别于代码(Code)、工具(Tool)、知识检索(Knowledge Retrieval)等功能性节点,触发器的作用是**将第三方事件转化为 Dify 能接受的入参格式**。
|
||
|
||

|
||
|
||
例如,在 Gmail 中将事件的接收方配置为 Dify 后,每当你收到一封新邮件,Gmail 就会向 Dify 发送一个事件,可用于触发工作流。然而:
|
||
|
||
- Gmail 的原始请求格式不是 Dify 可接受的格式;
|
||
|
||
- 全世界有成千上万的平台,每个平台的格式都不尽相同。
|
||
|
||
因此,我们需要触发器插件来定义和解析这些来自不同平台、不同格式的事件,并将它们统一为 Dify 可接受的标准入参格式。
|
||
|
||
## 技术原理
|
||
|
||
Dify 触发器基于 Webhook 实现。Webhook 是互联网中广泛应用的机制,主流 SaaS 平台(如 Github、Slack、Linear 等)均支持 Webhook 并配有详细的开发文档。
|
||
|
||
Webhook 可理解为基于 HTTP 协议的事件派发中心。**当配置好事件接收地址后,这些 SaaS 平台会在指定事件发生时自动将其推送至目标服务器。**
|
||
|
||
为了统一处理来自不同平台的 Webhook 事件,Dify 抽象出两个核心概念:订阅(Subscription) 和事件(Event)。
|
||
|
||
- **订阅**:如前所述,基于 Webhook 的事件派发有一个前提条件——**需要将 Dify 的网络地址作为目标服务器,配置在第三方平台的开发者后台中。这个配置过程在 Dify 中称为「订阅」。**
|
||
|
||
- **事件**:一个平台可能会发送多种类型的事件,例如 *新邮件*、*邮件删除*、*邮件已读* 等,这些事件均会被推送至预先配置的地址。一个触发器插件可配置多个事件,一个事件对应 Dify 工作流中的一个触发器节点。
|
||
|
||
## 插件开发
|
||
|
||
触发器插件的开发方式与工具(Tool)、数据源(Data Source)、模型(Model)等插件类型一致,均可通过 `dify plugin init` 命令创建开发模板,其文件结构遵循统一的插件格式规范:
|
||
|
||
```
|
||
├── _assets
|
||
│ └── icon.svg
|
||
├── events
|
||
│ └── star
|
||
│ ├── star_created.py
|
||
│ └── star_created.yaml
|
||
├── main.py
|
||
├── manifest.yaml
|
||
├── provider
|
||
│ ├── github.py
|
||
│ └── github.yaml
|
||
├── README.md
|
||
├── PRIVACY.md
|
||
└── requirements.txt
|
||
```
|
||
- `manifest.yaml`:描述插件的基本信息。
|
||
|
||
- `provider` 目录:包含插件供应商的描述信息、创建订阅的代码,以及接收 Webhook 请求后对事件进行分类的代码。
|
||
|
||
- **`events` 目录:包含事件处理和筛选功能的代码,支持在节点端对事件进行本地筛选;每个目录下可创建二级目录,以便对事件进行分组。**
|
||
|
||
<Note>
|
||
在触发器插件中,最低 Dify 版本需设置为 `1.10.0`,SDK 版本需设置为大于等于 `0.6.0`。
|
||
</Note>
|
||
|
||
下面以 GitHub 为例,介绍触发器插件的具体开发方法。
|
||
|
||
### 订阅创建
|
||
|
||
在主流 SaaS 平台中,Webhook 的配置方式差异较大:
|
||
|
||
- 部分平台(如 GitHub)支持通过 API 配置 Webhook。此类平台在 Dify 中完成 OAuth 鉴权后,Dify 即可自动完成 Webhook 配置。
|
||
|
||
- 另一类平台(如 Notion)不仅不提供 Webhook API 配置功能,还要求用户手动完成部分校验工作。
|
||
|
||
因此,我们将订阅过程分为两个部分:Subscription Constructor 和 Subscription。
|
||
|
||
对于 Notion 这类平台,订阅的创建需要用户手动将 Dify 提供的回调地址(Callback URL)复制粘贴到 Notion 后台中,以完成 Webhook 配置。此流程对应 Dify 界面中的 **粘贴 URL 以创建新订阅** 选项。
|
||
|
||
<img src="/images/trigger_plugin_manual_webhook_setup.PNG" alt="Paste URL to Create a New Subscription" width="563" />
|
||
|
||
若要实现通过手动复制 Callback URL 的方式创建订阅,需要修改 `github.yaml` 和 `github.py` 两个文件。
|
||
|
||
<Tabs>
|
||
<Tab title="github.yaml">
|
||
由于 Github Webhook 存在加密机制,解密和校验请求需要使用密钥,因此需在 `github.yaml` 中声明 `webhook_secret`。
|
||
|
||
```YAML
|
||
subscription_schema:
|
||
- name: "webhook_secret"
|
||
type: "secret-input"
|
||
required: false
|
||
label:
|
||
zh_Hans: "Webhook Secret"
|
||
en_US: "Webhook Secret"
|
||
ja_JP: "Webhookシークレット"
|
||
help:
|
||
en_US: "Optional webhook secret for validating GitHub webhook requests"
|
||
ja_JP: "GitHub Webhookリクエストの検証用のオプションのWebhookシークレット"
|
||
zh_Hans: "可选的用于验证 GitHub webhook 请求的 webhook 密钥"
|
||
```
|
||
</Tab>
|
||
<Tab title="github.py">
|
||
|
||
首先,我们需要编写 `dispatch_event` 接口。所有发送到 Callback URL 的请求都会先经过该接口处理,处理后的事件将显示在界面上的 **请求日志** 中,便于调试与验证。
|
||
|
||
<img src="/images/trigger_plugin_manual_webhook_setup_config.PNG" alt="Manual Setup" width="500" />
|
||
|
||
在代码中,可通过 `subscription.properties` 获取在 `github.yaml` 中声明的 `webhook_secret`。
|
||
|
||
`dispatch_event` 方法需要根据请求内容判断该请求对应的事件类型。在以下示例中,事件提取由 `_dispatch_trigger_event` 方法完成。
|
||
|
||
<Tip>
|
||
完整代码示例,请参考 [Dify GitHub 插件代码](https://github.com/langgenius/dify-plugin-sdks/tree/feat/trigger/python/examples/github_trigger)。
|
||
</Tip>
|
||
|
||
```Python
|
||
class GithubTrigger(Trigger):
|
||
"""Handle GitHub webhook event dispatch."""
|
||
|
||
def _dispatch_event(self, subscription: Subscription, request: Request) -> EventDispatch:
|
||
webhook_secret = subscription.properties.get("webhook_secret")
|
||
if webhook_secret:
|
||
self._validate_signature(request=request, webhook_secret=webhook_secret)
|
||
|
||
event_type: str | None = request.headers.get("X-GitHub-Event")
|
||
if not event_type:
|
||
raise TriggerDispatchError("Missing GitHub event type header")
|
||
|
||
payload: Mapping[str, Any] = self._validate_payload(request)
|
||
response = Response(response='{"status": "ok"}', status=200, mimetype="application/json")
|
||
event: str = self._dispatch_trigger_event(event_type=event_type, payload=payload)
|
||
return EventDispatch(events=[event] if event else [], response=response)
|
||
```
|
||
</Tab>
|
||
</Tabs>
|
||
|
||
### 事件处理
|
||
|
||
提取出事件后,需要由相应的事件实现对原始 HTTP 请求进行过滤,并将其转换为 Dify 工作流可接受的入参。
|
||
|
||
以 Issue 事件为例,可分别通过 `events/issues/issues.yaml` 和 `events/issues/issues.py` 来定义事件与实现事件。事件的输出可在 `issues.yaml` 的 `output_schema` 中定义,与工具类型插件相同,遵循 JSON Schema 规范。
|
||
|
||
<Tabs>
|
||
<Tab title="issues.yaml">
|
||
```YAML
|
||
identity:
|
||
name: issues
|
||
author: langgenius
|
||
label:
|
||
en_US: Issues
|
||
zh_Hans: 议题
|
||
ja_JP: イシュー
|
||
description:
|
||
en_US: Unified issues event with actions filter
|
||
zh_Hans: 带 actions 过滤的统一 issues 事件
|
||
ja_JP: アクションフィルタ付きの統合イシューイベント
|
||
output_schema:
|
||
type: object
|
||
properties:
|
||
action:
|
||
type: string
|
||
issue:
|
||
type: object
|
||
description: The issue itself
|
||
extra:
|
||
python:
|
||
source: events/issues/issues.py
|
||
```
|
||
</Tab>
|
||
<Tab title="issues.py">
|
||
```Python
|
||
from collections.abc import Mapping
|
||
from typing import Any
|
||
|
||
from werkzeug import Request
|
||
|
||
from dify_plugin.entities.trigger import Variables
|
||
from dify_plugin.errors.trigger import EventIgnoreError
|
||
from dify_plugin.interfaces.trigger import Event
|
||
|
||
class IssuesUnifiedEvent(Event):
|
||
"""Unified Issues event. Filters by actions and common issue attributes."""
|
||
|
||
def _on_event(self, request: Request, parameters: Mapping[str, Any], payload: Mapping[str, Any]) -> Variables:
|
||
payload = request.get_json()
|
||
if not payload:
|
||
raise ValueError("No payload received")
|
||
|
||
allowed_actions = parameters.get("actions") or []
|
||
action = payload.get("action")
|
||
if allowed_actions and action not in allowed_actions:
|
||
raise EventIgnoreError()
|
||
|
||
issue = payload.get("issue")
|
||
if not isinstance(issue, Mapping):
|
||
raise ValueError("No issue in payload")
|
||
|
||
return Variables(variables={**payload})
|
||
```
|
||
</Tab>
|
||
</Tabs>
|
||
|
||
### 事件过滤
|
||
|
||
若希望插件能够筛选掉部分事件,例如只关注具有某个特定标签的 Issue 事件,可在事件定义中为 Issue 事件添加 `parameters`。在 `_on_event` 方法中,通过抛出 `EventIgnoreError` 异常,即可在实际运行中根据配置的参数过滤掉不符合条件的事件。
|
||
|
||
<Tabs>
|
||
<Tab title="issues.yaml">
|
||
```YAML
|
||
parameters:
|
||
- name: added_label
|
||
label:
|
||
en_US: Added Label
|
||
zh_Hans: 添加的标签
|
||
ja_JP: 追加されたラベル
|
||
type: string
|
||
required: false
|
||
description:
|
||
en_US: "Only trigger if these specific labels were added (e.g., critical, priority-high, security, comma-separated). Leave empty to trigger for any label addition."
|
||
zh_Hans: "仅当添加了这些特定标签时触发(例如:critical, priority-high, security,逗号分隔)。留空则对任何标签添加触发。"
|
||
ja_JP: "これらの特定のラベルが追加された場合のみトリガー(例: critical, priority-high, security,カンマ区切り)。空の場合は任意のラベル追加でトリガー。"
|
||
```
|
||
</Tab>
|
||
<Tab title="issues.py">
|
||
```Python
|
||
def _check_added_label(self, payload: Mapping[str, Any], added_label_param: str | None) -> None:
|
||
"""Check if the added label matches the allowed labels"""
|
||
if not added_label_param:
|
||
return
|
||
|
||
allowed_labels = [label.strip() for label in added_label_param.split(",") if label.strip()]
|
||
if not allowed_labels:
|
||
return
|
||
|
||
# The payload contains the label that was added
|
||
label = payload.get("label", {})
|
||
label_name = label.get("name", "")
|
||
|
||
if label_name not in allowed_labels:
|
||
raise EventIgnoreError()
|
||
|
||
def _on_event(self, request: Request, parameters: Mapping[str, Any], payload: Mapping[str, Any]) -> Variables:
|
||
# ...
|
||
# Apply all filters
|
||
self._check_added_label(payload, parameters.get("added_label"))
|
||
|
||
return Variables(variables={**payload})
|
||
```
|
||
</Tab>
|
||
</Tabs>
|
||
|
||
### 通过 OAuth 或 API key 创建订阅
|
||
|
||
若要实现通过 OAuth 或 API key 自动创建订阅,同样需要修改 `github.yaml` 和 `github.py` 两个文件。
|
||
|
||
<Tabs>
|
||
<Tab title="github.yaml">
|
||
在 `github.yaml` 中,添加如下字段。
|
||
|
||
```YAML
|
||
subscription_constructor:
|
||
parameters:
|
||
- name: "repository"
|
||
label:
|
||
en_US: "Repository"
|
||
zh_Hans: "仓库"
|
||
ja_JP: "リポジトリ"
|
||
type: "dynamic-select"
|
||
required: true
|
||
placeholder:
|
||
en_US: "owner/repo"
|
||
zh_Hans: "owner/repo"
|
||
ja_JP: "owner/repo"
|
||
help:
|
||
en_US: "GitHub repository in format owner/repo (e.g., microsoft/vscode)"
|
||
zh_Hans: "GitHub 仓库,格式为 owner/repo(例如:microsoft/vscode)"
|
||
ja_JP: "GitHubリポジトリは owner/repo 形式で入力してください(例: microsoft/vscode)"
|
||
credentials_schema:
|
||
access_tokens:
|
||
help:
|
||
en_US: Get your Access Tokens from GitHub
|
||
ja_JP: GitHub からアクセストークンを取得してください
|
||
zh_Hans: 从 GitHub 获取您的 Access Tokens
|
||
label:
|
||
en_US: Access Tokens
|
||
ja_JP: アクセストークン
|
||
zh_Hans: Access Tokens
|
||
placeholder:
|
||
en_US: Please input your GitHub Access Tokens
|
||
ja_JP: GitHub のアクセストークンを入力してください
|
||
zh_Hans: 请输入你的 GitHub Access Tokens
|
||
required: true
|
||
type: secret-input
|
||
url: https://github.com/settings/tokens?type=beta
|
||
extra:
|
||
python:
|
||
source: provider/github.py
|
||
```
|
||
`subscription_constructor` 是 Dify 抽象出的一个概念,用于定义如何构建订阅,包含以下字段:
|
||
|
||
- `parameters`(可选):用于定义创建订阅时所需的参数,例如想要订阅的事件类型或目标 GitHub 仓库等。
|
||
|
||
- `credentials_schema`(可选):通过 API key 创建订阅时,必须填写该字段以声明所需的密钥,例如在 GitHub 中定义 `access_tokens`。
|
||
|
||
- `oauth_schema`(可选):通过 OAuth 创建订阅时,必须填写该字段以启用 OAuth 鉴权。具体定义方式,可参考 [为插件添加 OAuth 支持](/plugin-dev-zh/0222-tool-oauth)。
|
||
</Tab>
|
||
<Tab title="github.py">
|
||
在 `github.py` 中创建一个 `Constructor` 类,以实现自动化订阅逻辑。
|
||
|
||
```Python
|
||
class GithubSubscriptionConstructor(TriggerSubscriptionConstructor):
|
||
"""Manage GitHub trigger subscriptions."""
|
||
def _validate_api_key(self, credentials: Mapping[str, Any]) -> None:
|
||
# ...
|
||
|
||
def _create_subscription(
|
||
self,
|
||
endpoint: str,
|
||
parameters: Mapping[str, Any],
|
||
credentials: Mapping[str, Any],
|
||
credential_type: CredentialType,
|
||
) -> Subscription:
|
||
repository = parameters.get("repository")
|
||
if not repository:
|
||
raise ValueError("repository is required (format: owner/repo)")
|
||
|
||
try:
|
||
owner, repo = repository.split("/")
|
||
except ValueError:
|
||
raise ValueError("repository must be in format 'owner/repo'") from None
|
||
|
||
events: list[str] = parameters.get("events", [])
|
||
webhook_secret = uuid.uuid4().hex
|
||
url = f"https://api.github.com/repos/{owner}/{repo}/hooks"
|
||
headers = {
|
||
"Authorization": f"Bearer {credentials.get('access_tokens')}",
|
||
"Accept": "application/vnd.github+json",
|
||
}
|
||
|
||
webhook_data = {
|
||
"name": "web",
|
||
"active": True,
|
||
"events": events,
|
||
"config": {"url": endpoint, "content_type": "json", "insecure_ssl": "0", "secret": webhook_secret},
|
||
}
|
||
|
||
try:
|
||
response = requests.post(url, json=webhook_data, headers=headers, timeout=10)
|
||
except requests.RequestException as exc:
|
||
raise SubscriptionError(f"Network error while creating webhook: {exc}", error_code="NETWORK_ERROR") from exc
|
||
|
||
if response.status_code == 201:
|
||
webhook = response.json()
|
||
return Subscription(
|
||
expires_at=int(time.time()) + self._WEBHOOK_TTL,
|
||
endpoint=endpoint,
|
||
parameters=parameters,
|
||
properties={
|
||
"external_id": str(webhook["id"]),
|
||
"repository": repository,
|
||
"events": events,
|
||
"webhook_secret": webhook_secret,
|
||
"active": webhook.get("active", True),
|
||
},
|
||
)
|
||
|
||
response_data: dict[str, Any] = response.json() if response.content else {}
|
||
error_msg = response_data.get("message", "Unknown error")
|
||
error_details = response_data.get("errors", [])
|
||
detailed_error = f"Failed to create GitHub webhook: {error_msg}"
|
||
if error_details:
|
||
detailed_error += f" Details: {error_details}"
|
||
|
||
raise SubscriptionError(
|
||
detailed_error,
|
||
error_code="WEBHOOK_CREATION_FAILED",
|
||
external_response=response_data,
|
||
)
|
||
```
|
||
</Tab>
|
||
</Tabs>
|
||
|
||
---
|
||
|
||
完成以上两个文件的配置后,你将在 Dify 界面中看到 **通过 API Key 创建** 选项。
|
||
|
||
通过 OAuth 的订阅自动创建也可以在 `Constructor` 中实现:在 `subscription_constructor` 中添加 `oauth_schema` 字段,即可启用 OAuth 认证方式。
|
||
|
||

|
||
|
||
## 探索更多
|
||
|
||
触发器插件开发中核心类的接口定义和实现方法如下。
|
||
|
||
### Trigger
|
||
|
||
```Python
|
||
class Trigger(ABC):
|
||
@abstractmethod
|
||
def _dispatch_event(self, subscription: Subscription, request: Request) -> EventDispatch:
|
||
"""
|
||
Internal method to implement event dispatch logic.
|
||
|
||
Subclasses must override this method to handle incoming webhook events.
|
||
|
||
Implementation checklist:
|
||
1. Validate the webhook request:
|
||
- Check signature/HMAC using properties when you create the subscription from subscription.properties
|
||
- Verify request is from expected source
|
||
2. Extract event information:
|
||
- Parse event type from headers or body
|
||
- Extract relevant payload data
|
||
3. Return EventDispatch with:
|
||
- events: List of Event names to invoke (can be single or multiple)
|
||
- response: Appropriate HTTP response for the webhook
|
||
|
||
Args:
|
||
subscription: The Subscription object with endpoint and properties fields
|
||
request: Incoming webhook HTTP request
|
||
|
||
Returns:
|
||
EventDispatch: Event dispatch routing information
|
||
|
||
Raises:
|
||
TriggerValidationError: For security validation failures
|
||
TriggerDispatchError: For parsing or routing errors
|
||
"""
|
||
raise NotImplementedError("This plugin should implement `_dispatch_event` method to enable event dispatch")
|
||
|
||
```
|
||
|
||
### TriggerSubscriptionConstructor
|
||
|
||
```Python
|
||
class TriggerSubscriptionConstructor(ABC, OAuthProviderProtocol):
|
||
# OPTIONAL
|
||
def _validate_api_key(self, credentials: Mapping[str, Any]) -> None:
|
||
raise NotImplementedError(
|
||
"This plugin should implement `_validate_api_key` method to enable credentials validation"
|
||
)
|
||
|
||
# OPTIONAL
|
||
def _oauth_get_authorization_url(self, redirect_uri: str, system_credentials: Mapping[str, Any]) -> str:
|
||
raise NotImplementedError(
|
||
"The trigger you are using does not support OAuth, please implement `_oauth_get_authorization_url` method"
|
||
)
|
||
|
||
# OPTIONAL
|
||
def _oauth_get_credentials(
|
||
self, redirect_uri: str, system_credentials: Mapping[str, Any], request: Request
|
||
) -> TriggerOAuthCredentials:
|
||
raise NotImplementedError(
|
||
"The trigger you are using does not support OAuth, please implement `_oauth_get_credentials` method"
|
||
)
|
||
|
||
# OPTIONAL
|
||
def _oauth_refresh_credentials(
|
||
self, redirect_uri: str, system_credentials: Mapping[str, Any], credentials: Mapping[str, Any]
|
||
) -> OAuthCredentials:
|
||
raise NotImplementedError(
|
||
"The trigger you are using does not support OAuth, please implement `_oauth_refresh_credentials` method"
|
||
)
|
||
|
||
@abstractmethod
|
||
def _create_subscription(
|
||
self,
|
||
endpoint: str,
|
||
parameters: Mapping[str, Any],
|
||
credentials: Mapping[str, Any],
|
||
credential_type: CredentialType,
|
||
) -> Subscription:
|
||
"""
|
||
Internal method to implement subscription logic.
|
||
|
||
Subclasses must override this method to handle subscription creation.
|
||
|
||
Implementation checklist:
|
||
1. Use the endpoint parameter provided by Dify
|
||
2. Register webhook with external service using their API
|
||
3. Store all necessary information in Subscription.properties for future operations(e.g., dispatch_event)
|
||
4. Return Subscription with:
|
||
- expires_at: Set appropriate expiration time
|
||
- endpoint: The webhook endpoint URL allocated by Dify for receiving events, same with the endpoint parameter
|
||
- parameters: The parameters of the subscription
|
||
- properties: All configuration and external IDs
|
||
|
||
Args:
|
||
endpoint: The webhook endpoint URL allocated by Dify for receiving events
|
||
parameters: Subscription creation parameters
|
||
credentials: Authentication credentials
|
||
credential_type: The type of the credentials, e.g., "api-key", "oauth2", "unauthorized"
|
||
|
||
Returns:
|
||
Subscription: Subscription details with metadata for future operations
|
||
|
||
Raises:
|
||
SubscriptionError: For operational failures (API errors, invalid credentials)
|
||
ValueError: For programming errors (missing required params)
|
||
"""
|
||
raise NotImplementedError(
|
||
"This plugin should implement `_create_subscription` method to enable event subscription"
|
||
)
|
||
|
||
@abstractmethod
|
||
def _delete_subscription(
|
||
self, subscription: Subscription, credentials: Mapping[str, Any], credential_type: CredentialType
|
||
) -> UnsubscribeResult:
|
||
"""
|
||
Internal method to implement unsubscription logic.
|
||
|
||
Subclasses must override this method to handle subscription removal.
|
||
|
||
Implementation guidelines:
|
||
1. Extract necessary IDs from subscription.properties (e.g., external_id)
|
||
2. Use credentials and credential_type to call external service API to delete the webhook
|
||
3. Handle common errors (not found, unauthorized, etc.)
|
||
4. Always return UnsubscribeResult with detailed status
|
||
5. Never raise exceptions for operational failures - use UnsubscribeResult.success=False
|
||
|
||
Args:
|
||
subscription: The Subscription object with endpoint and properties fields
|
||
|
||
Returns:
|
||
UnsubscribeResult: Always returns result, never raises for operational failures
|
||
"""
|
||
raise NotImplementedError(
|
||
"This plugin should implement `_delete_subscription` method to enable event unsubscription"
|
||
)
|
||
|
||
@abstractmethod
|
||
def _refresh_subscription(
|
||
self, subscription: Subscription, credentials: Mapping[str, Any], credential_type: CredentialType
|
||
) -> Subscription:
|
||
"""
|
||
Internal method to implement subscription refresh logic.
|
||
|
||
Subclasses must override this method to handle simple expiration extension.
|
||
|
||
Implementation patterns:
|
||
1. For webhooks without expiration (e.g., GitHub):
|
||
- Update the Subscription.expires_at=-1 then Dify will never call this method again
|
||
|
||
2. For lease-based subscriptions (e.g., Microsoft Graph):
|
||
- Use the information in Subscription.properties to call service's lease renewal API if available
|
||
- Handle renewal limits (some services limit renewal count)
|
||
- Update the Subscription.properties and Subscription.expires_at for next time renewal if needed
|
||
|
||
Args:
|
||
subscription: Current subscription with properties
|
||
credential_type: The type of the credentials, e.g., "api-key", "oauth2", "unauthorized"
|
||
credentials: Current authentication credentials from credentials_schema.
|
||
For API key auth, according to `credentials_schema` defined in the YAML.
|
||
For OAuth auth, according to `oauth_schema.credentials_schema` defined in the YAML.
|
||
For unauthorized auth, there is no credentials.
|
||
|
||
Returns:
|
||
Subscription: Same subscription with extended expiration
|
||
or new properties and expires_at for next time renewal
|
||
|
||
Raises:
|
||
SubscriptionError: For operational failures (API errors, invalid credentials)
|
||
"""
|
||
raise NotImplementedError("This plugin should implement `_refresh` method to enable subscription refresh")
|
||
|
||
# OPTIONAL
|
||
def _fetch_parameter_options(
|
||
self, parameter: str, credentials: Mapping[str, Any], credential_type: CredentialType
|
||
) -> list[ParameterOption]:
|
||
"""
|
||
Fetch the parameter options of the trigger.
|
||
|
||
Implementation guidelines:
|
||
When you need to fetch parameter options from an external service, use the credentials
|
||
and credential_type to call the external service API, then return the options to Dify
|
||
for user selection.
|
||
|
||
Args:
|
||
parameter: The parameter name for which to fetch options
|
||
credentials: Authentication credentials for the external service
|
||
credential_type: The type of credentials (e.g., "api-key", "oauth2", "unauthorized")
|
||
|
||
Returns:
|
||
list[ParameterOption]: A list of available options for the parameter
|
||
|
||
Examples:
|
||
GitHub Repositories:
|
||
>>> result = provider.fetch_parameter_options(parameter="repository")
|
||
>>> print(result) # [ParameterOption(label="owner/repo", value="owner/repo")]
|
||
|
||
Slack Channels:
|
||
>>> result = provider.fetch_parameter_options(parameter="channel")
|
||
>>> print(result)
|
||
```
|
||
|
||
### Event
|
||
|
||
```Python
|
||
class Event(ABC):
|
||
@abstractmethod
|
||
def _on_event(self, request: Request, parameters: Mapping[str, Any], payload: Mapping[str, Any]) -> Variables:
|
||
"""
|
||
Transform the incoming webhook request into structured Variables.
|
||
|
||
This method should:
|
||
1. Parse the webhook payload from the request
|
||
2. Apply filtering logic based on parameters
|
||
3. Extract relevant data matching the output_schema
|
||
4. Return a structured Variables object
|
||
|
||
Args:
|
||
request: The incoming webhook HTTP request containing the raw payload.
|
||
Use request.get_json() to parse JSON body.
|
||
parameters: User-configured parameters for filtering and transformation
|
||
(e.g., label filters, regex patterns, threshold values).
|
||
These come from the subscription configuration.
|
||
payload: The decoded payload from previous step `Trigger.dispatch_event`.
|
||
It will be delivered into `_on_event` method.
|
||
Returns:
|
||
Variables: Structured variables matching the output_schema
|
||
defined in the event's YAML configuration.
|
||
|
||
Raises:
|
||
EventIgnoreError: When the event should be filtered out based on parameters
|
||
ValueError: When the payload is invalid or missing required fields
|
||
|
||
Example:
|
||
>>> def _on_event(self, request, parameters):
|
||
... payload = request.get_json()
|
||
...
|
||
... # Apply filters
|
||
... if not self._matches_filters(payload, parameters):
|
||
... raise EventIgnoreError()
|
||
...
|
||
... # Transform data
|
||
... return Variables(variables={
|
||
... "title": payload["issue"]["title"],
|
||
... "author": payload["issue"]["user"]["login"],
|
||
... "url": payload["issue"]["html_url"],
|
||
... })
|
||
"""
|
||
|
||
def _fetch_parameter_options(self, parameter: str) -> list[ParameterOption]:
|
||
"""
|
||
Fetch the parameter options of the trigger.
|
||
|
||
To be implemented by subclasses.
|
||
|
||
Also, it's optional to implement, that's why it's not an abstract method.
|
||
"""
|
||
raise NotImplementedError(
|
||
"This plugin should implement `_fetch_parameter_options` method to enable dynamic select parameter"
|
||
)
|
||
```
|
||
|
||
{/*
|
||
Contributing Section
|
||
DO NOT edit this section!
|
||
It will be automatically generated by the script.
|
||
*/}
|
||
|
||
---
|
||
|
||
[编辑此页面](https://github.com/langgenius/dify-docs/edit/main/plugin-dev-zh/0222-trigger-plugin.mdx) | [提交问题](https://github.com/langgenius/dify-docs/issues/new?template=docs.yml)
|
||
|