Compare commits

...

6 Commits

Author SHA1 Message Date
-LAN-
a30a64d51b Scope tool configuration test patches 2026-03-14 19:29:05 +08:00
-LAN-
34ef10c818 Keep direct provider_id consumers unchanged 2026-03-14 19:14:30 +08:00
-LAN-
26fedca865 Keep trigger provider handling in the node 2026-03-14 19:14:30 +08:00
-LAN-
2fd4e9e259 Restore trigger provider metadata on start events 2026-03-14 19:14:30 +08:00
-LAN-
9e8a4c8a71 Keep dify_graph node base generic 2026-03-14 19:14:30 +08:00
-LAN-
238497b7ab Move trigger workflow nodes into core workflow 2026-03-14 19:14:29 +08:00
42 changed files with 214 additions and 82 deletions

View File

@@ -30,6 +30,7 @@ from core.app.entities.queue_entities import (
QueueWorkflowSucceededEvent, QueueWorkflowSucceededEvent,
) )
from core.workflow.node_factory import DifyNodeFactory from core.workflow.node_factory import DifyNodeFactory
from core.workflow.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING
from core.workflow.workflow_entry import WorkflowEntry from core.workflow.workflow_entry import WorkflowEntry
from dify_graph.entities import GraphInitParams from dify_graph.entities import GraphInitParams
from dify_graph.entities.graph_config import NodeConfigDictAdapter from dify_graph.entities.graph_config import NodeConfigDictAdapter
@@ -63,7 +64,6 @@ from dify_graph.graph_events import (
NodeRunSucceededEvent, NodeRunSucceededEvent,
) )
from dify_graph.graph_events.graph import GraphRunAbortedEvent from dify_graph.graph_events.graph import GraphRunAbortedEvent
from dify_graph.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING
from dify_graph.runtime import GraphRuntimeState, VariablePool from dify_graph.runtime import GraphRuntimeState, VariablePool
from dify_graph.system_variable import SystemVariable from dify_graph.system_variable import SystemVariable
from dify_graph.variable_loader import DUMMY_VARIABLE_LOADER, VariableLoader, load_into_variable_pool from dify_graph.variable_loader import DUMMY_VARIABLE_LOADER, VariableLoader, load_into_variable_pool

View File

@@ -316,7 +316,7 @@ class QueueNodeStartedEvent(AppQueueEvent):
start_at: datetime start_at: datetime
agent_strategy: AgentNodeStrategyInit | None = None agent_strategy: AgentNodeStrategyInit | None = None
# FIXME(-LAN-): only for ToolNode, need to refactor # Legacy provider fields kept for existing start-event consumers.
provider_type: str # should be a core.tools.entities.tool_entities.ToolProviderType provider_type: str # should be a core.tools.entities.tool_entities.ToolProviderType
provider_id: str provider_id: str

View File

@@ -19,10 +19,10 @@ from core.trigger.debug.events import (
build_plugin_pool_key, build_plugin_pool_key,
build_webhook_pool_key, build_webhook_pool_key,
) )
from core.workflow.nodes.trigger_plugin.entities import TriggerEventNodeData
from core.workflow.nodes.trigger_schedule.entities import ScheduleConfig
from dify_graph.entities.graph_config import NodeConfigDict from dify_graph.entities.graph_config import NodeConfigDict
from dify_graph.enums import NodeType from dify_graph.enums import NodeType
from dify_graph.nodes.trigger_plugin.entities import TriggerEventNodeData
from dify_graph.nodes.trigger_schedule.entities import ScheduleConfig
from extensions.ext_redis import redis_client from extensions.ext_redis import redis_client
from libs.datetime_utils import ensure_naive_utc, naive_utc_now from libs.datetime_utils import ensure_naive_utc, naive_utc_now
from libs.schedule_utils import calculate_next_run_at from libs.schedule_utils import calculate_next_run_at

View File

