chore(agent-v2): sync daily changes (#38298)

Signed-off-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: 盐粒 Yanli <mail@yanli.one>
Co-authored-by: Joel <iamjoel007@gmail.com>
Co-authored-by: zyssyz123 <916125788@qq.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: 盐粒 Yanli <yanli@dify.ai>
Co-authored-by: 林玮 (Jade Lin) <linw1995@icloud.com>
This commit is contained in:
yyh
2026-07-05 08:09:38 +00:00
committed by GitHub
co-authored by 盐粒 Yanli Joel zyssyz123 autofix-ci[bot] 盐粒 Yanli 林玮
parent f8d47616c1
commit 1b3e8e9943
303 changed files with 11391 additions and 1912 deletions
@@ -11,6 +11,7 @@ from dify_agent.layers.dify_plugin import (
DifyPluginToolCredentialType,
DifyPluginToolParameter,
DifyPluginToolParameterForm,
DifyPluginToolParameterType,
DifyPluginToolsLayerConfig,
)
from sqlalchemy import select
@@ -351,7 +352,9 @@ class WorkflowAgentDifyToolsBuilder:
@staticmethod
def _tool_layer_destination(tool_config: AgentSoulDifyToolConfig) -> Literal["plugin", "core"]:
provider_type = ToolProviderType.value_of(tool_config.provider_type)
if provider_type is ToolProviderType.PLUGIN:
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,
@@ -404,7 +407,7 @@ class WorkflowAgentDifyToolsBuilder:
credentials=self._normalize_credentials(runtime.credentials, tool_name=exposed_name),
runtime_parameters=runtime_parameters,
parameters=parameters,
parameters_json_schema=tool_runtime.get_llm_parameters_json_schema(),
parameters_json_schema=self._plugin_parameters_json_schema(tool_runtime, parameters),
)
def _to_core_backend_tool_config(
@@ -456,6 +459,41 @@ class WorkflowAgentDifyToolsBuilder:
description = tool_runtime.entity.description.llm
return description
@staticmethod
def _plugin_parameters_json_schema(
tool_runtime: Tool,
parameters: list[DifyPluginToolParameter],
) -> dict[str, Any]:
schema = tool_runtime.get_llm_parameters_json_schema()
properties = schema.setdefault("properties", {})
required = schema.setdefault("required", [])
if not isinstance(properties, dict) or not isinstance(required, list):
raise WorkflowAgentDifyToolsBuildError(
"agent_tool_declaration_invalid",
f"Dify Plugin Tool {tool_runtime.entity.identity.name!r} has invalid parameter schema.",
)
for parameter in parameters:
if parameter.form is not DifyPluginToolParameterForm.LLM:
continue
if parameter.type is DifyPluginToolParameterType.FILE:
properties[parameter.name] = _plugin_file_input_schema(parameter.llm_description or "")
elif parameter.type in {
DifyPluginToolParameterType.FILES,
DifyPluginToolParameterType.SYSTEM_FILES,
}:
properties[parameter.name] = {
"type": "array",
"items": _plugin_file_input_schema(parameter.llm_description or ""),
"description": parameter.llm_description or "",
}
else:
continue
if parameter.required and parameter.name not in required:
required.append(parameter.name)
return schema
@staticmethod
def _runtime_parameters(
tool_runtime: Tool,
@@ -498,3 +536,44 @@ class WorkflowAgentDifyToolsBuilder:
),
)
return normalized
def _is_plugin_provider_id(provider_id: str | None) -> bool:
if not provider_id:
return False
parts = provider_id.split("/")
return len(parts) == 3 and all(parts)
def _plugin_file_input_schema(description: str) -> dict[str, Any]:
return {
"description": description,
"anyOf": [
{
"type": "string",
"minLength": 1,
"description": "HTTP(S) URL or sandbox-local file path.",
},
{
"type": "object",
"additionalProperties": False,
"required": ["transfer_method", "url"],
"properties": {
"transfer_method": {"type": "string", "enum": ["remote_url"]},
"url": {"type": "string", "minLength": 1},
},
},
{
"type": "object",
"additionalProperties": False,
"required": ["transfer_method", "reference"],
"properties": {
"transfer_method": {
"type": "string",
"enum": ["local_file", "tool_file", "datasource_file"],
},
"reference": {"type": "string", "minLength": 1},
},
},
],
}
@@ -333,6 +333,8 @@ class WorkflowAgentOutputAdapter:
session_snapshot = None
if isinstance(event, AgentBackendRunSucceededInternalEvent | AgentBackendDeferredToolCallInternalEvent):
session_snapshot = event.session_snapshot
if event.usage is not None:
agent_backend["usage"] = dict(event.usage)
if session_snapshot is not None:
agent_backend["session_snapshot"] = {
"layer_count": len(session_snapshot.layers),
@@ -206,17 +206,14 @@ class WorkflowAgentRuntimeRequestBuilder:
"cli_tool_count": len(agent_soul.tools.cli_tools),
}
config_layer_config: DifyConfigLayerConfig | None = None
soul_prompt_resolver = build_soul_mention_resolver(agent_soul)
if dify_config.AGENT_DRIVE_MANIFEST_ENABLED:
config_layer_config, config_warnings = build_config_layer_config(
agent_soul,
agent_id=context.agent.id,
config_version_id=context.snapshot.id,
config_version_kind="snapshot",
)
append_runtime_warnings(metadata, config_warnings)
soul_prompt_resolver = build_config_aware_soul_mention_resolver(agent_soul)
config_layer_config, config_warnings = build_config_layer_config(
agent_soul,
agent_id=context.agent.id,
config_version_id=context.snapshot.id,
config_version_kind="snapshot",
)
append_runtime_warnings(metadata, config_warnings)
soul_prompt_resolver = build_config_aware_soul_mention_resolver(agent_soul)
soul_prompt = expand_prompt_mentions(agent_soul.prompt.system_prompt, soul_prompt_resolver).strip()
knowledge_config = build_knowledge_layer_config(agent_soul)
@@ -717,7 +714,7 @@ def build_shell_layer_config(agent_soul: AgentSoulConfig) -> DifyShellLayerConfi
for tool in (_shell_cli_tool(item) for item in agent_soul.tools.cli_tools if _cli_tool_enabled(item))
if tool is not None
],
env=[env for env in (_shell_env_var(item) for item in agent_soul.env.variables) if env is not None],
env=_shell_env_vars(agent_soul.env.variables, agent_soul.env.secret_refs),
secret_refs=[
secret for secret in (_shell_secret_ref(item) for item in agent_soul.env.secret_refs) if secret is not None
],
@@ -864,8 +861,13 @@ def build_config_layer_config(
agent_id: str | None = None,
config_version_id: str | None = None,
config_version_kind: Literal["snapshot", "draft", "build_draft"] = "snapshot",
) -> tuple[DifyConfigLayerConfig | None, list[dict[str, str]]]:
"""Derive prompt-mentioned eager-pull names from Agent Soul."""
) -> tuple[DifyConfigLayerConfig, list[dict[str, str]]]:
"""Build the always-present Agent config layer from Agent Soul state.
The ``dify.config`` layer must exist for every Agent v2 runtime request so
the backend can expose the config CLI/help surface even when the current
Agent Soul has no config assets, note, or prompt mentions.
"""
ordered_mentions = list(
dict.fromkeys(
@@ -874,14 +876,6 @@ def build_config_layer_config(
if mention.kind in {MentionKind.SKILL, MentionKind.FILE} and mention.ref_id
)
)
if (
not agent_soul.config_skills
and not agent_soul.config_files
and not agent_soul.config_note
and not ordered_mentions
):
return None, []
skill_names = {skill.name for skill in agent_soul.config_skills}
file_names = {file_ref.name for file_ref in agent_soul.config_files}
warnings: list[dict[str, str]] = []
@@ -968,11 +962,7 @@ def _shell_cli_tool(item: object) -> DifyShellCliToolConfig | None:
if not commands and not isinstance(name, str):
return None
tool_env = data.get("env") if isinstance(data.get("env"), Mapping) else {}
env = [
env_var
for env_var in (_shell_env_var(item) for item in _env_entries(tool_env, "variables"))
if env_var is not None
]
env = _shell_env_vars(_env_entries(tool_env, "variables"), _env_entries(tool_env, "secret_refs"))
secret_refs = [
secret_ref
for secret_ref in (_shell_secret_ref(item) for item in _env_entries(tool_env, "secret_refs"))
@@ -995,6 +985,12 @@ def _env_entries(env: object, key: str) -> list[object]:
return entries
def _shell_env_vars(variables: Sequence[object], secret_refs: Sequence[object]) -> list[DifyShellEnvVarConfig]:
env_vars = [_shell_env_var(item) for item in variables]
secret_env_vars = [_shell_env_var(item) for item in secret_refs if _has_secret_value(item)]
return [env for env in [*env_vars, *secret_env_vars] if env is not None]
def _shell_env_var(item: object) -> DifyShellEnvVarConfig | None:
data = _plain_mapping(item)
name = _name_from_mapping(data)
@@ -1011,13 +1007,15 @@ def _shell_secret_ref(item: object) -> DifyShellSecretRefConfig | None:
name = _name_from_mapping(data)
if name is None:
return None
ref = (
data.get("ref")
or data.get("value")
or data.get("id")
or data.get("credential_id")
or data.get("provider_credential_id")
)
# Inline Composer values are passed as env vars because the agent-backend
# secret ref schema only accepts short backend-managed reference IDs.
if _has_secret_value(item):
return None
ref = data.get("ref") or data.get("credential_id") or data.get("provider_credential_id")
if ref is None:
ref = data.get("id")
if ref is None:
return None
return DifyShellSecretRefConfig(name=name, ref=str(ref) if ref is not None else None)
@@ -1029,6 +1027,12 @@ def _plain_mapping(item: object) -> dict[str, Any]:
return {}
def _has_secret_value(item: object) -> bool:
data = _plain_mapping(item)
value = data.get("value")
return isinstance(value, str) and bool(value)
def _name_from_mapping(item: Mapping[str, Any]) -> str | None:
for key in ("name", "key", "env_name", "variable"):
value = item.get(key)