feat(constants): introduce DIFY_CLI_ROOT and update paths for Dify CLI and app assets

- Added DIFY_CLI_ROOT constant for the root directory of Dify CLI.
- Updated DIFY_CLI_PATH and DIFY_CLI_CONFIG_PATH to use absolute paths.
- Modified app asset initialization to create directories under DIFY_CLI_ROOT.
- Enhanced Docker and E2B environment file handling to use workspace paths.
This commit is contained in:
Harry
2026-01-19 14:18:50 +08:00
parent c38463c9a9
commit 3bb9c4b280
6 changed files with 67 additions and 20 deletions

View File

@ -11,6 +11,7 @@ from .constants import (
DIFY_CLI_CONFIG_PATH,
DIFY_CLI_PATH,
DIFY_CLI_PATH_PATTERN,
DIFY_CLI_ROOT,
)
from .initializer import AppAssetsInitializer, DifyCliInitializer, SandboxInitializer
from .manager import SandboxManager
@ -26,6 +27,7 @@ __all__ = [
"DIFY_CLI_CONFIG_PATH",
"DIFY_CLI_PATH",
"DIFY_CLI_PATH_PATTERN",
"DIFY_CLI_ROOT",
"AppAssetsInitializer",
"ArchiveSandboxStorage",
"DifyCliBinary",

View File

@ -1,11 +1,13 @@
from typing import Final
DIFY_CLI_PATH: Final[str] = ".dify/bin/dify"
# Dify CLI (absolute path - hidden in /tmp, not in sandbox workdir)
DIFY_CLI_ROOT: Final[str] = "/tmp/.dify"
DIFY_CLI_PATH: Final[str] = "/tmp/.dify/bin/dify"
DIFY_CLI_PATH_PATTERN: Final[str] = "dify-cli-{os}-{arch}"
DIFY_CLI_CONFIG_PATH: Final[str] = ".dify_cli.json"
DIFY_CLI_CONFIG_PATH: Final[str] = "/tmp/.dify/.dify_cli.json"
# App Assets
# App Assets (relative path - stays in sandbox workdir)
APP_ASSETS_PATH: Final[str] = "assets"
APP_ASSETS_ZIP_PATH: Final[str] = ".dify/tmp/assets.zip"
APP_ASSETS_ZIP_PATH: Final[str] = "/tmp/.dify/tmp/assets.zip"

View File

@ -10,7 +10,7 @@ from extensions.ext_database import db
from extensions.ext_storage import storage
from models.app_asset import AppAssets
from ..constants import APP_ASSETS_PATH, APP_ASSETS_ZIP_PATH
from ..constants import APP_ASSETS_PATH, APP_ASSETS_ZIP_PATH, DIFY_CLI_ROOT
from .base import SandboxInitializer
logger = logging.getLogger(__name__)
@ -44,7 +44,7 @@ class AppAssetsInitializer(SandboxInitializer):
with with_connection(env) as conn:
execute(
env,
["mkdir", "-p", ".dify/tmp"],
["mkdir", "-p", f"{DIFY_CLI_ROOT}/tmp"],
connection=conn,
error_message="Failed to create temp directory",
)

View File

@ -6,7 +6,7 @@ from core.virtual_environment.__base.helpers import execute
from core.virtual_environment.__base.virtual_environment import VirtualEnvironment
from ..bash.dify_cli import DifyCliLocator
from ..constants import DIFY_CLI_PATH
from ..constants import DIFY_CLI_PATH, DIFY_CLI_ROOT
from .base import SandboxInitializer
logger = logging.getLogger(__name__)
@ -18,6 +18,14 @@ class DifyCliInitializer(SandboxInitializer):
def initialize(self, env: VirtualEnvironment) -> None:
binary = self._locator.resolve(env.metadata.os, env.metadata.arch)
execute(
env,
["mkdir", "-p", f"{DIFY_CLI_ROOT}/bin"],
timeout=10,
error_message="Failed to create dify CLI directory",
)
env.upload_file(DIFY_CLI_PATH, BytesIO(binary.path.read_bytes()))
execute(

View File

@ -369,8 +369,33 @@ class DockerDaemonEnvironment(VirtualEnvironment):
return self._working_dir
return f"{self._working_dir}/{relative.as_posix()}"
def _workspace_path(self, path: str) -> str:
"""
Convert a path to an absolute path in the Docker container.
Absolute paths are returned as-is, relative paths are joined with _working_dir.
"""
normalized = PurePosixPath(path)
if normalized.is_absolute():
return str(normalized)
return self._container_path(path)
def upload_file(self, path: str, content: BytesIO) -> None:
container = self._get_container()
normalized = PurePosixPath(path)
if normalized.is_absolute():
parent_dir = str(normalized.parent)
file_name = normalized.name
payload = content.getvalue()
tar_stream = BytesIO()
with tarfile.open(fileobj=tar_stream, mode="w") as tar:
tar_info = tarfile.TarInfo(name=file_name)
tar_info.size = len(payload)
tar.addfile(tar_info, BytesIO(payload))
tar_stream.seek(0)
container.put_archive(parent_dir, tar_stream.read()) # pyright: ignore[reportUnknownMemberType] #
return
relative_path = self._relative_path(path)
if not relative_path.parts:
raise ValueError("Upload path must point to a file within the workspace.")
@ -386,7 +411,7 @@ class DockerDaemonEnvironment(VirtualEnvironment):
def download_file(self, path: str) -> BytesIO:
container = self._get_container()
container_path = self._container_path(path)
container_path = self._workspace_path(path)
stream, _ = container.get_archive(container_path)
tar_stream = BytesIO()
for chunk in stream:
@ -469,7 +494,7 @@ class DockerDaemonEnvironment(VirtualEnvironment):
raise RuntimeError("Docker container ID is not available for exec.")
api_client = self.get_docker_api_client(self.get_docker_sock())
working_dir = self._container_path(cwd) if cwd else self._working_dir
working_dir = self._workspace_path(cwd) if cwd else self._working_dir
exec_info: dict[str, object] = cast(
dict[str, object],

View File

@ -1,4 +1,4 @@
import os
import posixpath
import shlex
import threading
from collections.abc import Mapping, Sequence
@ -171,10 +171,9 @@ class E2BEnvironment(VirtualEnvironment):
path (str): The path to upload the file to.
content (BytesIO): The content of the file.
"""
path = os.path.join(self._WORKDIR, path.lstrip("/"))
remote_path = self._workspace_path(path)
sandbox: Sandbox = self.metadata.store[self.StoreKey.SANDBOX]
sandbox.files.write(path, content) # pyright: ignore[reportUnknownMemberType] #
sandbox.files.write(remote_path, content) # pyright: ignore[reportUnknownMemberType] #
def download_file(self, path: str) -> BytesIO:
"""
@ -185,10 +184,9 @@ class E2BEnvironment(VirtualEnvironment):
Returns:
BytesIO: The content of the file.
"""
path = os.path.join(self._WORKDIR, path.lstrip("/"))
remote_path = self._workspace_path(path)
sandbox: Sandbox = self.metadata.store[self.StoreKey.SANDBOX]
content = sandbox.files.read(path)
content = sandbox.files.read(remote_path)
return BytesIO(content.encode())
def list_files(self, directory_path: str, limit: int) -> Sequence[FileState]:
@ -196,11 +194,11 @@ class E2BEnvironment(VirtualEnvironment):
List files in a directory of the E2B virtual environment.
"""
sandbox: Sandbox = self.metadata.store[self.StoreKey.SANDBOX]
directory_path = os.path.join(self._WORKDIR, directory_path.lstrip("/"))
files_info = sandbox.files.list(directory_path, depth=self.options.get(self.OptionsKey.E2B_LIST_FILE_DEPTH, 3))
remote_dir = self._workspace_path(directory_path)
files_info = sandbox.files.list(remote_dir, depth=self.options.get(self.OptionsKey.E2B_LIST_FILE_DEPTH, 3))
return [
FileState(
path=os.path.relpath(file_info.path, self._WORKDIR),
path=posixpath.relpath(file_info.path, self._WORKDIR),
size=file_info.size,
created_at=int(file_info.modified_time.timestamp()),
updated_at=int(file_info.modified_time.timestamp()),
@ -225,7 +223,7 @@ class E2BEnvironment(VirtualEnvironment):
stdout_stream = QueueTransportReadCloser()
stderr_stream = QueueTransportReadCloser()
working_dir = os.path.join(self._WORKDIR, cwd) if cwd else self._WORKDIR
working_dir = self._workspace_path(cwd) if cwd else self._WORKDIR
threading.Thread(
target=self._cmd_thread,
@ -282,6 +280,18 @@ class E2BEnvironment(VirtualEnvironment):
"""
return self.options.get(self.OptionsKey.API_KEY, "")
def _workspace_path(self, path: str) -> str:
"""
Convert a path to an absolute path in the E2B environment.
Absolute paths are returned as-is, relative paths are joined with _WORKDIR.
"""
normalized = posixpath.normpath(path)
if normalized in ("", "."):
return self._WORKDIR
if normalized.startswith("/"):
return normalized
return posixpath.join(self._WORKDIR, normalized)
def _convert_architecture(self, arch_str: str) -> Arch:
arch_map = {
"x86_64": Arch.AMD64,