@@ -22,6 +22,7 @@ from core.rag.retrieval.dataset_retrieval import DatasetRetrieval
from core.rag.summary_index.summary_index import SummaryIndex from core.rag.summary_index.summary_index import SummaryIndex
from core.repositories.human_input_repository import HumanInputFormRepositoryImpl from core.repositories.human_input_repository import HumanInputFormRepositoryImpl
from core.tools.tool_file_manager import ToolFileManager from core.tools.tool_file_manager import ToolFileManager
from core.workflow.nodes.node_mapping import LATEST_VERSION, NODE_TYPE_CLASSES_MAPPING
from dify_graph.entities.base_node_data import BaseNodeData from dify_graph.entities.base_node_data import BaseNodeData
from dify_graph.entities.graph_config import NodeConfigDict, NodeConfigDictAdapter from dify_graph.entities.graph_config import NodeConfigDict, NodeConfigDictAdapter
from dify_graph.entities.graph_init_params import DIFY_RUN_CONTEXT_KEY from dify_graph.entities.graph_init_params import DIFY_RUN_CONTEXT_KEY
@@ -39,7 +40,6 @@ from dify_graph.nodes.document_extractor import UnstructuredApiConfig
from dify_graph.nodes.http_request import build_http_request_config from dify_graph.nodes.http_request import build_http_request_config
from dify_graph.nodes.llm.entities import LLMNodeData from dify_graph.nodes.llm.entities import LLMNodeData
from dify_graph.nodes.llm.exc import LLMModeRequiredError, ModelNotExistError from dify_graph.nodes.llm.exc import LLMModeRequiredError, ModelNotExistError
from dify_graph.nodes.node_mapping import LATEST_VERSION, NODE_TYPE_CLASSES_MAPPING
from dify_graph.nodes.parameter_extractor.entities import ParameterExtractorNodeData from dify_graph.nodes.parameter_extractor.entities import ParameterExtractorNodeData
from dify_graph.nodes.question_classifier.entities import QuestionClassifierNodeData from dify_graph.nodes.question_classifier.entities import QuestionClassifierNodeData
from dify_graph.nodes.template_transform.template_renderer import ( from dify_graph.nodes.template_transform.template_renderer import (

View File

@@ -0,0 +1 @@
"""Core-owned workflow node packages."""

View File

@@ -0,0 +1,30 @@
"""Node mapping for workflow execution.
`core.workflow` owns the trigger node implementations, while the remaining node
implementations still live under `dify_graph`. This module imports the
core-owned node packages first, then asks the shared `Node` registry to load the
rest of the workflow nodes from `dify_graph`.
"""
import importlib
import pkgutil
from collections.abc import Mapping
from dify_graph.enums import NodeType
from dify_graph.nodes.base.node import Node
LATEST_VERSION = "latest"
def _register_core_workflow_nodes() -> None:
import core.workflow.nodes as workflow_nodes_pkg
for _, modname, _ in pkgutil.walk_packages(workflow_nodes_pkg.__path__, workflow_nodes_pkg.__name__ + "."):
if modname == "core.workflow.nodes.node_mapping":
continue
importlib.import_module(modname)
_register_core_workflow_nodes()
NODE_TYPE_CLASSES_MAPPING: Mapping[NodeType, Mapping[str, type[Node]]] = Node.get_node_type_classes_mapping()

View File

@@ -6,7 +6,8 @@ from pydantic import BaseModel, Field, ValidationInfo, field_validator
from core.trigger.entities.entities import EventParameter from core.trigger.entities.entities import EventParameter
from dify_graph.entities.base_node_data import BaseNodeData from dify_graph.entities.base_node_data import BaseNodeData
from dify_graph.enums import NodeType from dify_graph.enums import NodeType
from dify_graph.nodes.trigger_plugin.exc import TriggerEventParameterError
from .exc import TriggerEventParameterError
class TriggerEventNodeData(BaseNodeData): class TriggerEventNodeData(BaseNodeData):

View File

@@ -3,6 +3,7 @@ from collections.abc import Mapping
from dify_graph.constants import SYSTEM_VARIABLE_NODE_ID from dify_graph.constants import SYSTEM_VARIABLE_NODE_ID
from dify_graph.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionStatus from dify_graph.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionStatus
from dify_graph.enums import NodeExecutionType, NodeType from dify_graph.enums import NodeExecutionType, NodeType
from dify_graph.graph_events import NodeRunStartedEvent
from dify_graph.node_events import NodeRunResult from dify_graph.node_events import NodeRunResult
from dify_graph.nodes.base.node import Node from dify_graph.nodes.base.node import Node
@@ -32,6 +33,11 @@ class TriggerEventNode(Node[TriggerEventNodeData]):
def version(cls) -> str: def version(cls) -> str:
return "1" return "1"
def customize_start_event(self, event: NodeRunStartedEvent) -> None:
provider_id = self.node_data.provider_id
event.provider_id = provider_id
event.extras["provider_id"] = provider_id
def _run(self) -> NodeRunResult: def _run(self) -> NodeRunResult:
""" """
Run the plugin trigger node. Run the plugin trigger node.

View File

@@ -0,0 +1,3 @@
from .trigger_schedule_node import TriggerScheduleNode
__all__ = ["TriggerScheduleNode"]

View File

@@ -5,7 +5,8 @@ from dify_graph.entities.workflow_node_execution import WorkflowNodeExecutionSta
from dify_graph.enums import NodeExecutionType, NodeType from dify_graph.enums import NodeExecutionType, NodeType
from dify_graph.node_events import NodeRunResult from dify_graph.node_events import NodeRunResult
from dify_graph.nodes.base.node import Node from dify_graph.nodes.base.node import Node
from dify_graph.nodes.trigger_schedule.entities import TriggerScheduleNodeData
from .entities import TriggerScheduleNodeData
class TriggerScheduleNode(Node[TriggerScheduleNodeData]): class TriggerScheduleNode(Node[TriggerScheduleNodeData]):

View File

@@ -9,6 +9,7 @@ from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom, build_di
from core.app.workflow.layers.llm_quota import LLMQuotaLayer from core.app.workflow.layers.llm_quota import LLMQuotaLayer
from core.app.workflow.layers.observability import ObservabilityLayer from core.app.workflow.layers.observability import ObservabilityLayer
from core.workflow.node_factory import DifyNodeFactory from core.workflow.node_factory import DifyNodeFactory
from core.workflow.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING
from dify_graph.constants import ENVIRONMENT_VARIABLE_NODE_ID from dify_graph.constants import ENVIRONMENT_VARIABLE_NODE_ID
from dify_graph.entities import GraphInitParams from dify_graph.entities import GraphInitParams
from dify_graph.entities.graph_config import NodeConfigDictAdapter from dify_graph.entities.graph_config import NodeConfigDictAdapter
@@ -23,7 +24,6 @@ from dify_graph.graph_engine.protocols.command_channel import CommandChannel
from dify_graph.graph_events import GraphEngineEvent, GraphNodeEventBase, GraphRunFailedEvent from dify_graph.graph_events import GraphEngineEvent, GraphNodeEventBase, GraphRunFailedEvent
from dify_graph.nodes import NodeType from dify_graph.nodes import NodeType
from dify_graph.nodes.base.node import Node from dify_graph.nodes.base.node import Node
from dify_graph.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING
from dify_graph.runtime import ChildGraphNotFoundError, GraphRuntimeState, VariablePool from dify_graph.runtime import ChildGraphNotFoundError, GraphRuntimeState, VariablePool
from dify_graph.system_variable import SystemVariable from dify_graph.system_variable import SystemVariable
from dify_graph.variable_loader import DUMMY_VARIABLE_LOADER, VariableLoader, load_into_variable_pool from dify_graph.variable_loader import DUMMY_VARIABLE_LOADER, VariableLoader, load_into_variable_pool

View File

@@ -15,8 +15,9 @@ class NodeRunStartedEvent(GraphNodeEventBase):
predecessor_node_id: str | None = None predecessor_node_id: str | None = None
agent_strategy: AgentNodeStrategyInit | None = None agent_strategy: AgentNodeStrategyInit | None = None
start_at: datetime = Field(..., description="node start time") start_at: datetime = Field(..., description="node start time")
extras: dict[str, object] = Field(default_factory=dict)
# FIXME(-LAN-): only for ToolNode # Legacy provider fields kept for existing start-event consumers.
provider_type: str = "" provider_type: str = ""
provider_id: str = "" provider_id: str = ""

View File

@@ -179,7 +179,7 @@ class Node(Generic[NodeDataT]):
# Skip base class itself # Skip base class itself
if cls is Node: if cls is Node:
return return
# Only register production node implementations defined under dify_graph.nodes.* # Only register production node implementations defined under dify_graph.nodes.*.
# This prevents test helper subclasses from polluting the global registry and # This prevents test helper subclasses from polluting the global registry and
# accidentally overriding real node types (e.g., a test Answer node). # accidentally overriding real node types (e.g., a test Answer node).
module_name = getattr(cls, "__module__", "") module_name = getattr(cls, "__module__", "")
@@ -273,6 +273,10 @@ class Node(Generic[NodeDataT]):
"""Optional hook for subclasses requiring extra initialization.""" """Optional hook for subclasses requiring extra initialization."""
return return
def customize_start_event(self, event: NodeRunStartedEvent) -> None:
"""Optional hook for subclasses to attach start-event metadata or extras."""
return
@property @property
def graph_init_params(self) -> GraphInitParams: def graph_init_params(self) -> GraphInitParams:
return self._graph_init_params return self._graph_init_params
@@ -379,12 +383,6 @@ class Node(Generic[NodeDataT]):
start_event.provider_id = f"{plugin_id}/{provider_name}" start_event.provider_id = f"{plugin_id}/{provider_name}"
start_event.provider_type = getattr(self.node_data, "provider_type", "") start_event.provider_type = getattr(self.node_data, "provider_type", "")
from dify_graph.nodes.trigger_plugin.trigger_event_node import TriggerEventNode
if isinstance(self, TriggerEventNode):
start_event.provider_id = getattr(self.node_data, "provider_id", "")
start_event.provider_type = getattr(self.node_data, "provider_type", "")
from dify_graph.nodes.agent.agent_node import AgentNode from dify_graph.nodes.agent.agent_node import AgentNode
from dify_graph.nodes.agent.entities import AgentNodeData from dify_graph.nodes.agent.entities import AgentNodeData
@@ -394,6 +392,8 @@ class Node(Generic[NodeDataT]):
icon=self.agent_strategy_icon, icon=self.agent_strategy_icon,
) )
self.customize_start_event(start_event)
# === # ===
yield start_event yield start_event
@@ -524,6 +524,7 @@ class Node(Generic[NodeDataT]):
"""Return mapping of NodeType -> {version -> Node subclass} using __init_subclass__ registry. """Return mapping of NodeType -> {version -> Node subclass} using __init_subclass__ registry.
Import all modules under dify_graph.nodes so subclasses register themselves on import. Import all modules under dify_graph.nodes so subclasses register themselves on import.
Higher-level packages may register additional nodes before calling this helper.
Then we return a readonly view of the registry to avoid accidental mutation. Then we return a readonly view of the registry to avoid accidental mutation.
""" """
# Import all node modules to ensure they are loaded (thus registered) # Import all node modules to ensure they are loaded (thus registered)

