mirror of
https://github.com/langgenius/dify.git
synced 2026-07-26 22:28:32 +08:00
fix: enforce tool provider type contracts (#39380)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
5216ee1d20
commit
9b432d0d29
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Literal, Protocol, cast
|
||||
from typing import Any, Final, Literal, Protocol
|
||||
|
||||
from dify_agent.layers.dify_core_tools import DifyCoreToolConfig, DifyCoreToolProviderType, DifyCoreToolsLayerConfig
|
||||
from dify_agent.layers.dify_plugin import (
|
||||
@ -29,6 +29,13 @@ from models.provider_ids import ToolProviderID
|
||||
from models.tools import WorkflowToolProvider
|
||||
from services.tools.mcp_tools_manage_service import MCPToolManageService
|
||||
|
||||
_CORE_TOOL_PROVIDER_TYPES: Final[dict[ToolProviderType, DifyCoreToolProviderType]] = {
|
||||
ToolProviderType.BUILT_IN: "builtin",
|
||||
ToolProviderType.API: "api",
|
||||
ToolProviderType.WORKFLOW: "workflow",
|
||||
ToolProviderType.MCP: "mcp",
|
||||
}
|
||||
|
||||
|
||||
class WorkflowAgentDifyToolsBuildError(ValueError):
|
||||
"""Raised when Agent Soul tools cannot be prepared for Agent backend."""
|
||||
@ -235,7 +242,7 @@ class WorkflowAgentDifyToolsBuilder:
|
||||
if tool_config.tool_name is not None:
|
||||
expanded.append(tool_config)
|
||||
continue
|
||||
provider_type = ToolProviderType.value_of(tool_config.provider_type)
|
||||
provider_type = tool_config.provider_type
|
||||
provider_id = self._provider_id(tool_config)
|
||||
try:
|
||||
tool_names = self._provider_declared_tool_names(
|
||||
@ -279,7 +286,7 @@ class WorkflowAgentDifyToolsBuilder:
|
||||
tenant_id: str,
|
||||
tool_config: AgentSoulDifyToolConfig,
|
||||
) -> AgentSoulDifyToolConfig:
|
||||
if tool_config.provider_type != ToolProviderType.MCP.value:
|
||||
if tool_config.provider_type is not ToolProviderType.MCP:
|
||||
return tool_config
|
||||
provider_id = self._mcp_provider_id_resolver(tenant_id=tenant_id, provider_id=self._provider_id(tool_config))
|
||||
return tool_config.model_copy(update={"provider_id": provider_id, "plugin_id": None, "provider": None})
|
||||
@ -326,7 +333,7 @@ class WorkflowAgentDifyToolsBuilder:
|
||||
def _to_agent_tool_entity(tool_config: AgentSoulDifyToolConfig) -> AgentToolEntity:
|
||||
assert tool_config.tool_name is not None
|
||||
return AgentToolEntity(
|
||||
provider_type=ToolProviderType.value_of(tool_config.provider_type),
|
||||
provider_type=tool_config.provider_type,
|
||||
provider_id=WorkflowAgentDifyToolsBuilder._provider_id(tool_config),
|
||||
tool_name=tool_config.tool_name,
|
||||
tool_parameters=dict(tool_config.runtime_parameters),
|
||||
@ -343,24 +350,16 @@ class WorkflowAgentDifyToolsBuilder:
|
||||
|
||||
@staticmethod
|
||||
def _provider_key(tool_config: AgentSoulDifyToolConfig) -> tuple[ToolProviderType, str]:
|
||||
return (
|
||||
ToolProviderType.value_of(tool_config.provider_type),
|
||||
WorkflowAgentDifyToolsBuilder._provider_id(tool_config),
|
||||
)
|
||||
return (tool_config.provider_type, WorkflowAgentDifyToolsBuilder._provider_id(tool_config))
|
||||
|
||||
@staticmethod
|
||||
def _tool_layer_destination(tool_config: AgentSoulDifyToolConfig) -> Literal["plugin", "core"]:
|
||||
provider_type = ToolProviderType.value_of(tool_config.provider_type)
|
||||
provider_type = tool_config.provider_type
|
||||
if provider_type is ToolProviderType.PLUGIN or (
|
||||
provider_type is ToolProviderType.BUILT_IN and _is_plugin_provider_id(tool_config.provider_id)
|
||||
):
|
||||
return "plugin"
|
||||
if provider_type in {
|
||||
ToolProviderType.BUILT_IN,
|
||||
ToolProviderType.API,
|
||||
ToolProviderType.WORKFLOW,
|
||||
ToolProviderType.MCP,
|
||||
}:
|
||||
if provider_type in _CORE_TOOL_PROVIDER_TYPES:
|
||||
return "core"
|
||||
if provider_type is ToolProviderType.DATASET_RETRIEVAL:
|
||||
raise WorkflowAgentDifyToolsBuildError(
|
||||
@ -417,7 +416,7 @@ class WorkflowAgentDifyToolsBuilder:
|
||||
) -> DifyCoreToolConfig:
|
||||
parameters = self._prepared_parameters(tool_runtime)
|
||||
return DifyCoreToolConfig(
|
||||
provider_type=cast(DifyCoreToolProviderType, tool_config.provider_type),
|
||||
provider_type=_CORE_TOOL_PROVIDER_TYPES[tool_config.provider_type],
|
||||
provider_id=self._provider_id(tool_config),
|
||||
tool_name=tool_config.tool_name or exposed_name,
|
||||
credential_id=tool_config.credential_ref.id if tool_config.credential_ref else None,
|
||||
|
||||
@ -7,6 +7,7 @@ from typing import Annotated, Any, Final, Literal, Self
|
||||
from pydantic import BaseModel, ConfigDict, Field, WithJsonSchema, field_validator, model_validator
|
||||
|
||||
from core.rag.entities.metadata_entities import ConditionValue, SupportedComparisonOperator
|
||||
from core.tools.entities.tool_entities import ToolProviderType
|
||||
from core.workflow.file_reference import is_canonical_file_reference
|
||||
from graphon.file import FileTransferMethod, FileType
|
||||
|
||||
@ -653,11 +654,7 @@ class AgentSoulDifyToolConfig(BaseModel):
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
|
||||
enabled: bool = True
|
||||
# ``plugin`` remains the default for legacy Agent Soul payloads. The runtime
|
||||
# now also accepts ``builtin`` / ``api`` / ``workflow`` / ``mcp`` here and
|
||||
# routes them through ``dify.core.tools``; keeping the default narrow still
|
||||
# makes a missing field resolve against the plugin provider table.
|
||||
provider_type: str = "plugin"
|
||||
provider_type: ToolProviderType
|
||||
provider_id: str | None = Field(default=None, max_length=255)
|
||||
plugin_id: str | None = Field(default=None, max_length=255)
|
||||
provider: str | None = Field(default=None, max_length=255)
|
||||
|
||||
@ -14737,7 +14737,7 @@ should send ``plugin_id`` + ``provider`` when available.
|
||||
| plugin_id | string | | No |
|
||||
| provider | string | | No |
|
||||
| provider_id | string | | No |
|
||||
| provider_type | string, <br>**Default:** plugin | | No |
|
||||
| provider_type | [ToolProviderType](#toolprovidertype) | | Yes |
|
||||
| runtime_parameters | object | | No |
|
||||
| tool_name | string | | No |
|
||||
|
||||
|
||||
@ -235,6 +235,7 @@ def test_builds_dify_plugin_tools_layer_from_existing_tool_runtime():
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "langgenius/search/search",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "search",
|
||||
"credential_type": "api-key",
|
||||
"credential_id": "credential-1",
|
||||
@ -258,9 +259,6 @@ def test_builds_dify_plugin_tools_layer_from_existing_tool_runtime():
|
||||
assert "region" not in prepared.parameters_json_schema["properties"]
|
||||
assert runtime_provider.last_agent_tool is not None
|
||||
assert runtime_provider.last_agent_tool.credential_id == "credential-1"
|
||||
# Default ``provider_type`` is now ``"plugin"`` — the agent tool entity
|
||||
# must surface that so ToolManager hits the plugin provider table, not the
|
||||
# built-in legacy table.
|
||||
assert runtime_provider.last_agent_tool.provider_type.value == "plugin"
|
||||
|
||||
|
||||
@ -599,6 +597,7 @@ def test_rejects_duplicate_exposed_tool_names():
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "langgenius/search/search",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "search",
|
||||
"credential_type": "api-key",
|
||||
"credential_id": "credential-1",
|
||||
@ -606,6 +605,7 @@ def test_rejects_duplicate_exposed_tool_names():
|
||||
},
|
||||
{
|
||||
"provider_id": "langgenius/search/search",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "search",
|
||||
"credential_type": "api-key",
|
||||
"credential_id": "credential-1",
|
||||
@ -629,6 +629,7 @@ def test_rejects_missing_required_runtime_parameter():
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "langgenius/search/search",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "search",
|
||||
"credential_type": "api-key",
|
||||
"credential_id": "credential-1",
|
||||
@ -663,6 +664,7 @@ def test_invoke_from_is_forwarded_to_tool_runtime_provider():
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "langgenius/search/search",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "search",
|
||||
"credential_type": "api-key",
|
||||
"credential_id": "credential-1",
|
||||
@ -690,6 +692,7 @@ def test_disabled_tools_are_skipped():
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "langgenius/search/search",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "search",
|
||||
"credential_type": "api-key",
|
||||
"credential_id": "credential-1",
|
||||
@ -717,6 +720,7 @@ def test_plugin_id_plus_provider_fallback_when_provider_id_missing():
|
||||
{
|
||||
"plugin_id": "langgenius/search",
|
||||
"provider": "search",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "search",
|
||||
"credential_type": "api-key",
|
||||
"credential_id": "credential-1",
|
||||
@ -752,6 +756,7 @@ def test_unauthorized_tool_without_credentials():
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "langgenius/time/time",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "current_time",
|
||||
"credential_type": "unauthorized",
|
||||
"runtime_parameters": {"region": "us"},
|
||||
@ -777,6 +782,7 @@ def _standard_tools_payload() -> AgentSoulToolsConfig:
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "langgenius/search/search",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "search",
|
||||
"credential_type": "api-key",
|
||||
"credential_id": "credential-1",
|
||||
@ -859,6 +865,7 @@ def test_legacy_provider_name_and_tool_parameters_normalized():
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_name": "langgenius/search/search",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "search",
|
||||
"credential_type": "api-key",
|
||||
"credential_id": "credential-1",
|
||||
@ -875,6 +882,21 @@ def test_legacy_provider_name_and_tool_parameters_normalized():
|
||||
assert tool.credential_ref.id == "credential-1"
|
||||
|
||||
|
||||
def test_rejects_unknown_provider_type_at_config_boundary():
|
||||
with pytest.raises(ValueError, match="provider_type"):
|
||||
AgentSoulToolsConfig.model_validate(
|
||||
{
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "future-provider",
|
||||
"provider_type": "future-provider",
|
||||
"credential_type": "unauthorized",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# ── provider-level entries (tool_name omitted = all tools of the provider) ───
|
||||
|
||||
|
||||
@ -888,7 +910,15 @@ def test_provider_level_entry_expands_to_all_tools():
|
||||
|
||||
builder = WorkflowAgentDifyToolsBuilder(tool_runtime_provider=runtime_provider, provider_tools_lister=lister)
|
||||
tools = AgentSoulToolsConfig.model_validate(
|
||||
{"dify_tools": [{"provider_id": "langgenius/search/search", "credential_type": "unauthorized"}]}
|
||||
{
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "langgenius/search/search",
|
||||
"provider_type": "plugin",
|
||||
"credential_type": "unauthorized",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
result = _build(builder, tools)
|
||||
@ -906,9 +936,14 @@ def test_explicit_tool_entry_wins_over_provider_expansion():
|
||||
tools = AgentSoulToolsConfig.model_validate(
|
||||
{
|
||||
"dify_tools": [
|
||||
{"provider_id": "langgenius/search/search", "credential_type": "unauthorized"},
|
||||
{
|
||||
"provider_id": "langgenius/search/search",
|
||||
"provider_type": "plugin",
|
||||
"credential_type": "unauthorized",
|
||||
},
|
||||
{
|
||||
"provider_id": "langgenius/search/search",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "search",
|
||||
"credential_type": "unauthorized",
|
||||
"runtime_parameters": {"region": "eu"},
|
||||
@ -930,7 +965,15 @@ def test_provider_level_entry_with_no_tools_maps_to_declaration_not_found():
|
||||
provider_tools_lister=lambda *, tenant_id, provider_type, provider_id: [],
|
||||
)
|
||||
tools = AgentSoulToolsConfig.model_validate(
|
||||
{"dify_tools": [{"provider_id": "langgenius/search/search", "credential_type": "unauthorized"}]}
|
||||
{
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "langgenius/search/search",
|
||||
"provider_type": "plugin",
|
||||
"credential_type": "unauthorized",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
with pytest.raises(WorkflowAgentDifyToolsBuildError) as exc_info:
|
||||
@ -948,7 +991,15 @@ def test_provider_level_entry_unknown_provider_maps_to_declaration_not_found():
|
||||
tool_runtime_provider=FakeRuntimeProvider(_tool()), provider_tools_lister=lister
|
||||
)
|
||||
tools = AgentSoulToolsConfig.model_validate(
|
||||
{"dify_tools": [{"provider_id": "langgenius/search/search", "credential_type": "unauthorized"}]}
|
||||
{
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "langgenius/search/search",
|
||||
"provider_type": "plugin",
|
||||
"credential_type": "unauthorized",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
with pytest.raises(WorkflowAgentDifyToolsBuildError) as exc_info:
|
||||
|
||||
@ -667,6 +667,7 @@ def test_builds_workflow_run_request_with_dify_plugin_tools_layer(monkeypatch: p
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "langgenius/time/time",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "current_time",
|
||||
"credential_type": "unauthorized",
|
||||
}
|
||||
|
||||
@ -295,8 +295,16 @@ def test_publish_validation_dedupes_provider_level_tool_entries():
|
||||
),
|
||||
tools={
|
||||
"dify_tools": [
|
||||
{"provider_id": "langgenius/duckduckgo/duckduckgo", "credential_type": "unauthorized"},
|
||||
{"provider_id": "langgenius/duckduckgo/duckduckgo", "credential_type": "unauthorized"},
|
||||
{
|
||||
"provider_id": "langgenius/duckduckgo/duckduckgo",
|
||||
"provider_type": "plugin",
|
||||
"credential_type": "unauthorized",
|
||||
},
|
||||
{
|
||||
"provider_id": "langgenius/duckduckgo/duckduckgo",
|
||||
"provider_type": "plugin",
|
||||
"credential_type": "unauthorized",
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
@ -321,9 +329,14 @@ def test_publish_validation_accepts_provider_level_plus_explicit_tool_entry():
|
||||
),
|
||||
tools={
|
||||
"dify_tools": [
|
||||
{"provider_id": "langgenius/duckduckgo/duckduckgo", "credential_type": "unauthorized"},
|
||||
{
|
||||
"provider_id": "langgenius/duckduckgo/duckduckgo",
|
||||
"provider_type": "plugin",
|
||||
"credential_type": "unauthorized",
|
||||
},
|
||||
{
|
||||
"provider_id": "langgenius/duckduckgo/duckduckgo",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "ddg_search",
|
||||
"credential_type": "unauthorized",
|
||||
},
|
||||
|
||||
@ -78,6 +78,7 @@ def test_make_portable_agent_package_strips_workspace_credentials_and_assets() -
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "langgenius/google/google",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "search",
|
||||
"credential_type": "api-key",
|
||||
"credential_ref": {"type": "tool", "id": "tool-secret"},
|
||||
@ -196,6 +197,7 @@ def test_import_warnings_cover_runtime_setup_removed_from_package(monkeypatch) -
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "langgenius/google/google",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "search",
|
||||
"credential_type": "unauthorized",
|
||||
}
|
||||
@ -513,10 +515,15 @@ def test_extract_package_dependencies_covers_model_tools_and_knowledge(monkeypat
|
||||
"model": {"plugin_id": "model-plugin", "model_provider": "provider/model", "model": "model"},
|
||||
"tools": {
|
||||
"dify_tools": [
|
||||
{"provider_id": "provider/tool", "credential_type": "unauthorized"},
|
||||
{
|
||||
"provider_id": "provider/tool",
|
||||
"provider_type": "plugin",
|
||||
"credential_type": "unauthorized",
|
||||
},
|
||||
{
|
||||
"plugin_id": "plugin-id",
|
||||
"provider": "fallback-provider",
|
||||
"provider_type": "plugin",
|
||||
"credential_type": "unauthorized",
|
||||
},
|
||||
]
|
||||
|
||||
@ -199,6 +199,7 @@ def test_provider_all_tools_mention_resolves_against_provider_level_entry():
|
||||
{
|
||||
"plugin_id": "langgenius/duckduckgo",
|
||||
"provider": "duckduckgo",
|
||||
"provider_type": "plugin",
|
||||
"credential_type": "unauthorized",
|
||||
}
|
||||
]
|
||||
|
||||
@ -140,6 +140,7 @@ def soul() -> AgentSoulConfig:
|
||||
{
|
||||
"plugin_id": "langgenius/tavily",
|
||||
"provider": "tavily",
|
||||
"provider_type": "plugin",
|
||||
"tool_name": "tavily_search",
|
||||
"credential_type": "unauthorized",
|
||||
},
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type {
|
||||
AgentKnowledgeDatasetConfig,
|
||||
AgentSoulConfig,
|
||||
AgentSoulDifyToolConfig,
|
||||
} from '@dify/contracts/api/console/agent/types.gen'
|
||||
import type {
|
||||
ConsoleSegmentListResponse,
|
||||
@ -713,14 +714,15 @@ const getStableModelResource = (context: SeedContext): StableModel | undefined =
|
||||
const getToolResource = (context: SeedContext, displayName: string) =>
|
||||
context.resources.get(`tool:${displayName}`) as ToolResource | undefined
|
||||
|
||||
const toolConfig = (tool: ToolResource) => ({
|
||||
credential_type: 'unauthorized' as const,
|
||||
enabled: true,
|
||||
provider_id: tool.providerName,
|
||||
provider_type: 'builtin',
|
||||
runtime_parameters: {},
|
||||
tool_name: tool.toolName,
|
||||
})
|
||||
const toolConfig = (tool: ToolResource) =>
|
||||
({
|
||||
credential_type: 'unauthorized',
|
||||
enabled: true,
|
||||
provider_id: tool.providerName,
|
||||
provider_type: 'builtin',
|
||||
runtime_parameters: {},
|
||||
tool_name: tool.toolName,
|
||||
}) satisfies AgentSoulDifyToolConfig
|
||||
|
||||
const saveSeededAgentComposer = async ({
|
||||
agentId,
|
||||
|
||||
@ -3875,7 +3875,7 @@
|
||||
},
|
||||
"web/app/components/tools/types.ts": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 4
|
||||
"count": 3
|
||||
},
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 4
|
||||
|
||||
@ -1579,7 +1579,7 @@ export type AgentSoulDifyToolConfig = {
|
||||
plugin_id?: string | null
|
||||
provider?: string | null
|
||||
provider_id?: string | null
|
||||
provider_type?: string
|
||||
provider_type: ToolProviderType
|
||||
runtime_parameters?: {
|
||||
[key: string]:
|
||||
| string
|
||||
@ -1744,6 +1744,15 @@ export type AgentSoulDifyToolCredentialRef = {
|
||||
type?: 'provider' | 'tool'
|
||||
}
|
||||
|
||||
export type ToolProviderType =
|
||||
| 'api'
|
||||
| 'app'
|
||||
| 'builtin'
|
||||
| 'dataset-retrieval'
|
||||
| 'mcp'
|
||||
| 'plugin'
|
||||
| 'workflow'
|
||||
|
||||
export type OutputErrorStrategy = 'default_value' | 'fail_branch' | 'stop'
|
||||
|
||||
export type DeclaredOutputRetryConfig = {
|
||||
|
||||
@ -2065,6 +2065,21 @@ export const zAgentSoulDifyToolCredentialRef = z.object({
|
||||
type: z.enum(['provider', 'tool']).optional().default('tool'),
|
||||
})
|
||||
|
||||
/**
|
||||
* ToolProviderType
|
||||
*
|
||||
* Enum class for tool provider
|
||||
*/
|
||||
export const zToolProviderType = z.enum([
|
||||
'api',
|
||||
'app',
|
||||
'builtin',
|
||||
'dataset-retrieval',
|
||||
'mcp',
|
||||
'plugin',
|
||||
'workflow',
|
||||
])
|
||||
|
||||
/**
|
||||
* AgentSoulDifyToolConfig
|
||||
*
|
||||
@ -2087,7 +2102,7 @@ export const zAgentSoulDifyToolConfig = z.object({
|
||||
plugin_id: z.string().max(255).nullish(),
|
||||
provider: z.string().max(255).nullish(),
|
||||
provider_id: z.string().max(255).nullish(),
|
||||
provider_type: z.string().optional().default('plugin'),
|
||||
provider_type: zToolProviderType,
|
||||
runtime_parameters: z
|
||||
.record(
|
||||
z.string(),
|
||||
|
||||
@ -2813,7 +2813,7 @@ export type AgentSoulDifyToolConfig = {
|
||||
plugin_id?: string | null
|
||||
provider?: string | null
|
||||
provider_id?: string | null
|
||||
provider_type?: string
|
||||
provider_type: ToolProviderType
|
||||
runtime_parameters?: {
|
||||
[key: string]:
|
||||
| string
|
||||
@ -2964,6 +2964,15 @@ export type AgentSoulDifyToolCredentialRef = {
|
||||
type?: 'provider' | 'tool'
|
||||
}
|
||||
|
||||
export type ToolProviderType =
|
||||
| 'api'
|
||||
| 'app'
|
||||
| 'builtin'
|
||||
| 'dataset-retrieval'
|
||||
| 'mcp'
|
||||
| 'plugin'
|
||||
| 'workflow'
|
||||
|
||||
export type StringSource = {
|
||||
selector?: Array<string>
|
||||
type: ValueSourceType
|
||||
|
||||
@ -3682,6 +3682,21 @@ export const zAgentSoulDifyToolCredentialRef = z.object({
|
||||
type: z.enum(['provider', 'tool']).optional().default('tool'),
|
||||
})
|
||||
|
||||
/**
|
||||
* ToolProviderType
|
||||
*
|
||||
* Enum class for tool provider
|
||||
*/
|
||||
export const zToolProviderType = z.enum([
|
||||
'api',
|
||||
'app',
|
||||
'builtin',
|
||||
'dataset-retrieval',
|
||||
'mcp',
|
||||
'plugin',
|
||||
'workflow',
|
||||
])
|
||||
|
||||
/**
|
||||
* AgentSoulDifyToolConfig
|
||||
*
|
||||
@ -3704,7 +3719,7 @@ export const zAgentSoulDifyToolConfig = z.object({
|
||||
plugin_id: z.string().max(255).nullish(),
|
||||
provider: z.string().max(255).nullish(),
|
||||
provider_id: z.string().max(255).nullish(),
|
||||
provider_type: z.string().optional().default('plugin'),
|
||||
provider_type: zToolProviderType,
|
||||
runtime_parameters: z
|
||||
.record(
|
||||
z.string(),
|
||||
|
||||
@ -943,7 +943,7 @@ export type AgentSoulDifyToolConfig = {
|
||||
plugin_id?: string | null
|
||||
provider?: string | null
|
||||
provider_id?: string | null
|
||||
provider_type?: string
|
||||
provider_type: ToolProviderType
|
||||
runtime_parameters?: {
|
||||
[key: string]:
|
||||
| string
|
||||
@ -1063,6 +1063,15 @@ export type AgentSoulDifyToolCredentialRef = {
|
||||
type?: 'provider' | 'tool'
|
||||
}
|
||||
|
||||
export type ToolProviderType =
|
||||
| 'api'
|
||||
| 'app'
|
||||
| 'builtin'
|
||||
| 'dataset-retrieval'
|
||||
| 'mcp'
|
||||
| 'plugin'
|
||||
| 'workflow'
|
||||
|
||||
export type AgentModerationIoConfig = {
|
||||
enabled?: boolean
|
||||
preset_response?: string | null
|
||||
|
||||
@ -1261,6 +1261,21 @@ export const zAgentSoulDifyToolCredentialRef = z.object({
|
||||
type: z.enum(['provider', 'tool']).optional().default('tool'),
|
||||
})
|
||||
|
||||
/**
|
||||
* ToolProviderType
|
||||
*
|
||||
* Enum class for tool provider
|
||||
*/
|
||||
export const zToolProviderType = z.enum([
|
||||
'api',
|
||||
'app',
|
||||
'builtin',
|
||||
'dataset-retrieval',
|
||||
'mcp',
|
||||
'plugin',
|
||||
'workflow',
|
||||
])
|
||||
|
||||
/**
|
||||
* AgentSoulDifyToolConfig
|
||||
*
|
||||
@ -1283,7 +1298,7 @@ export const zAgentSoulDifyToolConfig = z.object({
|
||||
plugin_id: z.string().max(255).nullish(),
|
||||
provider: z.string().max(255).nullish(),
|
||||
provider_id: z.string().max(255).nullish(),
|
||||
provider_type: z.string().optional().default('plugin'),
|
||||
provider_type: zToolProviderType,
|
||||
runtime_parameters: z
|
||||
.record(
|
||||
z.string(),
|
||||
|
||||
@ -22,6 +22,7 @@ import AppIcon from '@/app/components/base/app-icon'
|
||||
import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import { parseToolProviderType } from '@/app/components/tools/provider-type'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import {
|
||||
addDefaultValue,
|
||||
@ -115,16 +116,17 @@ const AgentTools: FC = () => {
|
||||
? toolParametersToFormSchemas(currToolWithConfigs.parameters)
|
||||
: []
|
||||
const paramsWithDefaultValue = addDefaultValue(tool.params, formSchemas)
|
||||
const providerType = parseToolProviderType(tool.provider_type)
|
||||
return {
|
||||
provider_id: tool.provider_id,
|
||||
provider_type: tool.provider_type as CollectionType,
|
||||
provider_type: providerType,
|
||||
provider_name: tool.provider_name,
|
||||
tool_name: tool.tool_name,
|
||||
tool_label: tool.tool_label,
|
||||
tool_parameters: paramsWithDefaultValue,
|
||||
notAuthor: !tool.is_team_authorization,
|
||||
enabled: true,
|
||||
type: tool.provider_type as CollectionType,
|
||||
type: providerType,
|
||||
}
|
||||
}
|
||||
const handleSelectTool = (tool: ToolDefaultValue) => {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { PluginPayload } from '../../types'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AuthCategory, CredentialTypeEnum } from '../../types'
|
||||
@ -57,7 +58,7 @@ const toolPayload = {
|
||||
category: AuthCategory.tool,
|
||||
provider: 'test-provider',
|
||||
providerType: 'builtin',
|
||||
}
|
||||
} satisfies PluginPayload
|
||||
|
||||
describe('use-credential hooks', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { CollectionType } from '../../tools/types'
|
||||
import type { CollectionProviderType } from '../../tools/types'
|
||||
import type { PluginDetail } from '../types'
|
||||
|
||||
export type { AddApiKeyButtonProps } from './authorize/add-api-key-button'
|
||||
@ -14,7 +14,7 @@ export enum AuthCategory {
|
||||
export type PluginPayload = {
|
||||
category: AuthCategory
|
||||
provider: string
|
||||
providerType?: CollectionType | string
|
||||
providerType?: CollectionProviderType
|
||||
detail?: PluginDetail
|
||||
}
|
||||
|
||||
|
||||
@ -346,7 +346,7 @@ const DetailHeader = ({
|
||||
pluginPayload={{
|
||||
provider: provider?.name || '',
|
||||
category: AuthCategory.tool,
|
||||
providerType: provider?.type || '',
|
||||
providerType: provider?.type,
|
||||
detail,
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { PluginPayload } from '@/app/components/plugins/plugin-auth/types'
|
||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
@ -12,7 +13,7 @@ vi.mock('@/app/components/plugins/plugin-auth', () => ({
|
||||
pluginPayload,
|
||||
credentialId,
|
||||
}: {
|
||||
pluginPayload: { provider: string; providerType: string }
|
||||
pluginPayload: PluginPayload
|
||||
credentialId?: string
|
||||
}) => (
|
||||
<div data-testid="plugin-auth-in-agent">
|
||||
|
||||
5
web/app/components/tools/provider-type.ts
Normal file
5
web/app/components/tools/provider-type.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import type { ToolProviderType } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import { zToolProviderType } from '@dify/contracts/api/console/workspaces/zod.gen'
|
||||
|
||||
export const parseToolProviderType = (providerType: unknown): ToolProviderType =>
|
||||
zToolProviderType.parse(providerType)
|
||||
@ -1,3 +1,7 @@
|
||||
import type {
|
||||
DatasourceProviderType,
|
||||
ToolProviderType,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { VarType } from '../workflow/types'
|
||||
|
||||
type LocalizedText<T = string> = {
|
||||
@ -27,16 +31,20 @@ export type Credential = {
|
||||
api_key_query_param?: string
|
||||
}
|
||||
|
||||
export enum CollectionType {
|
||||
all = 'all',
|
||||
builtIn = 'builtin',
|
||||
custom = 'api',
|
||||
model = 'model',
|
||||
workflow = 'workflow',
|
||||
mcp = 'mcp',
|
||||
datasource = 'datasource',
|
||||
trigger = 'trigger',
|
||||
}
|
||||
export const CollectionType = {
|
||||
all: 'all',
|
||||
builtIn: 'builtin',
|
||||
custom: 'api',
|
||||
model: 'model',
|
||||
workflow: 'workflow',
|
||||
mcp: 'mcp',
|
||||
datasource: 'datasource',
|
||||
trigger: 'trigger',
|
||||
} as const
|
||||
|
||||
export type CollectionType = (typeof CollectionType)[keyof typeof CollectionType]
|
||||
|
||||
export type CollectionProviderType = CollectionType | DatasourceProviderType | ToolProviderType
|
||||
|
||||
export type Emoji = {
|
||||
background: string
|
||||
@ -51,7 +59,7 @@ export type Collection = {
|
||||
icon: string | Emoji
|
||||
icon_dark?: string | Emoji
|
||||
label: LocalizedText
|
||||
type: CollectionType | string
|
||||
type: CollectionProviderType
|
||||
team_credentials: Record<string, any>
|
||||
is_team_authorization: boolean
|
||||
allow_delete: boolean
|
||||
|
||||
@ -4,7 +4,6 @@ import { screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/query'
|
||||
import { PluginCategoryEnum } from '@/app/components/plugins/types'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import { renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
@ -43,7 +42,7 @@ const createToolProvider = (overrides: Partial<ToolWithProvider> = {}): ToolWith
|
||||
description: { en_US: 'desc', zh_Hans: '描述' },
|
||||
icon: 'icon',
|
||||
label: { en_US: 'File Source', zh_Hans: '文件源' },
|
||||
type: CollectionType.datasource,
|
||||
type: 'local_file',
|
||||
team_credentials: {},
|
||||
is_team_authorization: false,
|
||||
allow_delete: false,
|
||||
|
||||
@ -77,7 +77,7 @@ describe('ToolBrowser', () => {
|
||||
customTools={[
|
||||
createToolProvider({
|
||||
id: 'provider-custom',
|
||||
type: 'custom',
|
||||
type: CollectionType.custom,
|
||||
label: { en_US: 'Custom Provider', zh_Hans: 'Custom Provider' },
|
||||
}),
|
||||
]}
|
||||
@ -89,7 +89,7 @@ describe('ToolBrowser', () => {
|
||||
expect(screen.getByText('Built In Provider')).toBeInTheDocument()
|
||||
expect(screen.getByText('Custom Provider')).toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByText('workflow.tabs.customTool'))
|
||||
await user.click(screen.getByRole('button', { name: 'workflow.tabs.customTool' }))
|
||||
|
||||
expect(screen.getByText('Custom Provider')).toBeInTheDocument()
|
||||
expect(screen.queryByText('Built In Provider')).not.toBeInTheDocument()
|
||||
|
||||
@ -71,7 +71,7 @@ describe('createToolListData', () => {
|
||||
const result = createToolListData(
|
||||
[
|
||||
createToolProvider({ id: 'workflow', type: CollectionType.workflow }),
|
||||
createToolProvider({ id: 'data-source', type: CollectionType.datasource }),
|
||||
createToolProvider({ id: 'data-source', type: 'local_file' }),
|
||||
],
|
||||
() => 'A',
|
||||
)
|
||||
@ -90,19 +90,19 @@ describe('createToolListData', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('merges providers across letters and keeps MCP and unknown provider identities explicit', () => {
|
||||
it('merges providers across letters and keeps MCP and plugin provider identities explicit', () => {
|
||||
const lettersByToolId: Record<string, string> = {
|
||||
'author-a': 'A',
|
||||
'author-b': 'B',
|
||||
mcp: 'C',
|
||||
unknown: 'D',
|
||||
plugin: 'D',
|
||||
}
|
||||
const result = createToolListData(
|
||||
[
|
||||
createToolProvider({ id: 'author-a', author: 'Dify' }),
|
||||
createToolProvider({ id: 'author-b', author: 'Dify' }),
|
||||
createToolProvider({ id: 'mcp', type: CollectionType.mcp }),
|
||||
createToolProvider({ id: 'unknown', author: 'Future', type: 'future-provider' }),
|
||||
createToolProvider({ id: 'plugin', author: 'Plugin Author', type: 'plugin' }),
|
||||
],
|
||||
(tool) => lettersByToolId[tool.id] ?? '#',
|
||||
)
|
||||
@ -123,8 +123,8 @@ describe('createToolListData', () => {
|
||||
},
|
||||
{
|
||||
kind: 'author',
|
||||
author: 'Future',
|
||||
tools: [expect.objectContaining({ id: 'unknown' })],
|
||||
author: 'Plugin Author',
|
||||
tools: [expect.objectContaining({ id: 'plugin' })],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
@ -12,7 +12,7 @@ const createDataSourceItem = (overrides: Partial<DataSourceItem> = {}): DataSour
|
||||
provider: 'provider-a',
|
||||
declaration: {
|
||||
credentials_schema: [{ name: 'api_key' }],
|
||||
provider_type: 'hosted',
|
||||
provider_type: 'local_file',
|
||||
identity: {
|
||||
author: 'Dify',
|
||||
description: createLocalizedText('Datasource provider'),
|
||||
@ -58,7 +58,7 @@ describe('transformDataSourceToTool', () => {
|
||||
description: createLocalizedText('Datasource provider'),
|
||||
icon: 'provider-icon',
|
||||
label: createLocalizedText('Provider A'),
|
||||
type: 'hosted',
|
||||
type: 'local_file',
|
||||
allow_delete: true,
|
||||
is_authorized: true,
|
||||
is_team_authorization: true,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { OnSelectBlock, ToolWithProvider } from '../types'
|
||||
import type { DataSourceDefaultValue, ToolDefaultValue } from './types'
|
||||
import type { ListRef } from '@/app/components/workflow/block-selector/marketplace-plugin/list'
|
||||
import { zDatasourceProviderType } from '@dify/contracts/api/console/workspaces/zod.gen'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useDebounce } from 'ahooks'
|
||||
@ -60,7 +61,7 @@ function DataSources({
|
||||
(_: BlockEnum, toolDefaultValue: ToolDefaultValue) => {
|
||||
let defaultValue: DataSourceDefaultValue = {
|
||||
plugin_id: toolDefaultValue?.provider_id,
|
||||
provider_type: toolDefaultValue?.provider_type,
|
||||
provider_type: zDatasourceProviderType.parse(toolDefaultValue.provider_type),
|
||||
provider_name: toolDefaultValue?.provider_name,
|
||||
datasource_name: toolDefaultValue?.tool_name,
|
||||
datasource_label: toolDefaultValue?.tool_label,
|
||||
|
||||
@ -1,9 +1,17 @@
|
||||
import type { DatasourceProviderType } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { ToolWithProvider } from '../types'
|
||||
import { pinyin } from 'pinyin-pro'
|
||||
import { CollectionType } from '../../tools/types'
|
||||
|
||||
type ToolCategoryGroup = 'custom' | 'data-source' | 'mcp' | 'workflow'
|
||||
|
||||
const datasourceProviderTypes: Record<DatasourceProviderType, true> = {
|
||||
local_file: true,
|
||||
online_document: true,
|
||||
online_drive: true,
|
||||
website_crawl: true,
|
||||
}
|
||||
|
||||
type AuthorToolGroup = {
|
||||
kind: 'author'
|
||||
author: string
|
||||
@ -114,10 +122,14 @@ function addToolToAuthorGroup(bucket: LetterBucket, tool: ToolWithProvider) {
|
||||
function getToolCategoryGroup(type: ToolWithProvider['type']): ToolCategoryGroup | undefined {
|
||||
if (type === CollectionType.custom) return 'custom'
|
||||
if (type === CollectionType.workflow) return 'workflow'
|
||||
if (type === CollectionType.datasource) return 'data-source'
|
||||
if (isDatasourceProviderType(type)) return 'data-source'
|
||||
if (type === CollectionType.mcp) return 'mcp'
|
||||
}
|
||||
|
||||
function isDatasourceProviderType(type: ToolWithProvider['type']): type is DatasourceProviderType {
|
||||
return Object.hasOwn(datasourceProviderTypes, type)
|
||||
}
|
||||
|
||||
function mergeGroupsByProvider(buckets: LetterBucket[]) {
|
||||
const groups: ToolGroup[] = []
|
||||
const authorGroups = new Map<string, AuthorToolGroup>()
|
||||
|
||||
@ -50,7 +50,7 @@ describe('ToolListTreeView', () => {
|
||||
tools: [
|
||||
createToolProvider({
|
||||
id: 'custom-provider',
|
||||
type: 'custom',
|
||||
type: CollectionType.custom,
|
||||
label: { en_US: 'Custom Provider', zh_Hans: 'Custom Provider' },
|
||||
}),
|
||||
],
|
||||
@ -83,7 +83,7 @@ describe('ToolListTreeView', () => {
|
||||
tools: [
|
||||
createToolProvider({
|
||||
id: 'data-source-provider',
|
||||
type: CollectionType.datasource,
|
||||
type: 'local_file',
|
||||
label: { en_US: 'Data Source Provider', zh_Hans: 'Data Source Provider' },
|
||||
}),
|
||||
],
|
||||
|
||||
@ -62,7 +62,7 @@ const TriggerPluginActionItem: FC<Props> = ({
|
||||
onSelect(BlockEnum.TriggerPlugin, {
|
||||
plugin_id: provider.plugin_id,
|
||||
provider_id: provider.name,
|
||||
provider_type: provider.type as string,
|
||||
provider_type: provider.type,
|
||||
provider_name: provider.name,
|
||||
event_name: payload.name,
|
||||
event_label: payload.label[language]!,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { AgentInviteOptionResponse } from '@dify/contracts/api/console/agent/types.gen'
|
||||
import type { DatasourceProviderType } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type {
|
||||
ParametersSchema,
|
||||
PluginMeta,
|
||||
@ -6,7 +7,7 @@ import type {
|
||||
SupportedCreationMethods,
|
||||
TriggerEvent,
|
||||
} from '../../plugins/types'
|
||||
import type { Collection, Event } from '../../tools/types'
|
||||
import type { Collection, CollectionProviderType, Event } from '../../tools/types'
|
||||
import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
export const TabType = {
|
||||
@ -48,7 +49,7 @@ export type BlockClassification = (typeof BlockClassification)[keyof typeof Bloc
|
||||
|
||||
type PluginCommonDefaultValue = {
|
||||
provider_id: string
|
||||
provider_type: string
|
||||
provider_type: CollectionProviderType
|
||||
provider_name: string
|
||||
}
|
||||
|
||||
@ -87,7 +88,6 @@ export type ToolDefaultValue = PluginCommonDefaultValue & {
|
||||
|
||||
export type DataSourceDefaultValue = Omit<PluginCommonDefaultValue, 'provider_id'> & {
|
||||
plugin_id: string
|
||||
provider_type: string
|
||||
provider_name: string
|
||||
datasource_name: string
|
||||
datasource_label: string
|
||||
@ -145,7 +145,7 @@ export type DataSourceItem = {
|
||||
provider: string
|
||||
declaration: {
|
||||
credentials_schema: unknown[]
|
||||
provider_type: string
|
||||
provider_type: DatasourceProviderType
|
||||
identity: {
|
||||
author: string
|
||||
description: TypeWithI18N
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { CommonNodeType } from '../../types'
|
||||
import type { CollectionProviderType } from '@/app/components/tools/types'
|
||||
import { act } from '@testing-library/react'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { renderWorkflowHook } from '../../__tests__/workflow-test-env'
|
||||
@ -27,7 +28,8 @@ vi.mock('@/service/use-tools', () => ({
|
||||
useAllCustomTools: (enabled: boolean) => mockCustomTools(enabled),
|
||||
useAllWorkflowTools: (enabled: boolean) => mockWorkflowTools(enabled),
|
||||
useAllMCPTools: (enabled: boolean) => mockMcpTools(enabled),
|
||||
useInvalidToolsByType: (providerType?: string) => mockInvalidToolsByType(providerType),
|
||||
useInvalidToolsByType: (providerType?: CollectionProviderType) =>
|
||||
mockInvalidToolsByType(providerType),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-triggers', () => ({
|
||||
@ -164,11 +166,11 @@ describe('useNodePluginInstallation', () => {
|
||||
expect(result.current.shouldDim).toBe(false)
|
||||
})
|
||||
|
||||
it('should keep unknown tool collection types installable without collection state', () => {
|
||||
it('should keep plugin tool collections installable without collection state', () => {
|
||||
const { result } = renderWorkflowHook(() =>
|
||||
useNodePluginInstallation(
|
||||
makeToolNode({
|
||||
provider_type: 'unknown' as CollectionType,
|
||||
provider_type: 'plugin',
|
||||
plugin_unique_identifier: undefined,
|
||||
plugin_id: undefined,
|
||||
provider_id: 'legacy-provider',
|
||||
|
||||
@ -61,7 +61,7 @@ type Props = Readonly<{
|
||||
showManageInputField?: boolean
|
||||
onManageInputField?: () => void
|
||||
extraParams?: Record<string, unknown>
|
||||
providerType?: string
|
||||
providerType?: 'tool' | 'trigger'
|
||||
disableVariableInsertion?: boolean
|
||||
}>
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ describe('workflow-panel helpers', () => {
|
||||
const dataSourceData = asNodeData({
|
||||
type: BlockEnum.DataSource,
|
||||
plugin_id: 'source-1',
|
||||
provider_type: 'remote',
|
||||
provider_type: 'online_document',
|
||||
})
|
||||
const triggerPlugins = [{ plugin_id: 'trigger-1', id: '1' }]
|
||||
const dataSources = [{ plugin_id: 'source-1' }]
|
||||
|
||||
@ -592,7 +592,7 @@ describe('workflow-panel index', () => {
|
||||
createData({
|
||||
type: BlockEnum.DataSource,
|
||||
plugin_id: 'source-1',
|
||||
provider_type: 'remote',
|
||||
provider_type: 'online_document',
|
||||
}) as never
|
||||
}
|
||||
>
|
||||
|
||||
@ -637,7 +637,7 @@ const BasePanel: FC<BasePanelProps> = ({ id, data, children }) => {
|
||||
className="px-4 pb-2"
|
||||
pluginPayload={{
|
||||
provider: currToolCollection?.name || '',
|
||||
providerType: currToolCollection?.type || '',
|
||||
providerType: currToolCollection?.type,
|
||||
category: AuthCategory.tool,
|
||||
detail: currToolCollection as any,
|
||||
}}
|
||||
@ -647,7 +647,7 @@ const BasePanel: FC<BasePanelProps> = ({ id, data, children }) => {
|
||||
<AuthorizedInNode
|
||||
pluginPayload={{
|
||||
provider: currToolCollection?.name || '',
|
||||
providerType: currToolCollection?.type || '',
|
||||
providerType: currToolCollection?.type,
|
||||
category: AuthCategory.tool,
|
||||
detail: currToolCollection as any,
|
||||
}}
|
||||
|
||||
@ -26,7 +26,7 @@ vi.mock('@/app/components/workflow/block-selector', () => ({
|
||||
onClick={() =>
|
||||
onSelect(BlockEnum.DataSource, {
|
||||
plugin_id: 'plugin-id',
|
||||
provider_type: 'datasource',
|
||||
provider_type: 'local_file',
|
||||
provider_name: 'file',
|
||||
datasource_name: 'local-file',
|
||||
datasource_label: 'Local File',
|
||||
@ -89,7 +89,7 @@ describe('DataSourceEmptyNode', () => {
|
||||
|
||||
expect(handleReplaceNode).toHaveBeenCalledWith(BlockEnum.DataSource, {
|
||||
plugin_id: 'plugin-id',
|
||||
provider_type: 'datasource',
|
||||
provider_type: 'local_file',
|
||||
provider_name: 'file',
|
||||
datasource_name: 'local-file',
|
||||
datasource_label: 'Local File',
|
||||
|
||||
@ -25,7 +25,7 @@ const createNodeData = (overrides: Partial<DataSourceNodeType> = {}): DataSource
|
||||
desc: '',
|
||||
type: BlockEnum.DataSource,
|
||||
plugin_id: 'plugin-id',
|
||||
provider_type: 'datasource',
|
||||
provider_type: 'local_file',
|
||||
provider_name: 'file',
|
||||
datasource_name: 'local-file',
|
||||
datasource_label: 'Local File',
|
||||
|
||||
@ -106,7 +106,7 @@ const createData = (overrides: Partial<DataSourceNodeType> = {}): DataSourceNode
|
||||
desc: '',
|
||||
type: BlockEnum.DataSource,
|
||||
plugin_id: 'plugin-1',
|
||||
provider_type: 'remote',
|
||||
provider_type: 'online_document',
|
||||
provider_name: 'provider',
|
||||
datasource_name: 'source-a',
|
||||
datasource_label: 'Source A',
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { DatasourceProviderType } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { Dispatch, SetStateAction } from 'react'
|
||||
import type { ResourceVarInputs } from '../_base/types'
|
||||
import type { CommonNodeType, Node } from '@/app/components/workflow/types'
|
||||
@ -13,7 +14,7 @@ export type ToolVarInputs = ResourceVarInputs
|
||||
export type DataSourceNodeType = CommonNodeType & {
|
||||
fileExtensions?: string[]
|
||||
plugin_id: string
|
||||
provider_type: string
|
||||
provider_type: DatasourceProviderType
|
||||
provider_name: string
|
||||
datasource_name: string
|
||||
datasource_label: string
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import type { DataSourceNodeType } from '../../nodes/data-source/types'
|
||||
import type { ToolWithProvider } from '../../types'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { BlockEnum } from '../../types'
|
||||
import { getDataSourceCheckParams } from '../data-source'
|
||||
|
||||
@ -23,7 +22,7 @@ function createDataSourceData(overrides: Partial<DataSourceNodeType> = {}): Data
|
||||
desc: '',
|
||||
type: BlockEnum.DataSource,
|
||||
plugin_id: 'plugin-ds-1',
|
||||
provider_type: CollectionType.builtIn,
|
||||
provider_type: 'local_file',
|
||||
datasource_name: 'mysql_query',
|
||||
datasource_parameters: {},
|
||||
datasource_configurations: {},
|
||||
@ -70,7 +69,7 @@ describe('getDataSourceCheckParams', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('should mark notAuthed for builtin datasource without authorization', () => {
|
||||
it('should mark an unauthorized datasource as not authed', () => {
|
||||
const result = getDataSourceCheckParams(
|
||||
createDataSourceData(),
|
||||
[createDataSourceCollection()],
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import type { DataSourceNodeType } from '../nodes/data-source/types'
|
||||
import type { InputVar, ToolWithProvider } from '../types'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||
|
||||
export const getDataSourceCheckParams = (
|
||||
@ -8,8 +7,7 @@ export const getDataSourceCheckParams = (
|
||||
dataSourceList: ToolWithProvider[],
|
||||
language: string,
|
||||
) => {
|
||||
const { plugin_id, provider_type, datasource_name } = toolData
|
||||
const isBuiltIn = provider_type === CollectionType.builtIn
|
||||
const { plugin_id, datasource_name } = toolData
|
||||
const currentDataSource = dataSourceList.find((item) => item.plugin_id === plugin_id)
|
||||
const currentDataSourceItem = currentDataSource?.tools.find(
|
||||
(tool) => tool.name === datasource_name,
|
||||
@ -32,7 +30,7 @@ export const getDataSourceCheckParams = (
|
||||
})
|
||||
return formInputs
|
||||
})(),
|
||||
notAuthed: isBuiltIn && !!currentDataSource?.allow_delete && !currentDataSource?.is_authorized,
|
||||
notAuthed: !!currentDataSource?.allow_delete && !currentDataSource?.is_authorized,
|
||||
language,
|
||||
}
|
||||
}
|
||||
|
||||
@ -428,6 +428,7 @@ describe('agent composer store conversions', () => {
|
||||
kind: 'provider',
|
||||
name: 'duckduckgo',
|
||||
iconClassName: 'i-custom-public-other-default-tool-icon text-text-tertiary',
|
||||
providerType: 'builtin',
|
||||
credentialVariant: 'none',
|
||||
actions: [
|
||||
{
|
||||
|
||||
@ -287,7 +287,7 @@ const toDifyToolConfigs = (
|
||||
enabled: true,
|
||||
provider: tool.name,
|
||||
provider_id: tool.id,
|
||||
provider_type: tool.providerType ?? 'builtin',
|
||||
provider_type: tool.providerType,
|
||||
tool_name: action.toolName,
|
||||
runtime_parameters: toToolRuntimeParameters(toolSettings[action.id]),
|
||||
credential_type: credentialType,
|
||||
|
||||
@ -2,6 +2,7 @@ import type {
|
||||
AgentKnowledgeDatasetConfig,
|
||||
AgentSoulAppFeaturesConfig,
|
||||
AgentSoulModelConfig,
|
||||
ToolProviderType,
|
||||
} from '@dify/contracts/api/console/agent/types.gen'
|
||||
import type { FileTreeIconType } from '@langgenius/dify-ui/file-tree'
|
||||
import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
@ -95,7 +96,7 @@ export type AgentProviderTool = AgentToolBase & {
|
||||
iconClassName: string
|
||||
icon?: ToolDefaultValue['provider_icon']
|
||||
iconDark?: ToolDefaultValue['provider_icon_dark']
|
||||
providerType?: string
|
||||
providerType: ToolProviderType
|
||||
allowDelete?: boolean
|
||||
credentialId?: string
|
||||
credentialKey?: I18nKeysWithPrefix<'agentV2', 'agentDetail.configure.tools.'>
|
||||
|
||||
@ -151,6 +151,7 @@ describe('agent composer tools store', () => {
|
||||
kind: 'provider',
|
||||
name: 'DuckDuckGo',
|
||||
iconClassName: 'i-simple-icons-duckduckgo',
|
||||
providerType: 'builtin',
|
||||
credentialVariant: 'none',
|
||||
actions: [
|
||||
{
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ToolProviderType } from '@dify/contracts/api/console/agent/types.gen'
|
||||
import type {
|
||||
AgentCliTool,
|
||||
AgentProviderTool,
|
||||
@ -11,7 +12,8 @@ import { syncCliToolReferenceLabels } from '../reference-labels'
|
||||
import { agentComposerDraftAtom } from '../store'
|
||||
import { resolveDraftFieldUpdate } from './utils'
|
||||
|
||||
export type AgentProviderToolDefaultValue = ToolDefaultValue & {
|
||||
export type AgentProviderToolDefaultValue = Omit<ToolDefaultValue, 'provider_type'> & {
|
||||
provider_type: ToolProviderType
|
||||
allowDelete?: boolean
|
||||
credentialType?: AgentProviderTool['credentialType']
|
||||
credentialRequired?: boolean
|
||||
|
||||
@ -198,6 +198,7 @@ const duckDuckGoProviderTool: AgentTool = {
|
||||
name: 'DuckDuckGo',
|
||||
kind: 'provider',
|
||||
iconClassName: 'i-simple-icons-duckduckgo',
|
||||
providerType: 'builtin',
|
||||
credentialKey: 'agentDetail.configure.tools.credential.authOne',
|
||||
credentialVariant: 'authorized',
|
||||
actions: [duckDuckGoSearchAction],
|
||||
|
||||
@ -19,6 +19,7 @@ import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getMarketplaceCategoryUrl } from '@/app/components/plugins/marketplace/utils'
|
||||
import { PluginCategoryEnum } from '@/app/components/plugins/types'
|
||||
import { parseToolProviderType } from '@/app/components/tools/provider-type'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import { ToolType } from '@/app/components/workflow/block-selector/types'
|
||||
@ -493,7 +494,7 @@ function toToolDefaultValue(
|
||||
|
||||
return {
|
||||
provider_id: provider.id,
|
||||
provider_type: provider.type,
|
||||
provider_type: parseToolProviderType(provider.type),
|
||||
provider_name: provider.name,
|
||||
provider_show_name: providerLabel,
|
||||
plugin_id: provider.plugin_id,
|
||||
|
||||
@ -111,6 +111,7 @@ const agentToolsDraft = {
|
||||
kind: 'provider',
|
||||
name: 'DuckDuckGo',
|
||||
iconClassName: 'i-simple-icons-duckduckgo',
|
||||
providerType: 'builtin',
|
||||
credentialKey: 'agentDetail.configure.tools.credential.authOne',
|
||||
credentialVariant: 'none',
|
||||
actions: [
|
||||
@ -144,6 +145,7 @@ const reflectedAgentToolsDraft = {
|
||||
kind: 'provider',
|
||||
name: 'google',
|
||||
iconClassName: 'i-custom-public-other-default-tool-icon',
|
||||
providerType: 'builtin',
|
||||
credentialVariant: 'none',
|
||||
actions: [
|
||||
{
|
||||
@ -165,6 +167,7 @@ const reflectedUnauthorizedNoCredentialDraft = {
|
||||
kind: 'provider',
|
||||
name: 'duckduckgo',
|
||||
iconClassName: 'i-custom-public-other-default-tool-icon',
|
||||
providerType: 'builtin',
|
||||
credentialType: 'unauthorized',
|
||||
credentialVariant: 'unauthorized',
|
||||
actions: [
|
||||
@ -187,6 +190,7 @@ const reflectedUnauthorizedOAuthCredentialTypeDraft = {
|
||||
kind: 'provider',
|
||||
name: 'google',
|
||||
iconClassName: 'i-custom-public-other-default-tool-icon',
|
||||
providerType: 'builtin',
|
||||
credentialType: 'unauthorized',
|
||||
credentialVariant: 'none',
|
||||
actions: [
|
||||
|
||||
@ -15,6 +15,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/pop
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { memo, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { parseToolProviderType } from '@/app/components/tools/provider-type'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { ToolPickerContent } from '@/app/components/workflow/block-selector/tool-picker'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
@ -211,7 +212,7 @@ function useDisplayTools(tools: AgentTool[], providerById: Map<string, ToolWithP
|
||||
displayName: tool.displayName ?? getLocalizedText(provider.label, language) ?? tool.name,
|
||||
icon: tool.icon ?? provider.icon,
|
||||
iconDark: tool.iconDark ?? provider.icon_dark,
|
||||
providerType: tool.providerType ?? provider.type,
|
||||
providerType: tool.providerType,
|
||||
allowDelete: tool.allowDelete ?? provider.allow_delete,
|
||||
credentialKey: providerCredentialType
|
||||
? (tool.credentialKey ?? 'agentDetail.configure.tools.credential.authOne')
|
||||
@ -314,6 +315,7 @@ function AddToolMenu({
|
||||
const toAgentToolDefaultValue = useCallback(
|
||||
(tool: ToolDefaultValue): AgentProviderToolDefaultValue => ({
|
||||
...tool,
|
||||
provider_type: parseToolProviderType(tool.provider_type),
|
||||
allowDelete: (
|
||||
providerById.get(tool.provider_id) ??
|
||||
providerById.get(tool.provider_name) ??
|
||||
|
||||
@ -7,7 +7,6 @@ import type { AgentProviderTool } from '@/features/agent-v2/agent-composer/form-
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import SettingBuiltInTool from '@/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import {
|
||||
agentComposerToolsAtom,
|
||||
agentComposerToolSettingsAtom,
|
||||
@ -28,7 +27,7 @@ const createFallbackToolCollection = (tool: AgentProviderTool): ToolWithProvider
|
||||
icon: tool.icon ?? '',
|
||||
icon_dark: tool.iconDark,
|
||||
label: localize(tool.displayName ?? tool.name),
|
||||
type: (tool.providerType as CollectionType | undefined) ?? CollectionType.builtIn,
|
||||
type: tool.providerType,
|
||||
team_credentials: {},
|
||||
is_team_authorization: true,
|
||||
allow_delete: tool.allowDelete ?? false,
|
||||
|
||||
@ -60,7 +60,7 @@ function UnauthorizedCredentialStatus({
|
||||
() => ({
|
||||
provider: tool.id,
|
||||
category: AuthCategory.tool,
|
||||
providerType: tool.providerType ?? CollectionType.builtIn,
|
||||
providerType: tool.providerType,
|
||||
}),
|
||||
[tool.id, tool.providerType],
|
||||
)
|
||||
@ -129,8 +129,7 @@ function CredentialStatus({
|
||||
credentialType?: AgentProviderTool['credentialType'],
|
||||
) => void
|
||||
}) {
|
||||
const canSwitchCredential =
|
||||
(tool.providerType ?? CollectionType.builtIn) === CollectionType.builtIn && tool.allowDelete
|
||||
const canSwitchCredential = tool.providerType === CollectionType.builtIn && tool.allowDelete
|
||||
const handleAuthorizationItemClick = useCallback(
|
||||
(id: string) => {
|
||||
onCredentialChange(
|
||||
@ -161,7 +160,7 @@ function CredentialStatus({
|
||||
pluginPayload={{
|
||||
provider: tool.id,
|
||||
category: AuthCategory.tool,
|
||||
providerType: tool.providerType ?? CollectionType.builtIn,
|
||||
providerType: tool.providerType,
|
||||
}}
|
||||
credentialId={tool.credentialId}
|
||||
onAuthorizationItemClick={handleAuthorizationItemClick}
|
||||
|
||||
@ -1195,6 +1195,39 @@ describe('AgentPreviewChat', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should preserve the tool provider type in the preview runtime config', async () => {
|
||||
renderPreviewChat({
|
||||
agentSoulConfig: {
|
||||
tools: {
|
||||
dify_tools: [
|
||||
{
|
||||
provider_id: 'workflow-provider',
|
||||
provider_type: 'workflow',
|
||||
tool_name: 'search',
|
||||
credential_type: 'unauthorized',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await waitFor(() => expect(useChatMock).toHaveBeenCalled())
|
||||
|
||||
const config = useChatMock.mock.calls.at(-1)?.[0]
|
||||
expect(config.agent_mode).toEqual(
|
||||
expect.objectContaining({
|
||||
enabled: true,
|
||||
tools: [
|
||||
expect.objectContaining({
|
||||
provider_id: 'workflow-provider',
|
||||
provider_type: 'workflow',
|
||||
tool_name: 'search',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('should enable build chat file upload when chat features file upload is enabled', async () => {
|
||||
renderPreviewChat({
|
||||
agentSoulConfig: {
|
||||
|
||||
@ -132,7 +132,7 @@ export const getAgentSoulInputs = (inputsForm: InputForm[]) => {
|
||||
|
||||
const toAgentTool = (tool: AgentSoulDifyToolConfig) => ({
|
||||
provider_id: tool.provider_id ?? tool.provider ?? tool.plugin_id ?? '',
|
||||
provider_type: tool.provider_type ?? 'builtin',
|
||||
provider_type: tool.provider_type,
|
||||
provider_name: tool.provider ?? '',
|
||||
tool_name: tool.tool_name,
|
||||
tool_label: tool.name ?? tool.tool_name,
|
||||
|
||||
@ -1430,7 +1430,7 @@ export const useFetchDynamicOptions = (
|
||||
provider: string,
|
||||
action: string,
|
||||
parameter: string,
|
||||
provider_type?: string,
|
||||
provider_type?: 'tool' | 'trigger',
|
||||
extra?: Record<string, unknown>,
|
||||
) => {
|
||||
return useMutation({
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { QueryKey, UseQueryOptions } from '@tanstack/react-query'
|
||||
import type {
|
||||
Collection,
|
||||
CollectionProviderType,
|
||||
MCPServerDetail,
|
||||
Tool,
|
||||
WorkflowToolProviderResponse,
|
||||
@ -79,13 +80,13 @@ export const useInvalidateAllMCPTools = () => {
|
||||
return useInvalid(useAllMCPToolsKey)
|
||||
}
|
||||
|
||||
const useInvalidToolsKeyMap: Record<string, QueryKey> = {
|
||||
const useInvalidToolsKeyMap: Partial<Record<CollectionProviderType, QueryKey>> = {
|
||||
[CollectionType.builtIn]: useAllBuiltInToolsKey,
|
||||
[CollectionType.custom]: useAllCustomToolsKey,
|
||||
[CollectionType.workflow]: useAllWorkflowToolsKey,
|
||||
[CollectionType.mcp]: useAllMCPToolsKey,
|
||||
}
|
||||
export const useInvalidToolsByType = (type?: CollectionType | string) => {
|
||||
export const useInvalidToolsByType = (type?: CollectionProviderType) => {
|
||||
const queryKey = type ? useInvalidToolsKeyMap[type] : undefined
|
||||
return useInvalid(queryKey)
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { ToolProviderType } from '@dify/contracts/api/console/agent/types.gen'
|
||||
import type { TagResponse as Tag } from '@dify/contracts/api/console/tags/types.gen'
|
||||
import type { CollectionType } from '@/app/components/tools/types'
|
||||
import type { UploadFileSetting } from '@/app/components/workflow/types'
|
||||
import type { LanguagesSupported } from '@/i18n-config/language'
|
||||
import type { AccessMode } from '@/models/access-control'
|
||||
@ -172,7 +172,7 @@ export type UserInputFormItem =
|
||||
|
||||
export type AgentTool = {
|
||||
provider_id: string
|
||||
provider_type: CollectionType
|
||||
provider_type: ToolProviderType
|
||||
provider_name: string
|
||||
tool_name: string
|
||||
tool_label: string
|
||||
|
||||
Loading…
Reference in New Issue
Block a user