mirror of https://github.com/langgenius/dify.git
Merge branch 'feat/support-agent-sandbox' of https://github.com/langgenius/dify into feat/support-agent-sandbox
This commit is contained in:
commit
c9e428facf
|
|
@ -30,7 +30,7 @@ class AppAssetsInitializer(AsyncSandboxInitializer):
|
|||
(
|
||||
pipeline(vm)
|
||||
.add(
|
||||
["wget", "-q", download_url, "-O", AppAssets.ZIP_PATH],
|
||||
["curl", "-fsSL", download_url, "-o", AppAssets.ZIP_PATH],
|
||||
error_message="Failed to download assets zip",
|
||||
)
|
||||
# Create the assets directory first to ensure it exists even if zip is empty
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class ArchiveSandboxStorage(SandboxStorage):
|
|||
try:
|
||||
(
|
||||
pipeline(sandbox)
|
||||
.add(["wget", "-q", download_url, "-O", ARCHIVE_NAME], error_message="Failed to download archive")
|
||||
.add(["curl", "-fsSL", download_url, "-o", ARCHIVE_NAME], error_message="Failed to download archive")
|
||||
.add(["tar", "-xzf", ARCHIVE_NAME], error_message="Failed to extract archive")
|
||||
.add(["rm", ARCHIVE_NAME], error_message="Failed to cleanup archive")
|
||||
.execute(timeout=ARCHIVE_DOWNLOAD_TIMEOUT, raise_on_error=True)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from .skill_metadata import (
|
|||
ToolFieldConfig,
|
||||
ToolReference,
|
||||
)
|
||||
from .tool_access_policy import ToolAccessPolicy, ToolInvocationRequest, ToolKey
|
||||
from .tool_access_policy import ToolAccessPolicy, ToolDescription, ToolInvocationRequest
|
||||
from .tool_dependencies import ToolDependencies, ToolDependency
|
||||
|
||||
__all__ = [
|
||||
|
|
@ -24,8 +24,8 @@ __all__ = [
|
|||
"ToolConfiguration",
|
||||
"ToolDependencies",
|
||||
"ToolDependency",
|
||||
"ToolDescription",
|
||||
"ToolFieldConfig",
|
||||
"ToolInvocationRequest",
|
||||
"ToolKey",
|
||||
"ToolReference",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
from collections.abc import Mapping
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from core.skill.entities.tool_dependencies import ToolDependencies
|
||||
from core.tools.entities.tool_entities import ToolProviderType
|
||||
|
||||
|
||||
class ToolKey(BaseModel):
|
||||
class ToolDescription(BaseModel):
|
||||
"""Immutable identifier for a tool (type + provider + name)."""
|
||||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
|
@ -13,6 +15,9 @@ class ToolKey(BaseModel):
|
|||
provider: str
|
||||
tool_name: str
|
||||
|
||||
def tool_id(self) -> str:
|
||||
return f"{self.tool_type.value}:{self.provider}:{self.tool_name}"
|
||||
|
||||
|
||||
class ToolInvocationRequest(BaseModel):
|
||||
"""A request to invoke a specific tool with optional credential."""
|
||||
|
|
@ -25,8 +30,8 @@ class ToolInvocationRequest(BaseModel):
|
|||
credential_id: str | None = None
|
||||
|
||||
@property
|
||||
def key(self) -> ToolKey:
|
||||
return ToolKey(tool_type=self.tool_type, provider=self.provider, tool_name=self.tool_name)
|
||||
def tool_description(self) -> ToolDescription:
|
||||
return ToolDescription(tool_type=self.tool_type, provider=self.provider, tool_name=self.tool_name)
|
||||
|
||||
|
||||
class ToolAccessPolicy(BaseModel):
|
||||
|
|
@ -41,30 +46,37 @@ class ToolAccessPolicy(BaseModel):
|
|||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
allowed_tools: frozenset[ToolKey] = Field(default_factory=frozenset)
|
||||
credential_ids_by_tool: dict[ToolKey, frozenset[str | None]] = Field(default_factory=dict)
|
||||
allowed_tools: Mapping[str, ToolDescription] = Field(default_factory=dict)
|
||||
credentials_by_tool: Mapping[str, set[str]] = Field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dependencies(cls, deps: ToolDependencies | None) -> "ToolAccessPolicy":
|
||||
"""Create a ToolAccessPolicy from ToolDependencies."""
|
||||
if deps is None or deps.is_empty():
|
||||
return cls()
|
||||
|
||||
def to_key(t: ToolProviderType, p: str, n: str) -> ToolKey:
|
||||
return ToolKey(tool_type=t, provider=p, tool_name=n)
|
||||
allowed_tools: dict[str, ToolDescription] = {}
|
||||
credentials_by_tool: dict[str, set[str]] = {}
|
||||
|
||||
tools: set[ToolKey] = set()
|
||||
tools.update(to_key(dep.type, dep.provider, dep.tool_name) for dep in deps.dependencies)
|
||||
tools.update(to_key(ref.type, ref.provider, ref.tool_name) for ref in deps.references)
|
||||
# Process dependencies - tools that can be used without specific credentials
|
||||
for dep in deps.dependencies:
|
||||
tool_desc = ToolDescription(tool_type=dep.type, provider=dep.provider, tool_name=dep.tool_name)
|
||||
tool_id = tool_desc.tool_id()
|
||||
allowed_tools[tool_id] = tool_desc
|
||||
|
||||
creds: dict[ToolKey, set[str | None]] = {}
|
||||
# Process references - tools that may require specific credentials
|
||||
for ref in deps.references:
|
||||
key = to_key(ref.type, ref.provider, ref.tool_name)
|
||||
creds.setdefault(key, set()).add(ref.credential_id)
|
||||
tool_desc = ToolDescription(tool_type=ref.type, provider=ref.provider, tool_name=ref.tool_name)
|
||||
tool_id = tool_desc.tool_id()
|
||||
allowed_tools[tool_id] = tool_desc
|
||||
|
||||
return cls(
|
||||
allowed_tools=frozenset(tools),
|
||||
credential_ids_by_tool={k: frozenset(v) for k, v in creds.items()},
|
||||
)
|
||||
# If reference has a credential_id, add it to the allowed credentials for this tool
|
||||
if ref.credential_id is not None:
|
||||
if tool_id not in credentials_by_tool:
|
||||
credentials_by_tool[tool_id] = set()
|
||||
credentials_by_tool[tool_id].add(ref.credential_id)
|
||||
|
||||
return cls(allowed_tools=allowed_tools, credentials_by_tool=credentials_by_tool)
|
||||
|
||||
def is_empty(self) -> bool:
|
||||
return len(self.allowed_tools) == 0
|
||||
|
|
@ -76,12 +88,13 @@ class ToolAccessPolicy(BaseModel):
|
|||
if self.is_empty():
|
||||
return True
|
||||
|
||||
if request.key not in self.allowed_tools:
|
||||
tool_id = request.tool_description.tool_id()
|
||||
if tool_id not in self.allowed_tools:
|
||||
return False
|
||||
|
||||
allowed_credentials = self.credential_ids_by_tool.get(request.key)
|
||||
if not allowed_credentials:
|
||||
# No references for this tool: only allow invocation without credential.
|
||||
return request.credential_id is None
|
||||
|
||||
return request.credential_id in allowed_credentials
|
||||
# No special credential required, use default credentials only
|
||||
if request.credential_id is None or request.credential_id == "":
|
||||
return self.credentials_by_tool.get(tool_id) is None
|
||||
# Special credential required, check if it is allowed
|
||||
else:
|
||||
return request.credential_id in self.credentials_by_tool.get(tool_id, set())
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
from collections.abc import Mapping, Sequence
|
||||
from functools import cached_property
|
||||
|
|
@ -106,7 +107,7 @@ class LocalVirtualEnvironment(VirtualEnvironment):
|
|||
"""
|
||||
working_path = self.get_working_path()
|
||||
if os.path.exists(working_path):
|
||||
os.rmdir(working_path)
|
||||
shutil.rmtree(working_path)
|
||||
|
||||
def upload_file(self, path: str, content: BytesIO) -> None:
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue