mirror of
https://github.com/langgenius/dify.git
synced 2026-06-11 02:31:13 +08:00
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
94 lines
3.6 KiB
Python
94 lines
3.6 KiB
Python
"""Resolve a download request for a workflow file ref to a signed URL (Agent Files §3.1.1/§4.5).
|
|
|
|
The dify-agent server calls this on behalf of a sandbox that needs to pull a
|
|
``File`` / ``Array[File]`` workflow input. It binds the flattened file-access
|
|
context as a ``FileAccessScope``, rebuilds the graphon ``File`` from the mapping
|
|
(reusing tenant/user access checks), and returns an internal signed download URL
|
|
plus metadata — never the file bytes. The dify-agent server / sandbox then GETs
|
|
the URL directly from Dify API.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
from typing import Any
|
|
|
|
from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom
|
|
from core.app.file_access.controller import DatabaseFileAccessController
|
|
from core.app.file_access.scope import FileAccessScope, bind_file_access_scope
|
|
from core.app.workflow.file_runtime import DifyWorkflowFileRuntime
|
|
from factories import file_factory
|
|
|
|
|
|
class FileDownloadRequestError(Exception):
|
|
"""A download-request failure mapped to an HTTP status by the controller."""
|
|
|
|
code: str
|
|
message: str
|
|
status_code: int
|
|
|
|
def __init__(self, code: str, message: str, *, status_code: int = 400) -> None:
|
|
super().__init__(message)
|
|
self.code = code
|
|
self.message = message
|
|
self.status_code = status_code
|
|
|
|
|
|
class AgentFileDownloadRequestService:
|
|
"""Resolve a workflow file ref to a sandbox-accessible internal signed download URL."""
|
|
|
|
@classmethod
|
|
def resolve(
|
|
cls,
|
|
*,
|
|
tenant_id: str,
|
|
user_id: str,
|
|
user_from: str,
|
|
invoke_from: str,
|
|
file_mapping: Mapping[str, Any],
|
|
) -> dict[str, Any]:
|
|
try:
|
|
scope_user_from = UserFrom(user_from)
|
|
scope_invoke_from = InvokeFrom(invoke_from)
|
|
except ValueError as exc:
|
|
raise FileDownloadRequestError("invalid_access_context", str(exc), status_code=400) from exc
|
|
|
|
if not isinstance(file_mapping, Mapping) or not file_mapping.get("transfer_method"):
|
|
raise FileDownloadRequestError("invalid_file_mapping", "file.transfer_method is required", status_code=400)
|
|
|
|
scope = FileAccessScope(
|
|
tenant_id=tenant_id,
|
|
user_id=user_id,
|
|
user_from=scope_user_from,
|
|
invoke_from=scope_invoke_from,
|
|
)
|
|
controller = DatabaseFileAccessController()
|
|
runtime = DifyWorkflowFileRuntime(file_access_controller=controller)
|
|
try:
|
|
with bind_file_access_scope(scope):
|
|
file = file_factory.build_from_mapping(
|
|
mapping=file_mapping,
|
|
tenant_id=tenant_id,
|
|
access_controller=controller,
|
|
)
|
|
# Internal URL (for_external=False): the consumer is the agent backend /
|
|
# sandbox, not a browser. Resolves against INTERNAL_FILES_URL, falling
|
|
# back to FILES_URL when not configured.
|
|
download_url = runtime.resolve_file_url(file=file, for_external=False)
|
|
except ValueError as exc:
|
|
raise FileDownloadRequestError("file_not_accessible", str(exc), status_code=404) from exc
|
|
|
|
if not download_url:
|
|
raise FileDownloadRequestError(
|
|
"download_url_unavailable", "could not resolve a download URL for the file", status_code=502
|
|
)
|
|
return {
|
|
"filename": file.filename,
|
|
"mime_type": file.mime_type,
|
|
"size": file.size,
|
|
"download_url": download_url,
|
|
}
|
|
|
|
|
|
__all__ = ["AgentFileDownloadRequestService", "FileDownloadRequestError"]
|