View File

@@ -1,3 +0,0 @@
from dify_graph.nodes.trigger_schedule.trigger_schedule_node import TriggerScheduleNode
__all__ = ["TriggerScheduleNode"]

View File

@@ -4,7 +4,7 @@ from typing import cast
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from dify_graph.nodes.trigger_schedule.entities import SchedulePlanUpdate from core.workflow.nodes.trigger_schedule.entities import SchedulePlanUpdate
from events.app_event import app_published_workflow_was_updated from events.app_event import app_published_workflow_was_updated
from extensions.ext_database import db from extensions.ext_database import db
from models import AppMode, Workflow, WorkflowSchedulePlan from models import AppMode, Workflow, WorkflowSchedulePlan

View File

@@ -20,6 +20,7 @@ from sqlalchemy.orm import Session
from configs import dify_config from configs import dify_config
from core.helper import ssrf_proxy from core.helper import ssrf_proxy
from core.plugin.entities.plugin import PluginDependency from core.plugin.entities.plugin import PluginDependency
from core.workflow.nodes.trigger_schedule.trigger_schedule_node import TriggerScheduleNode
from dify_graph.enums import NodeType from dify_graph.enums import NodeType
from dify_graph.model_runtime.utils.encoders import jsonable_encoder from dify_graph.model_runtime.utils.encoders import jsonable_encoder
from dify_graph.nodes.knowledge_retrieval.entities import KnowledgeRetrievalNodeData from dify_graph.nodes.knowledge_retrieval.entities import KnowledgeRetrievalNodeData
@@ -27,7 +28,6 @@ from dify_graph.nodes.llm.entities import LLMNodeData
from dify_graph.nodes.parameter_extractor.entities import ParameterExtractorNodeData from dify_graph.nodes.parameter_extractor.entities import ParameterExtractorNodeData
from dify_graph.nodes.question_classifier.entities import QuestionClassifierNodeData from dify_graph.nodes.question_classifier.entities import QuestionClassifierNodeData
from dify_graph.nodes.tool.entities import ToolNodeData from dify_graph.nodes.tool.entities import ToolNodeData
from dify_graph.nodes.trigger_schedule.trigger_schedule_node import TriggerScheduleNode
from events.app_event import app_model_config_was_updated, app_was_created from events.app_event import app_model_config_was_updated, app_was_created
from extensions.ext_redis import redis_client from extensions.ext_redis import redis_client
from factories import variable_factory from factories import variable_factory

