mirror of
https://github.com/langgenius/dify-docs.git
synced 2026-03-27 13:28:32 +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>
667 lines
32 KiB
Plaintext
667 lines
32 KiB
Plaintext
---
|
||
title: "トリガープラグイン"
|
||
---
|
||
|
||
## トリガープラグインとは?
|
||
|
||
トリガー(Trigger) は、Dify v1.10.0で導入された新しいタイプの開始ノードです。Code、Tool、Knowledge Retrievalなどの機能的なノードとは異なり、トリガーの目的は、**サードパーティのイベントをDifyが認識・処理できる入力形式に変換する**ことです。
|
||
|
||

|
||
|
||
例えば、Gmailの`new email`イベントの受信先としてDifyを設定すると、新しいメールを受信するたびに、Gmailはワークフローを起動するためのイベントをDifyに自動的に送信します。しかし、これには課題があります。
|
||
|
||
- Gmailの元のイベント形式は、Difyの入力形式と互換性がありません。
|
||
|
||
- 世界中には何千ものプラットフォームが存在し、それぞれが独自のイベント形式を持っています。
|
||
|
||
そのため、さまざまなプラットフォームや形式から送られてくるこれらのイベントを定義・解析し、Difyが受け入れられる統一された入力形式に変換するために、トリガープラグインが必要となります。
|
||
|
||
## 技術概要
|
||
|
||
Difyのトリガーは、Webで広く採用されているメカニズムである**Webhook**に基づいて実装されています。多くの主流SaaSプラットフォーム(GitHub、Slack、Linearなど)は、包括的な開発者向けドキュメントと共にWebhookをサポートしています。
|
||
|
||
Webhookは、HTTPベースのイベントディスパッチャと理解することができます。**イベント受信アドレスを設定すると、これらのSaaSプラットフォームは、登録したイベントが発生するたびに、ターゲットサーバーにイベントデータを自動的にプッシュします。**
|
||
|
||
異なるプラットフォームからのWebhookイベントを統一的に処理するために、Difyは**サブスクリプション (Subscription)** と **イベント (Event)** という2つのコアコンセプトを定義しています。
|
||
|
||
- **サブスクリプション**: 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を例として、トリガープラグインの開発プロセスを説明します。
|
||
|
||
### Subscription(購読)の作成
|
||
|
||
Webhookの設定方法は、主流のSaaSプラットフォーム間で大きく異なります。
|
||
|
||
- 一部のプラットフォーム(GitHubなど)は、APIベースのWebhook設定をサポートしています。これらのプラットフォームでは、OAuth認証が完了すると、Difyが自動的にWebhookをセットアップできます。
|
||
|
||
- 他のプラットフォーム(Notionなど)は、Webhook設定APIを提供しておらず、ユーザーによる手動認証が必要な場合があります。
|
||
|
||
これらの違いに対応するため、サブスクリプションのプロセスを**サブスクリプションコンストラクタ (Subscription Constructor)** と **サブスクリプション (Subscription)** 自体の2つの部分に分けます。
|
||
|
||
Notionのようなプラットフォームでは、サブスクリプションを作成するために、ユーザーがDifyから提供されたコールバックURLを手動でコピーし、Notionワークスペースに貼り付けてWebhook設定を完了させる必要があります。このプロセスは、Difyインターフェースの**URLを貼り付けて新しいサブスクリプションを作成**オプションに対応します。
|
||
|
||
<img src="/images/trigger_plugin_manual_webhook_setup.PNG" alt="URLを貼り付けて新しいサブスクリプションを作成" width="563" />
|
||
|
||
手動でのURL貼り付けによるサブスクリプション作成を実装するには、`github.yaml`と`github.py`の2つのファイルを変更する必要があります。
|
||
|
||
<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`インターフェースを実装する必要があります。コールバックURLに送信されるすべてのリクエストはこのインターフェースによって処理され、処理されたイベントはデバッグと検証のために**リクエストログ**セクションに表示されます。
|
||
|
||
<img src="/images/trigger_plugin_manual_webhook_setup_config.PNG" alt="手動セットアップ" width="500" />
|
||
|
||
コード内では、`github.yaml`で宣言された`webhook_secret`を`subscription.properties`経由で取得できます。
|
||
|
||
`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イベントのみ)をフィルタリングするには、`issues.yaml`のイベント定義に`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キーによるサブスクリプションの作成
|
||
|
||
OAuthまたはAPIキーを介した自動的なサブスクリプション作成を有効にするには、`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キーでサブスクリプションを作成する際に必要な認証情報(例: GitHubの`access_tokens`)を宣言します。
|
||
|
||
- `oauth_schema` (任意): OAuth経由でのサブスクリプション作成を実装する場合に必要です。定義方法の詳細は、[ツールプラグインにOAuthサポートを追加する](/plugin-dev-en/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>
|
||
|
||
---
|
||
|
||
|
||
これら2つのファイルを変更すると、Difyインターフェースに**APIキーで作成**オプションが表示されます。
|
||
|
||
OAuth経由の自動サブスクリプション作成も同じ`Constructor`クラスで実装できます。`subscription_constructor`の下に`oauth_schema`フィールドを追加することで、OAuth認証を有効にできます。
|
||
|
||
<img src="/images/trigger_plugin_oauth_apikey.png" alt="OAuthとAPIキーのオプション" width="563" />
|
||
|
||
## さらに詳しく
|
||
|
||
トリガープラグイン開発における核心クラスのインターフェース定義と実装方法は以下の通りです。
|
||
|
||
### 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-ja/0222-trigger-plugin.mdx) | [問題を報告する](https://github.com/langgenius/dify-docs/issues/new?template=docs.yml)
|
||
|