View File

@@ -36,6 +36,7 @@ from core.rag.entities.event import (
) )
from core.repositories.factory import DifyCoreRepositoryFactory from core.repositories.factory import DifyCoreRepositoryFactory
from core.repositories.sqlalchemy_workflow_node_execution_repository import SQLAlchemyWorkflowNodeExecutionRepository from core.repositories.sqlalchemy_workflow_node_execution_repository import SQLAlchemyWorkflowNodeExecutionRepository
from core.workflow.nodes.node_mapping import LATEST_VERSION, NODE_TYPE_CLASSES_MAPPING
from core.workflow.workflow_entry import WorkflowEntry from core.workflow.workflow_entry import WorkflowEntry
from dify_graph.entities.workflow_node_execution import ( from dify_graph.entities.workflow_node_execution import (
WorkflowNodeExecution, WorkflowNodeExecution,
@@ -48,7 +49,6 @@ from dify_graph.graph_events.base import GraphNodeEventBase
from dify_graph.node_events.base import NodeRunResult from dify_graph.node_events.base import NodeRunResult
from dify_graph.nodes.base.node import Node from dify_graph.nodes.base.node import Node
from dify_graph.nodes.http_request import HTTP_REQUEST_CONFIG_FILTER_KEY, build_http_request_config from dify_graph.nodes.http_request import HTTP_REQUEST_CONFIG_FILTER_KEY, build_http_request_config
from dify_graph.nodes.node_mapping import LATEST_VERSION, NODE_TYPE_CLASSES_MAPPING
from dify_graph.repositories.workflow_node_execution_repository import OrderConfig from dify_graph.repositories.workflow_node_execution_repository import OrderConfig
from dify_graph.runtime import VariablePool from dify_graph.runtime import VariablePool
from dify_graph.system_variable import SystemVariable from dify_graph.system_variable import SystemVariable

View File

@@ -5,15 +5,15 @@ from datetime import datetime
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from dify_graph.entities.graph_config import NodeConfigDict from core.workflow.nodes.trigger_schedule.entities import (
from dify_graph.nodes import NodeType
from dify_graph.nodes.trigger_schedule.entities import (
ScheduleConfig, ScheduleConfig,
SchedulePlanUpdate, SchedulePlanUpdate,
TriggerScheduleNodeData, TriggerScheduleNodeData,
VisualConfig, VisualConfig,
) )
from dify_graph.nodes.trigger_schedule.exc import ScheduleConfigError, ScheduleNotFoundError from core.workflow.nodes.trigger_schedule.exc import ScheduleConfigError, ScheduleNotFoundError
from dify_graph.entities.graph_config import NodeConfigDict
from dify_graph.nodes import NodeType
from libs.schedule_utils import calculate_next_run_at, convert_12h_to_24h from libs.schedule_utils import calculate_next_run_at, convert_12h_to_24h
from models.account import Account, TenantAccountJoin from models.account import Account, TenantAccountJoin
from models.trigger import WorkflowSchedulePlan from models.trigger import WorkflowSchedulePlan

View File

@@ -16,9 +16,9 @@ from core.trigger.debug.events import PluginTriggerDebugEvent
from core.trigger.provider import PluginTriggerProviderController from core.trigger.provider import PluginTriggerProviderController
from core.trigger.trigger_manager import TriggerManager from core.trigger.trigger_manager import TriggerManager
from core.trigger.utils.encryption import create_trigger_provider_encrypter_for_subscription from core.trigger.utils.encryption import create_trigger_provider_encrypter_for_subscription
from core.workflow.nodes.trigger_plugin.entities import TriggerEventNodeData
from dify_graph.entities.graph_config import NodeConfigDict from dify_graph.entities.graph_config import NodeConfigDict
from dify_graph.enums import NodeType from dify_graph.enums import NodeType
from dify_graph.nodes.trigger_plugin.entities import TriggerEventNodeData
from extensions.ext_database import db from extensions.ext_database import db
from extensions.ext_redis import redis_client from extensions.ext_redis import redis_client
from models.model import App from models.model import App

View File

@@ -16,15 +16,15 @@ from werkzeug.exceptions import RequestEntityTooLarge
from configs import dify_config from configs import dify_config
from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.tools.tool_file_manager import ToolFileManager from core.tools.tool_file_manager import ToolFileManager
from dify_graph.entities.graph_config import NodeConfigDict from core.workflow.nodes.trigger_webhook.entities import (
from dify_graph.enums import NodeType
from dify_graph.file.models import FileTransferMethod
from dify_graph.nodes.trigger_webhook.entities import (
ContentType, ContentType,
WebhookBodyParameter, WebhookBodyParameter,
WebhookData, WebhookData,
WebhookParameter, WebhookParameter,
) )
from dify_graph.entities.graph_config import NodeConfigDict
from dify_graph.enums import NodeType
from dify_graph.file.models import FileTransferMethod
from dify_graph.variables.types import ArrayValidation, SegmentType from dify_graph.variables.types import ArrayValidation, SegmentType
from enums.quota_type import QuotaType from enums.quota_type import QuotaType
from extensions.ext_database import db from extensions.ext_database import db

View File

@@ -14,6 +14,7 @@ from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager
from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom, build_dify_run_context from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom, build_dify_run_context
from core.repositories import DifyCoreRepositoryFactory from core.repositories import DifyCoreRepositoryFactory
from core.repositories.human_input_repository import HumanInputFormRepositoryImpl from core.repositories.human_input_repository import HumanInputFormRepositoryImpl
from core.workflow.nodes.node_mapping import LATEST_VERSION, NODE_TYPE_CLASSES_MAPPING
from core.workflow.workflow_entry import WorkflowEntry from core.workflow.workflow_entry import WorkflowEntry
from dify_graph.entities import GraphInitParams, WorkflowNodeExecution from dify_graph.entities import GraphInitParams, WorkflowNodeExecution
from dify_graph.entities.graph_config import NodeConfigDict from dify_graph.entities.graph_config import NodeConfigDict
@@ -34,7 +35,6 @@ from dify_graph.nodes.human_input.entities import (
) )
from dify_graph.nodes.human_input.enums import HumanInputFormKind from dify_graph.nodes.human_input.enums import HumanInputFormKind
from dify_graph.nodes.human_input.human_input_node import HumanInputNode from dify_graph.nodes.human_input.human_input_node import HumanInputNode
from dify_graph.nodes.node_mapping import LATEST_VERSION, NODE_TYPE_CLASSES_MAPPING
from dify_graph.nodes.start.entities import StartNodeData from dify_graph.nodes.start.entities import StartNodeData
from dify_graph.repositories.human_input_form_repository import FormCreateParams from dify_graph.repositories.human_input_form_repository import FormCreateParams
from dify_graph.runtime import GraphRuntimeState, VariablePool from dify_graph.runtime import GraphRuntimeState, VariablePool

View File

@@ -25,8 +25,8 @@ from core.trigger.debug.events import PluginTriggerDebugEvent, build_plugin_pool
from core.trigger.entities.entities import TriggerProviderEntity from core.trigger.entities.entities import TriggerProviderEntity
from core.trigger.provider import PluginTriggerProviderController from core.trigger.provider import PluginTriggerProviderController
from core.trigger.trigger_manager import TriggerManager from core.trigger.trigger_manager import TriggerManager
from core.workflow.nodes.trigger_plugin.entities import TriggerEventNodeData
from dify_graph.enums import NodeType, WorkflowExecutionStatus from dify_graph.enums import NodeType, WorkflowExecutionStatus
from dify_graph.nodes.trigger_plugin.entities import TriggerEventNodeData
from enums.quota_type import QuotaType, unlimited from enums.quota_type import QuotaType, unlimited
from models.enums import ( from models.enums import (
AppTriggerType, AppTriggerType,

View File

@@ -3,7 +3,7 @@ import logging
from celery import shared_task from celery import shared_task
from core.db.session_factory import session_factory from core.db.session_factory import session_factory
from dify_graph.nodes.trigger_schedule.exc import ( from core.workflow.nodes.trigger_schedule.exc import (
ScheduleExecutionError, ScheduleExecutionError,
ScheduleNotFoundError, ScheduleNotFoundError,
TenantOwnerNotFoundError, TenantOwnerNotFoundError,

View File

@@ -1,6 +1,6 @@
import time import time
import uuid import uuid
from unittest.mock import MagicMock from unittest.mock import MagicMock, patch
from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom
from core.tools.utils.configuration import ToolParameterConfigurationManager from core.tools.utils.configuration import ToolParameterConfigurationManager
@@ -87,17 +87,20 @@ def test_tool_variable_invoke():
} }
) )
ToolParameterConfigurationManager.decrypt_tool_parameters = MagicMock(return_value={"format": "%Y-%m-%d %H:%M:%S"})
node.graph_runtime_state.variable_pool.add(["1", "args1"], "1+1") node.graph_runtime_state.variable_pool.add(["1", "args1"], "1+1")
# execute node with patch.object(
result = node._run() ToolParameterConfigurationManager,
for item in result: "decrypt_tool_parameters",
if isinstance(item, StreamCompletedEvent): return_value={"format": "%Y-%m-%d %H:%M:%S"},
assert item.node_run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED ):
assert item.node_run_result.outputs is not None # execute node
assert item.node_run_result.outputs.get("text") is not None result = node._run()
for item in result:
if isinstance(item, StreamCompletedEvent):
assert item.node_run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED
assert item.node_run_result.outputs is not None
assert item.node_run_result.outputs.get("text") is not None
def test_tool_mixed_invoke(): def test_tool_mixed_invoke():
@@ -121,12 +124,15 @@ def test_tool_mixed_invoke():
} }
) )
ToolParameterConfigurationManager.decrypt_tool_parameters = MagicMock(return_value={"format": "%Y-%m-%d %H:%M:%S"}) with patch.object(
ToolParameterConfigurationManager,
# execute node "decrypt_tool_parameters",
result = node._run() return_value={"format": "%Y-%m-%d %H:%M:%S"},
for item in result: ):
if isinstance(item, StreamCompletedEvent): # execute node
assert item.node_run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED result = node._run()
assert item.node_run_result.outputs is not None for item in result:
assert item.node_run_result.outputs.get("text") is not None if isinstance(item, StreamCompletedEvent):
assert item.node_run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED
assert item.node_run_result.outputs is not None
assert item.node_run_result.outputs.get("text") is not None

View File

@@ -6,7 +6,7 @@ import uuid
from collections.abc import Mapping from collections.abc import Mapping
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any
from unittest.mock import Mock from unittest.mock import Mock, patch
import pytest import pytest
@@ -738,6 +738,30 @@ class TestWorkflowResponseConverterServiceApiTruncation:
assert not response.data.process_data_truncated assert not response.data.process_data_truncated
assert not response.data.outputs_truncated assert not response.data.outputs_truncated
def test_trigger_plugin_start_event_uses_provider_id_for_icon(self):
converter = self.create_test_converter(InvokeFrom.WEB_APP)
event = QueueNodeStartedEvent(
node_execution_id=str(uuid.uuid4()),
node_id="trigger-node",
node_title="Trigger Node",
node_type=NodeType.TRIGGER_PLUGIN,
start_at=naive_utc_now(),
in_iteration_id=None,
in_loop_id=None,
provider_type="",
provider_id="provider-1",
)
with patch(
"core.app.apps.common.workflow_response_converter.TriggerManager.get_trigger_plugin_icon",
return_value="https://example.com/icon.png",
) as get_trigger_plugin_icon:
response = converter.workflow_node_start_to_stream_response(event=event, task_id="task-1")
assert response is not None
assert response.data.extras["icon"] == "https://example.com/icon.png"
get_trigger_plugin_icon.assert_called_once_with("test_tenant", "provider-1")
def test_service_api_iteration_events_no_truncation(self): def test_service_api_iteration_events_no_truncation(self):
"""Test that Service API doesn't truncate iteration events.""" """Test that Service API doesn't truncate iteration events."""
converter = self.create_test_converter(InvokeFrom.SERVICE_API) converter = self.create_test_converter(InvokeFrom.SERVICE_API)

View File

@@ -6,7 +6,7 @@ import pytest
import pytz import pytz
from core.trigger.debug import event_selectors from core.trigger.debug import event_selectors
from dify_graph.nodes.trigger_schedule.entities import ScheduleConfig from core.workflow.nodes.trigger_schedule.entities import ScheduleConfig
class _DummyRedis: class _DummyRedis:

View File

@@ -1,12 +1,11 @@
import pytest import pytest
# Ensures that all node classes are imported.
from core.workflow.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING
from dify_graph.entities.base_node_data import BaseNodeData from dify_graph.entities.base_node_data import BaseNodeData
from dify_graph.enums import NodeType from dify_graph.enums import NodeType
from dify_graph.nodes.base.node import Node from dify_graph.nodes.base.node import Node
# Ensures that all node classes are imported.
from dify_graph.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING
# Ensure `NODE_TYPE_CLASSES_MAPPING` is used and not automatically removed. # Ensure `NODE_TYPE_CLASSES_MAPPING` is used and not automatically removed.
_ = NODE_TYPE_CLASSES_MAPPING _ = NODE_TYPE_CLASSES_MAPPING

View File

@@ -0,0 +1,62 @@
from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom
from core.workflow.nodes.trigger_plugin.entities import TriggerEventNodeData
from core.workflow.nodes.trigger_plugin.trigger_event_node import TriggerEventNode
from dify_graph.entities.graph_init_params import DIFY_RUN_CONTEXT_KEY, GraphInitParams
from dify_graph.graph_events import NodeRunStartedEvent
from dify_graph.runtime.graph_runtime_state import GraphRuntimeState
from dify_graph.runtime.variable_pool import VariablePool
from dify_graph.system_variable import SystemVariable
def create_trigger_event_node(node_data: TriggerEventNodeData) -> TriggerEventNode:
node_config = {
"id": "trigger-node",
"data": node_data.model_dump(),
}
graph_init_params = GraphInitParams(
workflow_id="workflow-1",
graph_config={},
run_context={
DIFY_RUN_CONTEXT_KEY: {
"tenant_id": "tenant-1",
"app_id": "app-1",
"user_id": "user-1",
"user_from": UserFrom.ACCOUNT,
"invoke_from": InvokeFrom.SERVICE_API,
}
},
call_depth=0,
)
runtime_state = GraphRuntimeState(
variable_pool=VariablePool(
system_variables=SystemVariable.default(),
user_inputs={},
),
start_at=0,
)
return TriggerEventNode(
id="trigger-node",
config=node_config,
graph_init_params=graph_init_params,
graph_runtime_state=runtime_state,
)
def test_trigger_event_start_event_carries_provider_metadata() -> None:
node = create_trigger_event_node(
TriggerEventNodeData(
title="Plugin Trigger",
provider_id="provider-1",
plugin_id="plugin-1",
event_name="event.created",
subscription_id="subscription-1",
plugin_unique_identifier="plugin/provider",
event_parameters={},
)
)
start_event = next(node.run())
assert isinstance(start_event, NodeRunStartedEvent)
assert start_event.provider_id == "provider-1"
assert start_event.extras == {"provider_id": "provider-1"}

View File

@@ -1,7 +1,7 @@
import pytest import pytest
from pydantic import ValidationError from pydantic import ValidationError
from dify_graph.nodes.trigger_webhook.entities import ( from core.workflow.nodes.trigger_webhook.entities import (
ContentType, ContentType,
Method, Method,
WebhookBodyParameter, WebhookBodyParameter,

View File

@@ -1,12 +1,12 @@
import pytest import pytest
from dify_graph.entities.exc import BaseNodeError from core.workflow.nodes.trigger_webhook.exc import (
from dify_graph.nodes.trigger_webhook.exc import (
WebhookConfigError, WebhookConfigError,
WebhookNodeError, WebhookNodeError,
WebhookNotFoundError, WebhookNotFoundError,
WebhookTimeoutError, WebhookTimeoutError,
) )
from dify_graph.entities.exc import BaseNodeError
def test_webhook_node_error_inheritance(): def test_webhook_node_error_inheritance():
@@ -149,7 +149,7 @@ def test_webhook_error_attributes():
assert WebhookConfigError.__name__ == "WebhookConfigError" assert WebhookConfigError.__name__ == "WebhookConfigError"
# Test that all error classes have proper __module__ # Test that all error classes have proper __module__
expected_module = "dify_graph.nodes.trigger_webhook.exc" expected_module = "core.workflow.nodes.trigger_webhook.exc"
assert WebhookNodeError.__module__ == expected_module assert WebhookNodeError.__module__ == expected_module
assert WebhookTimeoutError.__module__ == expected_module assert WebhookTimeoutError.__module__ == expected_module
assert WebhookNotFoundError.__module__ == expected_module assert WebhookNotFoundError.__module__ == expected_module

View File

@@ -9,15 +9,15 @@ when passing files to downstream LLM nodes.
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom
from dify_graph.entities.graph_init_params import DIFY_RUN_CONTEXT_KEY, GraphInitParams from core.workflow.nodes.trigger_webhook.entities import (
from dify_graph.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from dify_graph.nodes.trigger_webhook.entities import (
ContentType, ContentType,
Method, Method,
WebhookBodyParameter, WebhookBodyParameter,
WebhookData, WebhookData,
) )
from dify_graph.nodes.trigger_webhook.node import TriggerWebhookNode from core.workflow.nodes.trigger_webhook.node import TriggerWebhookNode
from dify_graph.entities.graph_init_params import DIFY_RUN_CONTEXT_KEY, GraphInitParams
from dify_graph.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from dify_graph.runtime.graph_runtime_state import GraphRuntimeState from dify_graph.runtime.graph_runtime_state import GraphRuntimeState
from dify_graph.runtime.variable_pool import VariablePool from dify_graph.runtime.variable_pool import VariablePool
from dify_graph.system_variable import SystemVariable from dify_graph.system_variable import SystemVariable
@@ -130,8 +130,8 @@ def test_webhook_node_file_conversion_to_file_variable():
# Mock the file factory and variable factory # Mock the file factory and variable factory
with ( with (
patch("factories.file_factory.build_from_mapping") as mock_file_factory, patch("factories.file_factory.build_from_mapping") as mock_file_factory,
patch("dify_graph.nodes.trigger_webhook.node.build_segment_with_type") as mock_segment_factory, patch("core.workflow.nodes.trigger_webhook.node.build_segment_with_type") as mock_segment_factory,
patch("dify_graph.nodes.trigger_webhook.node.FileVariable") as mock_file_variable, patch("core.workflow.nodes.trigger_webhook.node.FileVariable") as mock_file_variable,
): ):
# Setup mocks # Setup mocks
mock_file_obj = Mock() mock_file_obj = Mock()
@@ -322,8 +322,8 @@ def test_webhook_node_file_conversion_mixed_parameters():
with ( with (
patch("factories.file_factory.build_from_mapping") as mock_file_factory, patch("factories.file_factory.build_from_mapping") as mock_file_factory,
patch("dify_graph.nodes.trigger_webhook.node.build_segment_with_type") as mock_segment_factory, patch("core.workflow.nodes.trigger_webhook.node.build_segment_with_type") as mock_segment_factory,
patch("dify_graph.nodes.trigger_webhook.node.FileVariable") as mock_file_variable, patch("core.workflow.nodes.trigger_webhook.node.FileVariable") as mock_file_variable,
): ):
# Setup mocks for file # Setup mocks for file
mock_file_obj = Mock() mock_file_obj = Mock()
@@ -390,8 +390,8 @@ def test_webhook_node_different_file_types():
with ( with (
patch("factories.file_factory.build_from_mapping") as mock_file_factory, patch("factories.file_factory.build_from_mapping") as mock_file_factory,
patch("dify_graph.nodes.trigger_webhook.node.build_segment_with_type") as mock_segment_factory, patch("core.workflow.nodes.trigger_webhook.node.build_segment_with_type") as mock_segment_factory,
patch("dify_graph.nodes.trigger_webhook.node.FileVariable") as mock_file_variable, patch("core.workflow.nodes.trigger_webhook.node.FileVariable") as mock_file_variable,
): ):
# Setup mocks for all files # Setup mocks for all files
mock_file_objs = [Mock() for _ in range(3)] mock_file_objs = [Mock() for _ in range(3)]

View File

@@ -3,17 +3,17 @@ from unittest.mock import patch
import pytest import pytest
from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom
from dify_graph.entities.graph_init_params import DIFY_RUN_CONTEXT_KEY, GraphInitParams from core.workflow.nodes.trigger_webhook.entities import (
from dify_graph.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from dify_graph.file import File, FileTransferMethod, FileType
from dify_graph.nodes.trigger_webhook.entities import (
ContentType, ContentType,
Method, Method,
WebhookBodyParameter, WebhookBodyParameter,
WebhookData, WebhookData,
WebhookParameter, WebhookParameter,
) )
from dify_graph.nodes.trigger_webhook.node import TriggerWebhookNode from core.workflow.nodes.trigger_webhook.node import TriggerWebhookNode
from dify_graph.entities.graph_init_params import DIFY_RUN_CONTEXT_KEY, GraphInitParams
from dify_graph.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from dify_graph.file import File, FileTransferMethod, FileType
from dify_graph.runtime.graph_runtime_state import GraphRuntimeState from dify_graph.runtime.graph_runtime_state import GraphRuntimeState
from dify_graph.runtime.variable_pool import VariablePool from dify_graph.runtime.variable_pool import VariablePool
from dify_graph.system_variable import SystemVariable from dify_graph.system_variable import SystemVariable

View File

@@ -294,7 +294,7 @@ class TestFrontendBackendIntegration(unittest.TestCase):
def test_schedule_service_integration(self): def test_schedule_service_integration(self):
"""Test integration with ScheduleService patterns.""" """Test integration with ScheduleService patterns."""
from dify_graph.nodes.trigger_schedule.entities import VisualConfig from core.workflow.nodes.trigger_schedule.entities import VisualConfig
from services.trigger.schedule_service import ScheduleService from services.trigger.schedule_service import ScheduleService
# Test enhanced syntax through visual config conversion # Test enhanced syntax through visual config conversion

View File

@@ -5,8 +5,8 @@ from unittest.mock import MagicMock, Mock, patch
import pytest import pytest
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from dify_graph.nodes.trigger_schedule.entities import ScheduleConfig, SchedulePlanUpdate, VisualConfig from core.workflow.nodes.trigger_schedule.entities import ScheduleConfig, SchedulePlanUpdate, VisualConfig
from dify_graph.nodes.trigger_schedule.exc import ScheduleConfigError from core.workflow.nodes.trigger_schedule.exc import ScheduleConfigError
from events.event_handlers.sync_workflow_schedule_when_app_published import ( from events.event_handlers.sync_workflow_schedule_when_app_published import (
sync_schedule_from_workflow, sync_schedule_from_workflow,
) )
@@ -136,7 +136,7 @@ class TestScheduleService(unittest.TestCase):
def test_update_schedule_not_found(self): def test_update_schedule_not_found(self):
"""Test updating a non-existent schedule raises exception.""" """Test updating a non-existent schedule raises exception."""
from dify_graph.nodes.trigger_schedule.exc import ScheduleNotFoundError from core.workflow.nodes.trigger_schedule.exc import ScheduleNotFoundError
mock_session = MagicMock(spec=Session) mock_session = MagicMock(spec=Session)
mock_session.get.return_value = None mock_session.get.return_value = None
@@ -172,7 +172,7 @@ class TestScheduleService(unittest.TestCase):
def test_delete_schedule_not_found(self): def test_delete_schedule_not_found(self):
"""Test deleting a non-existent schedule raises exception.""" """Test deleting a non-existent schedule raises exception."""
from dify_graph.nodes.trigger_schedule.exc import ScheduleNotFoundError from core.workflow.nodes.trigger_schedule.exc import ScheduleNotFoundError
mock_session = MagicMock(spec=Session) mock_session = MagicMock(spec=Session)
mock_session.get.return_value = None mock_session.get.return_value = None