refactor(agent): configure e2b storage with opendal uri
This commit is contained in:
+4
-12
@@ -47,19 +47,11 @@ DIFY_AGENT_E2B_TEMPLATE=difys-default-team/dify-agent-local-sandbox
|
||||
DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS=3600
|
||||
DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN=
|
||||
DIFY_AGENT_E2B_SHELLCTL_PORT=5004
|
||||
# e2b_s3 reuses the E2B settings above and stores immutable Home archives in S3-compatible storage.
|
||||
# Bucket is required; root defaults to dify-agent. Region and HTTP(S) endpoint are optional.
|
||||
# e2b_s3 reuses the E2B settings above and stores immutable Home archives in OpenDAL storage.
|
||||
# The URI selects and configures the OpenDAL service. It must support read, write,
|
||||
# multi-write, conditional create, and delete.
|
||||
# It also requires an HTTP(S) DIFY_AGENT_STUB_API_BASE_URL and DIFY_AGENT_SERVER_SECRET_KEY below.
|
||||
DIFY_AGENT_E2B_S3_BUCKET=
|
||||
DIFY_AGENT_E2B_S3_ROOT=dify-agent
|
||||
DIFY_AGENT_E2B_S3_REGION=
|
||||
DIFY_AGENT_E2B_S3_ENDPOINT=
|
||||
# Explicit access and secret keys must be configured together. A session token
|
||||
# is allowed only with that explicit pair; leave all three empty for OpenDAL's
|
||||
# standard S3 environment/config credential loading.
|
||||
DIFY_AGENT_E2B_S3_ACCESS_KEY_ID=
|
||||
DIFY_AGENT_E2B_S3_SECRET_ACCESS_KEY=
|
||||
DIFY_AGENT_E2B_S3_SESSION_TOKEN=
|
||||
DIFY_AGENT_E2B_S3_URI=
|
||||
# Maximum whole-file size read through /workspace/files for an Agent Stub ToolFile upload (50 MiB).
|
||||
# The environment variable keeps its existing SANDBOX name for deployment compatibility.
|
||||
DIFY_AGENT_SANDBOX_FILE_UPLOAD_MAX_BYTES=52428800
|
||||
|
||||
@@ -187,7 +187,7 @@ though sibling Home content cannot be read or written.
|
||||
| --- | --- | --- | --- |
|
||||
| Local | Supported | Supported, including default empty Homes and attaching multiple Bindings to one Workspace | Snapshot directory, per-Binding materialized Home, and Workspace directory are separate. |
|
||||
| E2B | Supported | Supported with template-backed default Homes, without shared-Workspace attachment | Binding and Workspace refs map to the same E2B resource; checkpoints use E2B snapshots. |
|
||||
| E2B + S3 (`e2b_s3`) | Supported as immutable OpenDAL/S3 tar.zst streams | Multiple private Binding Homes attach to one shared E2B Workspace Sandbox | Workspace ref is the Sandbox id, Binding ref is `sandbox-id:binding-id`, and Home Snapshot ref is an OpenDAL path relative to `DIFY_AGENT_E2B_S3_ROOT`; the physical S3 key combines the root and ref. |
|
||||
| E2B + S3 (`e2b_s3`) | Supported as immutable OpenDAL tar.zst streams | Multiple private Binding Homes attach to one shared E2B Workspace Sandbox | Workspace ref is the Sandbox id, Binding ref is `sandbox-id:binding-id`, and Home Snapshot ref is an OpenDAL path relative to the operator root configured by `DIFY_AGENT_E2B_S3_URI`. |
|
||||
| Enterprise | Not implemented | Default-Home Binding creation, acquire, and coupled destroy are supported | Binding and Workspace refs map to one Gateway sandbox. Explicit Home Snapshot materialization fails fast. |
|
||||
|
||||
Local creates a new Home for every Binding id. Destroying one Binding without
|
||||
|
||||
@@ -51,13 +51,7 @@ also reads `.env` and `dify-agent/.env` when present.
|
||||
| `DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS` | `3600` | Maximum continuous active time, up to 3600 seconds. Binding resources pause on timeout. This is not a retention TTL. |
|
||||
| `DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN` | empty | Optional bearer token expected by shellctl inside the E2B template. |
|
||||
| `DIFY_AGENT_E2B_SHELLCTL_PORT` | `5004` | shellctl port exposed by the E2B template. |
|
||||
| `DIFY_AGENT_E2B_S3_BUCKET` | empty | S3-compatible bucket required by `e2b_s3`. |
|
||||
| `DIFY_AGENT_E2B_S3_ROOT` | `dify-agent` | OpenDAL root prefix containing immutable Home Snapshot objects. Snapshot refs are paths relative to this root; the physical object key combines the root and ref. |
|
||||
| `DIFY_AGENT_E2B_S3_REGION` | empty | Optional S3 region. When omitted, OpenDAL uses its standard S3 environment/config loading. |
|
||||
| `DIFY_AGENT_E2B_S3_ENDPOINT` | empty | Optional HTTP(S) endpoint for S3-compatible storage. |
|
||||
| `DIFY_AGENT_E2B_S3_ACCESS_KEY_ID` | empty | Optional explicit S3 access key; must be paired with the secret key. |
|
||||
| `DIFY_AGENT_E2B_S3_SECRET_ACCESS_KEY` | empty | Optional explicit S3 secret key; must be paired with the access key. |
|
||||
| `DIFY_AGENT_E2B_S3_SESSION_TOKEN` | empty | Optional session token, allowed only with the explicit credential pair. |
|
||||
| `DIFY_AGENT_E2B_S3_URI` | empty | OpenDAL scheme URI required by `e2b_s3`. The selected service must support `read`, `write`, `write_can_multi`, `write_with_if_not_exists`, and `delete`; startup fails and lists missing capabilities otherwise. |
|
||||
| `DIFY_AGENT_SANDBOX_FILE_UPLOAD_MAX_BYTES` | `52428800` | Standalone Dify Agent maximum for whole-file Workspace upload capture; 50 MiB by default. Docker Compose derives it from `PLUGIN_MAX_FILE_SIZE`. |
|
||||
| `DIFY_AGENT_SHELL_REDACT_PATTERNS` | empty | JSON array of additional regex patterns redacted from Shell output. |
|
||||
| `DIFY_AGENT_STUB_API_BASE_URL` | empty | Public Agent Stub API base URL reachable from shellctl-managed remote machines. HTTP may be the service root or `/agent-stub`; gRPC must be `grpc://host:port`. Enables `DIFY_AGENT_STUB_*` env injection for user `shell.run` jobs. |
|
||||
@@ -188,15 +182,15 @@ The physical resource behind a Binding pauses when that timeout fires, preservin
|
||||
the current Workspace. The setting is not a resource-age TTL and does not delete
|
||||
paused resources or immutable snapshots.
|
||||
|
||||
For `e2b_s3`, export `DIFY_AGENT_RUNTIME_BACKEND=e2b_s3` plus the S3 bucket
|
||||
settings before starting the same overlay. `DIFY_AGENT_STUB_API_BASE_URL` must
|
||||
be an HTTP(S) URL reachable from the E2B Sandbox and
|
||||
For `e2b_s3`, export `DIFY_AGENT_RUNTIME_BACKEND=e2b_s3` plus
|
||||
`DIFY_AGENT_E2B_S3_URI` before starting the same overlay. The URI selects and
|
||||
configures an OpenDAL service; its scheme is not restricted to `s3`.
|
||||
`DIFY_AGENT_STUB_API_BASE_URL` must be an HTTP(S) URL reachable from the E2B Sandbox and
|
||||
`DIFY_AGENT_SERVER_SECRET_KEY` must be configured. The template must enable
|
||||
`SHELLCTL_ENABLE_PATH_ISOLATION=true`. Compose does not start MinIO or another
|
||||
object store. A Home Snapshot ref such as
|
||||
`home-snapshots/<tenant>/<agent>/<snapshot>.tar.zst` is an OpenDAL path relative
|
||||
to `DIFY_AGENT_E2B_S3_ROOT`; its physical object key is the configured root
|
||||
joined with that ref.
|
||||
to the operator root configured by the URI.
|
||||
|
||||
## Run runtime-backend integration contracts
|
||||
|
||||
@@ -239,16 +233,14 @@ test time; it is not a post-test retention TTL. Both contracts create unique
|
||||
resources and perform explicit cleanup in `finally` blocks.
|
||||
|
||||
The `e2b_s3` integration path additionally requires a public HTTP Agent Stub
|
||||
server configured with the same bucket/root/credentials and server secret.
|
||||
`DIFY_AGENT_TEST_E2B_S3_ROOT` must exactly match the server's
|
||||
`DIFY_AGENT_E2B_S3_ROOT`; the example uses `dify-agent-integration` for both:
|
||||
server configured with the same OpenDAL storage URI and server secret.
|
||||
`DIFY_AGENT_TEST_E2B_S3_URI` must address the same storage and root as the
|
||||
server's `DIFY_AGENT_E2B_S3_URI`:
|
||||
|
||||
```bash
|
||||
cd dify-agent
|
||||
DIFY_AGENT_TEST_E2B_API_KEY="$E2B_API_TOKEN" \
|
||||
DIFY_AGENT_TEST_E2B_S3_BUCKET=integration-bucket \
|
||||
DIFY_AGENT_TEST_E2B_S3_ROOT=dify-agent-integration \
|
||||
DIFY_AGENT_TEST_E2B_S3_REGION=us-east-1 \
|
||||
DIFY_AGENT_TEST_E2B_S3_URI='s3://integration-bucket/dify-agent-integration?region=us-east-1' \
|
||||
DIFY_AGENT_TEST_E2B_S3_STUB_API_BASE_URL=https://agent.example.com/agent-stub \
|
||||
DIFY_AGENT_TEST_SERVER_SECRET_KEY="$DIFY_AGENT_SERVER_SECRET_KEY" \
|
||||
pdm run pytest --import-mode=importlib \
|
||||
|
||||
@@ -25,7 +25,7 @@ server = [
|
||||
"jsonschema>=4.23.0,<5.0.0",
|
||||
"jwcrypto>=1.5.6,<2",
|
||||
"logfire[fastapi,httpx,redis]>=4.37.0,<5.0.0",
|
||||
"opendal==0.47.3",
|
||||
"opendal==0.47.5",
|
||||
"pydantic-ai-slim[anthropic,google,openai]>=1.85.1,<2.0.0",
|
||||
"pydantic-settings>=2.12.0,<3.0.0",
|
||||
"redis>=7.4.0,<8.0.0",
|
||||
@@ -51,6 +51,14 @@ testpaths = ["tests"]
|
||||
python_files = ["test_*.py", "*_test.py"]
|
||||
markers = ["integration: requires a real external service or exercises multiple concrete adapters"]
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "testpypi"
|
||||
url = "https://test.pypi.org/simple"
|
||||
explicit = true
|
||||
|
||||
[tool.uv.sources]
|
||||
opendal = { index = "testpypi" }
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
target-version = "py312"
|
||||
|
||||
@@ -71,6 +71,13 @@ _BASELINE_EXCLUDES = (
|
||||
".dify-agent-runtime",
|
||||
".local/share/shellctl",
|
||||
)
|
||||
_REQUIRED_HOME_ARCHIVE_CAPABILITIES = (
|
||||
"read",
|
||||
"write",
|
||||
"write_can_multi",
|
||||
"write_with_if_not_exists",
|
||||
"delete",
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -118,27 +125,17 @@ class OpenDALHomeArchiveStore:
|
||||
operator: opendal.AsyncOperator
|
||||
|
||||
@classmethod
|
||||
def create_s3(
|
||||
cls,
|
||||
*,
|
||||
bucket: str,
|
||||
root: str,
|
||||
region: str | None = None,
|
||||
endpoint: str | None = None,
|
||||
access_key_id: str | None = None,
|
||||
secret_access_key: str | None = None,
|
||||
session_token: str | None = None,
|
||||
) -> "OpenDALHomeArchiveStore":
|
||||
options = {"bucket": bucket, "root": root}
|
||||
optional = {
|
||||
"region": region,
|
||||
"endpoint": endpoint,
|
||||
"access_key_id": access_key_id,
|
||||
"secret_access_key": secret_access_key,
|
||||
"session_token": session_token,
|
||||
}
|
||||
options.update({key: value for key, value in optional.items() if value is not None and value.strip()})
|
||||
return cls(operator=opendal.AsyncOperator("s3", **options))
|
||||
def create_from_uri(cls, uri: str) -> "OpenDALHomeArchiveStore":
|
||||
operator = opendal.AsyncOperator.from_uri(uri)
|
||||
capability = operator.capability()
|
||||
missing = [
|
||||
name for name in _REQUIRED_HOME_ARCHIVE_CAPABILITIES if not getattr(capability, name, False)
|
||||
]
|
||||
if missing:
|
||||
raise ValueError(
|
||||
"OpenDAL Home Snapshot storage is missing required capabilities: " + ", ".join(missing)
|
||||
)
|
||||
return cls(operator=operator)
|
||||
|
||||
async def open_writer(self, snapshot_ref: str) -> OpenDALAsyncFile:
|
||||
path = validate_home_snapshot_ref(snapshot_ref)
|
||||
|
||||
@@ -62,13 +62,7 @@ class RuntimeBackendSettings(BaseSettings):
|
||||
)
|
||||
e2b_shellctl_auth_token: str = ""
|
||||
e2b_shellctl_port: int = Field(default=5004, ge=1, le=65535)
|
||||
e2b_s3_bucket: str | None = None
|
||||
e2b_s3_root: str = "dify-agent"
|
||||
e2b_s3_region: str | None = None
|
||||
e2b_s3_endpoint: str | None = None
|
||||
e2b_s3_access_key_id: str | None = None
|
||||
e2b_s3_secret_access_key: str | None = None
|
||||
e2b_s3_session_token: str | None = None
|
||||
e2b_s3_uri: str | None = None
|
||||
agent_stub_api_base_url: str | None = None
|
||||
server_secret_key: str | None = None
|
||||
|
||||
@@ -81,12 +75,7 @@ class RuntimeBackendSettings(BaseSettings):
|
||||
|
||||
@field_validator(
|
||||
"e2b_api_key",
|
||||
"e2b_s3_bucket",
|
||||
"e2b_s3_region",
|
||||
"e2b_s3_endpoint",
|
||||
"e2b_s3_access_key_id",
|
||||
"e2b_s3_secret_access_key",
|
||||
"e2b_s3_session_token",
|
||||
"e2b_s3_uri",
|
||||
"agent_stub_api_base_url",
|
||||
"server_secret_key",
|
||||
mode="before",
|
||||
@@ -121,18 +110,8 @@ class RuntimeBackendSettings(BaseSettings):
|
||||
return self
|
||||
|
||||
def _validate_e2b_s3(self) -> None:
|
||||
if not self.e2b_s3_bucket or not self.e2b_s3_bucket.strip():
|
||||
raise ValueError("e2b_s3_bucket is required for the e2b_s3 runtime backend")
|
||||
if not self.e2b_s3_root.strip():
|
||||
raise ValueError("e2b_s3_root must not be blank")
|
||||
if self.e2b_s3_endpoint is not None:
|
||||
_validate_http_url(self.e2b_s3_endpoint, field_name="e2b_s3_endpoint")
|
||||
has_access_key = bool(self.e2b_s3_access_key_id and self.e2b_s3_access_key_id.strip())
|
||||
has_secret_key = bool(self.e2b_s3_secret_access_key and self.e2b_s3_secret_access_key.strip())
|
||||
if has_access_key != has_secret_key:
|
||||
raise ValueError("e2b_s3_access_key_id and e2b_s3_secret_access_key must be configured together")
|
||||
if self.e2b_s3_session_token and self.e2b_s3_session_token.strip() and not has_access_key:
|
||||
raise ValueError("e2b_s3_session_token requires explicit S3 credentials")
|
||||
if not self.e2b_s3_uri:
|
||||
raise ValueError("e2b_s3_uri is required for the e2b_s3 runtime backend")
|
||||
if not self.agent_stub_api_base_url:
|
||||
raise ValueError("agent_stub_api_base_url is required for the e2b_s3 runtime backend")
|
||||
_validate_http_url(self.agent_stub_api_base_url, field_name="agent_stub_api_base_url")
|
||||
@@ -179,15 +158,7 @@ def create_runtime_backend_profile(settings: RuntimeBackendSettings) -> RuntimeB
|
||||
)
|
||||
case "e2b_s3":
|
||||
control_plane = E2BSDKControlPlane(api_key=settings.e2b_api_key or "")
|
||||
archive_store = OpenDALHomeArchiveStore.create_s3(
|
||||
bucket=settings.e2b_s3_bucket or "",
|
||||
root=settings.e2b_s3_root,
|
||||
region=settings.e2b_s3_region,
|
||||
endpoint=settings.e2b_s3_endpoint,
|
||||
access_key_id=settings.e2b_s3_access_key_id,
|
||||
secret_access_key=settings.e2b_s3_secret_access_key,
|
||||
session_token=settings.e2b_s3_session_token,
|
||||
)
|
||||
archive_store = OpenDALHomeArchiveStore.create_from_uri(settings.e2b_s3_uri or "")
|
||||
token_codec = HomeSnapshotTransferTokenCodec.from_server_secret(settings.server_secret_key or "")
|
||||
lifecycle_cli = E2BHomeSnapshotCLI(
|
||||
token_codec=token_codec,
|
||||
|
||||
@@ -10,10 +10,9 @@ optional gRPC bind override, and optional Dify inner API bridge settings all
|
||||
live here under the ``DIFY_AGENT_...`` environment-variable namespace.
|
||||
"""
|
||||
|
||||
import httpx
|
||||
|
||||
from typing import ClassVar, Literal, cast
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import httpx
|
||||
|
||||
from pydantic import AliasChoices, AnyHttpUrl, Field, TypeAdapter, field_validator, model_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
@@ -67,13 +66,7 @@ class ServerSettings(BaseSettings):
|
||||
)
|
||||
e2b_shellctl_auth_token: str = ""
|
||||
e2b_shellctl_port: int = Field(default=5004, ge=1, le=65535)
|
||||
e2b_s3_bucket: str | None = None
|
||||
e2b_s3_root: str = "dify-agent"
|
||||
e2b_s3_region: str | None = None
|
||||
e2b_s3_endpoint: str | None = None
|
||||
e2b_s3_access_key_id: str | None = None
|
||||
e2b_s3_secret_access_key: str | None = None
|
||||
e2b_s3_session_token: str | None = None
|
||||
e2b_s3_uri: str | None = None
|
||||
sandbox_file_upload_max_bytes: int = Field(default=50 * 1024 * 1024, ge=1)
|
||||
agent_stub_api_base_url: str | None = Field(default=None, validation_alias="DIFY_AGENT_STUB_API_BASE_URL")
|
||||
agent_stub_grpc_bind_address: str | None = Field(default=None, validation_alias="DIFY_AGENT_STUB_GRPC_BIND_ADDRESS")
|
||||
@@ -97,12 +90,7 @@ class ServerSettings(BaseSettings):
|
||||
|
||||
@field_validator(
|
||||
"e2b_api_key",
|
||||
"e2b_s3_bucket",
|
||||
"e2b_s3_region",
|
||||
"e2b_s3_endpoint",
|
||||
"e2b_s3_access_key_id",
|
||||
"e2b_s3_secret_access_key",
|
||||
"e2b_s3_session_token",
|
||||
"e2b_s3_uri",
|
||||
mode="before",
|
||||
)
|
||||
@classmethod
|
||||
@@ -199,23 +187,8 @@ class ServerSettings(BaseSettings):
|
||||
raise ValueError("DIFY_AGENT_E2B_API_KEY is required for the e2b_s3 runtime backend.")
|
||||
if not self.e2b_template.strip():
|
||||
raise ValueError("DIFY_AGENT_E2B_TEMPLATE must not be blank.")
|
||||
if not self.e2b_s3_bucket or not self.e2b_s3_bucket.strip():
|
||||
raise ValueError("DIFY_AGENT_E2B_S3_BUCKET is required for the e2b_s3 runtime backend.")
|
||||
if not self.e2b_s3_root.strip():
|
||||
raise ValueError("DIFY_AGENT_E2B_S3_ROOT must not be blank.")
|
||||
if self.e2b_s3_endpoint is not None:
|
||||
parsed_s3_endpoint = urlparse(self.e2b_s3_endpoint.strip())
|
||||
if parsed_s3_endpoint.scheme not in {"http", "https"} or not parsed_s3_endpoint.netloc:
|
||||
raise ValueError("DIFY_AGENT_E2B_S3_ENDPOINT must be a valid HTTP(S) URL.")
|
||||
has_access_key = bool(self.e2b_s3_access_key_id and self.e2b_s3_access_key_id.strip())
|
||||
has_secret_key = bool(self.e2b_s3_secret_access_key and self.e2b_s3_secret_access_key.strip())
|
||||
if has_access_key != has_secret_key:
|
||||
raise ValueError(
|
||||
"DIFY_AGENT_E2B_S3_ACCESS_KEY_ID and DIFY_AGENT_E2B_S3_SECRET_ACCESS_KEY "
|
||||
+ "must be configured together."
|
||||
)
|
||||
if self.e2b_s3_session_token and self.e2b_s3_session_token.strip() and not has_access_key:
|
||||
raise ValueError("DIFY_AGENT_E2B_S3_SESSION_TOKEN requires explicit S3 credentials.")
|
||||
if not self.e2b_s3_uri:
|
||||
raise ValueError("DIFY_AGENT_E2B_S3_URI is required for the e2b_s3 runtime backend.")
|
||||
if self.agent_stub_api_base_url is None:
|
||||
raise ValueError("DIFY_AGENT_STUB_API_BASE_URL is required for the e2b_s3 runtime backend.")
|
||||
if parse_agent_stub_endpoint(self.agent_stub_api_base_url).is_grpc:
|
||||
@@ -242,13 +215,7 @@ class ServerSettings(BaseSettings):
|
||||
e2b_active_timeout_seconds=self.e2b_active_timeout_seconds,
|
||||
e2b_shellctl_auth_token=self.e2b_shellctl_auth_token,
|
||||
e2b_shellctl_port=self.e2b_shellctl_port,
|
||||
e2b_s3_bucket=self.e2b_s3_bucket,
|
||||
e2b_s3_root=self.e2b_s3_root,
|
||||
e2b_s3_region=self.e2b_s3_region,
|
||||
e2b_s3_endpoint=self.e2b_s3_endpoint,
|
||||
e2b_s3_access_key_id=self.e2b_s3_access_key_id,
|
||||
e2b_s3_secret_access_key=self.e2b_s3_secret_access_key,
|
||||
e2b_s3_session_token=self.e2b_s3_session_token,
|
||||
e2b_s3_uri=self.e2b_s3_uri,
|
||||
agent_stub_api_base_url=self.agent_stub_api_base_url,
|
||||
server_secret_key=self.server_secret_key,
|
||||
)
|
||||
@@ -258,15 +225,7 @@ class ServerSettings(BaseSettings):
|
||||
"""Build the e2b_s3 HTTP byte-stream gateway from independent resources."""
|
||||
if self.runtime_backend != "e2b_s3":
|
||||
return None
|
||||
store = OpenDALHomeArchiveStore.create_s3(
|
||||
bucket=self.e2b_s3_bucket or "",
|
||||
root=self.e2b_s3_root,
|
||||
region=self.e2b_s3_region,
|
||||
endpoint=self.e2b_s3_endpoint,
|
||||
access_key_id=self.e2b_s3_access_key_id,
|
||||
secret_access_key=self.e2b_s3_secret_access_key,
|
||||
session_token=self.e2b_s3_session_token,
|
||||
)
|
||||
store = OpenDALHomeArchiveStore.create_from_uri(self.e2b_s3_uri or "")
|
||||
codec = HomeSnapshotTransferTokenCodec.from_server_secret(self.server_secret_key or "")
|
||||
return HomeSnapshotGatewayService(token_codec=codec, archive_store=store)
|
||||
|
||||
|
||||
+2
-10
@@ -209,7 +209,7 @@ async def test_e2b_binding_checkpoint_and_collection() -> None:
|
||||
@pytest.mark.anyio
|
||||
async def test_e2b_s3_shared_workspace_checkpoint_and_collection() -> None:
|
||||
api_key = _required_env("DIFY_AGENT_TEST_E2B_API_KEY", "real E2B + S3")
|
||||
bucket = _required_env("DIFY_AGENT_TEST_E2B_S3_BUCKET", "real E2B + S3")
|
||||
storage_uri = _required_env("DIFY_AGENT_TEST_E2B_S3_URI", "real E2B + OpenDAL storage")
|
||||
stub_url = _required_env("DIFY_AGENT_TEST_E2B_S3_STUB_API_BASE_URL", "public Home Snapshot gateway")
|
||||
server_secret = _required_env("DIFY_AGENT_TEST_SERVER_SECRET_KEY", "Home Snapshot transfer JWE")
|
||||
template = os.environ.get(
|
||||
@@ -218,15 +218,7 @@ async def test_e2b_s3_shared_workspace_checkpoint_and_collection() -> None:
|
||||
)
|
||||
marker = uuid.uuid4().hex
|
||||
control = E2BSDKControlPlane(api_key=api_key)
|
||||
store = OpenDALHomeArchiveStore.create_s3(
|
||||
bucket=bucket,
|
||||
root=os.environ.get("DIFY_AGENT_TEST_E2B_S3_ROOT", "dify-agent-integration"),
|
||||
region=os.environ.get("DIFY_AGENT_TEST_E2B_S3_REGION") or None,
|
||||
endpoint=os.environ.get("DIFY_AGENT_TEST_E2B_S3_ENDPOINT") or None,
|
||||
access_key_id=os.environ.get("DIFY_AGENT_TEST_E2B_S3_ACCESS_KEY_ID") or None,
|
||||
secret_access_key=os.environ.get("DIFY_AGENT_TEST_E2B_S3_SECRET_ACCESS_KEY") or None,
|
||||
session_token=os.environ.get("DIFY_AGENT_TEST_E2B_S3_SESSION_TOKEN") or None,
|
||||
)
|
||||
store = OpenDALHomeArchiveStore.create_from_uri(storage_uri)
|
||||
lifecycle_cli = E2BHomeSnapshotCLI(
|
||||
token_codec=HomeSnapshotTransferTokenCodec.from_server_secret(server_secret),
|
||||
agent_stub_api_base_url=stub_url,
|
||||
|
||||
@@ -76,7 +76,7 @@ def test_create_agent_stub_app_injects_e2b_s3_home_snapshot_gateway(monkeypatch)
|
||||
settings = ServerSettings(
|
||||
runtime_backend="e2b_s3",
|
||||
e2b_api_key="e2b-secret",
|
||||
e2b_s3_bucket="snapshots",
|
||||
e2b_s3_uri="s3://snapshots/dify-agent",
|
||||
agent_stub_api_base_url="https://agent.example.com/agent-stub",
|
||||
server_secret_key=_base64url_secret(b"1" * 32),
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
import opendal
|
||||
@@ -950,6 +951,23 @@ async def test_home_snapshot_cli_failure_always_closes_control_lease(
|
||||
assert fake_e2b_lease[0].close_calls == 1
|
||||
|
||||
|
||||
def test_opendal_store_constructs_from_uri_when_required_capabilities_are_supported(tmp_path: Path) -> None:
|
||||
store = OpenDALHomeArchiveStore.create_from_uri(f"fs://{tmp_path}")
|
||||
|
||||
assert isinstance(store.operator, opendal.AsyncOperator)
|
||||
|
||||
|
||||
def test_opendal_store_rejects_uri_with_missing_required_capabilities() -> None:
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match=(
|
||||
"OpenDAL Home Snapshot storage is missing required capabilities: "
|
||||
"write, write_can_multi, write_with_if_not_exists, delete"
|
||||
),
|
||||
):
|
||||
_ = OpenDALHomeArchiveStore.create_from_uri("http://example.com/root")
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_opendal_store_conditionally_writes_streams_and_deletes_idempotently() -> None:
|
||||
store = OpenDALHomeArchiveStore(operator=opendal.AsyncOperator("memory"))
|
||||
|
||||
@@ -40,8 +40,8 @@ def test_local_backend_requires_shellctl_endpoint() -> None:
|
||||
_ = RuntimeBackendSettings(runtime_backend="local")
|
||||
|
||||
|
||||
def test_e2b_s3_requires_bucket_http_gateway_and_server_secret() -> None:
|
||||
with pytest.raises(ValidationError, match="e2b_s3_bucket"):
|
||||
def test_e2b_s3_requires_uri_http_gateway_and_server_secret() -> None:
|
||||
with pytest.raises(ValidationError, match="e2b_s3_uri"):
|
||||
_ = RuntimeBackendSettings(
|
||||
runtime_backend="e2b_s3",
|
||||
e2b_api_key="secret",
|
||||
@@ -52,14 +52,14 @@ def test_e2b_s3_requires_bucket_http_gateway_and_server_secret() -> None:
|
||||
_ = RuntimeBackendSettings(
|
||||
runtime_backend="e2b_s3",
|
||||
e2b_api_key="secret",
|
||||
e2b_s3_bucket="bucket",
|
||||
e2b_s3_uri="s3://bucket/dify-agent",
|
||||
server_secret_key=_secret(),
|
||||
)
|
||||
with pytest.raises(ValidationError, match="http\\(s\\)"):
|
||||
_ = RuntimeBackendSettings(
|
||||
runtime_backend="e2b_s3",
|
||||
e2b_api_key="secret",
|
||||
e2b_s3_bucket="bucket",
|
||||
e2b_s3_uri="s3://bucket/dify-agent",
|
||||
agent_stub_api_base_url="grpc://agent.example:9091",
|
||||
server_secret_key=_secret(),
|
||||
)
|
||||
@@ -67,34 +67,18 @@ def test_e2b_s3_requires_bucket_http_gateway_and_server_secret() -> None:
|
||||
_ = RuntimeBackendSettings(
|
||||
runtime_backend="e2b_s3",
|
||||
e2b_api_key="secret",
|
||||
e2b_s3_bucket="bucket",
|
||||
e2b_s3_uri="s3://bucket/dify-agent",
|
||||
agent_stub_api_base_url="https://agent.example/agent-stub",
|
||||
)
|
||||
|
||||
|
||||
def test_e2b_s3_validates_explicit_credentials_and_session_token() -> None:
|
||||
common: dict[str, object] = {
|
||||
"runtime_backend": "e2b_s3",
|
||||
"e2b_api_key": "secret",
|
||||
"e2b_s3_bucket": "bucket",
|
||||
"agent_stub_api_base_url": "https://agent.example/agent-stub",
|
||||
"server_secret_key": _secret(),
|
||||
}
|
||||
with pytest.raises(ValidationError, match="configured together"):
|
||||
_ = RuntimeBackendSettings.model_validate({**common, "e2b_s3_access_key_id": "access"})
|
||||
with pytest.raises(ValidationError, match="requires explicit"):
|
||||
_ = RuntimeBackendSettings.model_validate({**common, "e2b_s3_session_token": "session"})
|
||||
|
||||
settings = RuntimeBackendSettings.model_validate(
|
||||
{
|
||||
**common,
|
||||
"e2b_s3_region": " ",
|
||||
"e2b_s3_endpoint": "",
|
||||
"e2b_s3_access_key_id": "access",
|
||||
"e2b_s3_secret_access_key": "secret",
|
||||
"e2b_s3_session_token": "session",
|
||||
}
|
||||
def test_e2b_s3_normalizes_uri() -> None:
|
||||
settings = RuntimeBackendSettings(
|
||||
runtime_backend="e2b_s3",
|
||||
e2b_api_key="secret",
|
||||
e2b_s3_uri=" s3://bucket/dify-agent?region=us-east-1 ",
|
||||
agent_stub_api_base_url="https://agent.example/agent-stub",
|
||||
server_secret_key=_secret(),
|
||||
)
|
||||
assert settings.e2b_s3_root == "dify-agent"
|
||||
assert settings.e2b_s3_region is None
|
||||
assert settings.e2b_s3_endpoint is None
|
||||
|
||||
assert settings.e2b_s3_uri == "s3://bucket/dify-agent?region=us-east-1"
|
||||
|
||||
@@ -330,7 +330,7 @@ def test_create_app_injects_e2b_s3_home_snapshot_gateway(monkeypatch: pytest.Mon
|
||||
settings = ServerSettings(
|
||||
runtime_backend="e2b_s3",
|
||||
e2b_api_key="e2b-secret",
|
||||
e2b_s3_bucket="snapshots",
|
||||
e2b_s3_uri="s3://snapshots/dify-agent",
|
||||
agent_stub_api_base_url="https://agent.example.com/agent-stub",
|
||||
server_secret_key=_base64url_secret(b"1" * 32),
|
||||
)
|
||||
|
||||
@@ -310,30 +310,25 @@ def test_build_runtime_backend_profile_passes_e2b_active_timeout() -> None:
|
||||
|
||||
|
||||
def test_build_runtime_backend_profile_and_gateway_for_e2b_s3(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
store_calls: list[dict[str, object]] = []
|
||||
store_calls: list[str] = []
|
||||
stores: list[OpenDALHomeArchiveStore] = []
|
||||
|
||||
def create_s3_store(
|
||||
def create_store_from_uri(
|
||||
cls: type[OpenDALHomeArchiveStore],
|
||||
**kwargs: object,
|
||||
uri: str,
|
||||
) -> OpenDALHomeArchiveStore:
|
||||
del cls
|
||||
store_calls.append(kwargs)
|
||||
store_calls.append(uri)
|
||||
store = cast(OpenDALHomeArchiveStore, object())
|
||||
stores.append(store)
|
||||
return store
|
||||
|
||||
monkeypatch.setattr(OpenDALHomeArchiveStore, "create_s3", classmethod(create_s3_store))
|
||||
monkeypatch.setattr(OpenDALHomeArchiveStore, "create_from_uri", classmethod(create_store_from_uri))
|
||||
storage_uri = "s3://snapshots/tenant-root?region=us-east-1"
|
||||
settings = ServerSettings(
|
||||
runtime_backend="e2b_s3",
|
||||
e2b_api_key="e2b-secret",
|
||||
e2b_s3_bucket="snapshots",
|
||||
e2b_s3_root="tenant-root",
|
||||
e2b_s3_region="us-east-1",
|
||||
e2b_s3_endpoint="https://s3.example.test",
|
||||
e2b_s3_access_key_id="access-key",
|
||||
e2b_s3_secret_access_key="secret-key",
|
||||
e2b_s3_session_token="session-token",
|
||||
e2b_s3_uri=storage_uri,
|
||||
agent_stub_api_base_url="https://agent.example/agent-stub",
|
||||
server_secret_key=_base64url_secret(b"s" * 32),
|
||||
)
|
||||
@@ -347,16 +342,7 @@ def test_build_runtime_backend_profile_and_gateway_for_e2b_s3(monkeypatch: pytes
|
||||
assert profile.home_snapshots.control_plane is profile.execution_bindings.control_plane
|
||||
assert profile.home_snapshots.lifecycle_cli is profile.execution_bindings.lifecycle_cli
|
||||
assert gateway is not None
|
||||
expected_store_settings = {
|
||||
"bucket": "snapshots",
|
||||
"root": "tenant-root",
|
||||
"region": "us-east-1",
|
||||
"endpoint": "https://s3.example.test",
|
||||
"access_key_id": "access-key",
|
||||
"secret_access_key": "secret-key",
|
||||
"session_token": "session-token",
|
||||
}
|
||||
assert store_calls == [expected_store_settings, expected_store_settings]
|
||||
assert store_calls == [storage_uri, storage_uri]
|
||||
assert profile.home_snapshots.archive_store is stores[0]
|
||||
assert gateway.archive_store is stores[1]
|
||||
assert gateway.token_codec is not profile.home_snapshots.lifecycle_cli.token_codec
|
||||
@@ -366,8 +352,8 @@ def test_home_snapshot_gateway_is_not_built_for_other_profiles() -> None:
|
||||
assert ServerSettings().build_home_snapshot_gateway() is None
|
||||
|
||||
|
||||
def test_server_settings_rejects_e2b_s3_without_required_s3_or_http_settings() -> None:
|
||||
with pytest.raises(ValidationError, match="DIFY_AGENT_E2B_S3_BUCKET"):
|
||||
def test_server_settings_rejects_e2b_s3_without_required_storage_or_http_settings() -> None:
|
||||
with pytest.raises(ValidationError, match="DIFY_AGENT_E2B_S3_URI"):
|
||||
_ = ServerSettings(
|
||||
runtime_backend="e2b_s3",
|
||||
e2b_api_key="e2b-secret",
|
||||
@@ -378,7 +364,7 @@ def test_server_settings_rejects_e2b_s3_without_required_s3_or_http_settings() -
|
||||
_ = ServerSettings(
|
||||
runtime_backend="e2b_s3",
|
||||
e2b_api_key="e2b-secret",
|
||||
e2b_s3_bucket="snapshots",
|
||||
e2b_s3_uri="s3://snapshots/dify-agent",
|
||||
agent_stub_api_base_url="grpc://agent.example:9091",
|
||||
server_secret_key=_base64url_secret(b"s" * 32),
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ SERVER_RUNTIME_DEPENDENCIES = {
|
||||
"jsonschema>=4.23.0,<5.0.0",
|
||||
"jwcrypto>=1.5.6,<2",
|
||||
"logfire[fastapi,httpx,redis]>=4.37.0,<5.0.0",
|
||||
"opendal==0.47.3",
|
||||
"opendal==0.47.5",
|
||||
"pydantic-ai-slim[anthropic,google,openai]>=1.85.1,<2.0.0",
|
||||
"pydantic-settings>=2.12.0,<3.0.0",
|
||||
"redis>=7.4.0,<8.0.0",
|
||||
|
||||
Generated
+22
-22
@@ -647,7 +647,7 @@ requires-dist = [
|
||||
{ name = "jsonschema", marker = "extra == 'server'", specifier = ">=4.23.0,<5.0.0" },
|
||||
{ name = "jwcrypto", marker = "extra == 'server'", specifier = ">=1.5.6,<2" },
|
||||
{ name = "logfire", extras = ["fastapi", "httpx", "redis"], marker = "extra == 'server'", specifier = ">=4.37.0,<5.0.0" },
|
||||
{ name = "opendal", marker = "extra == 'server'", specifier = "==0.47.3" },
|
||||
{ name = "opendal", marker = "extra == 'server'", specifier = "==0.47.5", index = "https://test.pypi.org/simple" },
|
||||
{ name = "protobuf", marker = "extra == 'grpc'", specifier = ">=6.33.5,<7.0.0" },
|
||||
{ name = "pydantic", specifier = ">=2.12.5,<2.13" },
|
||||
{ name = "pydantic-ai-slim", specifier = ">=1.102.0,<2.0.0" },
|
||||
@@ -2052,28 +2052,28 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "opendal"
|
||||
version = "0.47.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/56/048e7013047c2b80c4415c6f41c3099c676da34ac914e463204f18091645/opendal-0.47.3.tar.gz", hash = "sha256:f91873645c6ce391755a0352fe9e469699419e61d60c89d3c8f5c3b262451430", size = 1419363, upload-time = "2026-07-10T11:07:31.103Z" }
|
||||
version = "0.47.5"
|
||||
source = { registry = "https://test.pypi.org/simple" }
|
||||
sdist = { url = "https://test-files.pythonhosted.org/packages/48/65/44f2816c43e01a637ac8e77a2dde99c0f131b7617200af97c87637fd63fb/opendal-0.47.5.tar.gz", hash = "sha256:ca44d9eb5bf27d905e55d480d656dab1955b1528718c75dcb5d279fd19634b57", size = 1766937, upload-time = "2026-07-28T08:10:01.164Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/46/58/2bd9961bbbc18b106e5e64cc6cb7093abd21465b10e0bd6fca515bfec52f/opendal-0.47.3-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8187ad7124e909dc7b89f7227506bedddfeef389d376a16667bffeb8d80ebaa3", size = 17819057, upload-time = "2026-07-10T11:06:46.182Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/b1/39c09024f179344c603a784c8d16ee80ac7345d4eccc42bda052be4b364c/opendal-0.47.3-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:356cf2f677d94f241bcca84a2e6e4d165458ef694bd31591eb39c9cd534e8e87", size = 16436832, upload-time = "2026-07-10T11:06:48.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/98/230bff458603fca13f8f5277e9e96c4a56603093c0bb97153bbd0dc914ef/opendal-0.47.3-cp311-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1960a653ba38e6d7ca91109a815c5549b53ffa6f69237a0190abb5a511475114", size = 17180352, upload-time = "2026-07-10T11:06:51.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/fb/9f69d6543d84db3369985d28d0918ec8357bf2b65eb90e21b2266910b629/opendal-0.47.3-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e4e5a87a903f2b30ee0fb2626cddb08450fc2669a6df5f6a56225425476377a", size = 18259558, upload-time = "2026-07-10T11:06:53.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/8b/9b6ccf5d4f486f5672d200b0daef8666bf1ee1e43bf321ecedda43789b2e/opendal-0.47.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4b864c02dc84be808d6e98011863b74fe2050db396c7dd061c940e3f3f6e294b", size = 17372540, upload-time = "2026-07-10T11:06:56.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/a7/cc170bd00a187460399cfb219d3d52fbdf345ad0416bcd87aa69c880169e/opendal-0.47.3-cp311-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:dcbbd4825b2d90a747b8f3ccd8f5208328930cb7cb9cb7ef13dd6b58a81f5093", size = 17731303, upload-time = "2026-07-10T11:06:58.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/9b/1f5bd9f67a11ad0d86def760cea189386f1fd061d10a61936cfe8010dc94/opendal-0.47.3-cp311-abi3-musllinux_1_1_armv7l.whl", hash = "sha256:df9e9d7d510165ea62447b3e7488b1dff06323dc8d2617f80bd7d984ad798405", size = 17470164, upload-time = "2026-07-10T11:07:01.322Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/b5/b067b0055d1fb331a7c9b28bce142d3d3a83dd0f56480d9f735091784bc2/opendal-0.47.3-cp311-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:47a529f229976495e5229f6b916fcffc4c4f950c04380406f531d5b4735507b7", size = 18501611, upload-time = "2026-07-10T11:07:03.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/38/ef8fbfd6d21158d5ac2ea4c694f04790cd122c29da54933b712d5cdacdd4/opendal-0.47.3-cp311-abi3-win_amd64.whl", hash = "sha256:cca2d8d9a40e6151529ba8eab3b15acbb15497f41514139b53dfe227cb514bd2", size = 19671465, upload-time = "2026-07-10T11:07:06.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/40/6b89a058d58193c6146a4a3a3646dadb2608afe80b6c8c79814cd11033f3/opendal-0.47.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:051a02d56a823e1993d7686cec8bab2174dd3ffd82d8531b6fd0daa6adec807a", size = 17817945, upload-time = "2026-07-10T11:07:08.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/55/7a836c0a114ace48c69168f7257663baaf825df9d47d3d6a1179934a1a6e/opendal-0.47.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4c04dc93789c4140df94341598553dce23f43b20f2f709282998b927bccd8a2a", size = 16414489, upload-time = "2026-07-10T11:07:11.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/47/03bb7a6141841e373bd310f6b7e03057675437f9b9a9056c0147d0698c8d/opendal-0.47.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cd43422502d77ad153b041b6bfa7c6831c95e16f0d783e69e80afcf237250ef8", size = 17163020, upload-time = "2026-07-10T11:07:13.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/0c/5c5185b5c5e63fefa99138e69304e51a7eb35cbb6396e022d94b1d71c598/opendal-0.47.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a61bc5b3e6c0d8702f3e7605b68d41a20c89a8e59e6f461882fa7b2142ba49a2", size = 18242953, upload-time = "2026-07-10T11:07:16.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/e6/8b8a683835708e37cd85739487f90f3fdd84f03cbecbc5262f9942eab6f8/opendal-0.47.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b719146ebefa6f3c52e329c1c9beec54b8f26aa04c4789197d4aa9e12d9aab33", size = 17358553, upload-time = "2026-07-10T11:07:18.742Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/f4/2e45e9647f307cb7e3e0e81c2140f0dadf1f96672ff7d58caee377e5f837/opendal-0.47.3-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:375b585a9eafc119d50c1dbb3034dc0e35db6dbb87fbacbb6505067433f21782", size = 17714742, upload-time = "2026-07-10T11:07:21.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/da/37ca6f8d8500dbf72e01974eba68981639aaa3dec077d0cdcfbfa8519930/opendal-0.47.3-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:9841083c98e8896dd208dc40cc4a7905eb0b0a8af8422b2a94dd3d45b0a31383", size = 17455237, upload-time = "2026-07-10T11:07:24.249Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/23/72d566b06778c1af4aa59ea43c6c4f28011f77d4f55a0cefd87b62c319e9/opendal-0.47.3-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:376c85766b973fe423dfa0fc03245557b27ffcb88f087d62f70c96585938fb5c", size = 18485329, upload-time = "2026-07-10T11:07:26.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/44/0e57e3804af4a5ae45e78a06794d072d1635b09445bcb98a797afdd322bf/opendal-0.47.3-cp314-cp314t-win_amd64.whl", hash = "sha256:14b53bb4d188da150be15cd6702d6ef866885d452474e9d4b96c39cf3c8bc055", size = 19644607, upload-time = "2026-07-10T11:07:29.029Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/17/91/b572904880ee8998031fdc7afbaf292e52bf20e523c6d13968e6b3baa009/opendal-0.47.5-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:57a453e32dee21d4d943a59c997b6a41ac658c94b2b40a656bd9f4613a61f8be", size = 17529259, upload-time = "2026-07-28T08:09:17.218Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/31/e2/caa9c622d1c4604f5e6ee9a3aaf2cc39bf8e3889b2326528b959db0fc3ff/opendal-0.47.5-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:fb2efabdb98beb8eb8ce9bbdb33662f4430a95df9716320f90dd8608b826022b", size = 16118000, upload-time = "2026-07-28T08:09:19.679Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/28/e3/463213e2fa8e98a8ca4699f2fe13159290ac80bf242bbe70f119d36473c3/opendal-0.47.5-cp311-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:342a51b8e5fae9f049492af72fa4f16aaf6d16177f31edd0ee5178b57067be64", size = 16803733, upload-time = "2026-07-28T08:09:22.393Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/2e/4f/a2441d286c2534fb2f00d23f18b684b6bf327e74da8a3a4367ebf766dccf/opendal-0.47.5-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5101c9b75ad3f4a24593def41f7ce723962b79cb3e601151e808f5d78f78e87", size = 17955349, upload-time = "2026-07-28T08:09:24.774Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/d8/0e/a63cdeec6ba72270c10d93361f97dcc60105aa06c91b127328badb962aa7/opendal-0.47.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3a403748c68b9587405ede6236d3618a8f5833ccede623be7386f24d4e5c31a8", size = 17026301, upload-time = "2026-07-28T08:09:27.847Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/8a/5a/86d009a6bab0c02e636b5087b47bb9a7fd7b7b1785f2362d88c846ef9485/opendal-0.47.5-cp311-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:90703efb7f365639dc843d475f2b2477ec561824c0a68ad6a75c35f5c20e7751", size = 17375804, upload-time = "2026-07-28T08:09:30.801Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/99/11/ff7365587b241aacba5537c78864790b60dc8bb660796bea1fa201a97ca0/opendal-0.47.5-cp311-abi3-musllinux_1_1_armv7l.whl", hash = "sha256:c28395331c92839f91a162ba2b92edf2e92f023ff13adb1bb46b234da8172eb8", size = 17102402, upload-time = "2026-07-28T08:09:33.085Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/9b/3f/b24d881b970a41133be418876c79ba5b34c6e9811029b5d4fb9c04ccf203/opendal-0.47.5-cp311-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ee27f1df07608a4ba30c9a5f09341850f7f6c8695d6559522302c2c410db161f", size = 18187917, upload-time = "2026-07-28T08:09:35.408Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/90/b8/0b387e569b098e923977e36ec1b243c15dfe387eba9026984068bd87cd05/opendal-0.47.5-cp311-abi3-win_amd64.whl", hash = "sha256:1016ad511d8c61611719c9ffe509571ee216791bfcc07dc6aec47a756c30aa67", size = 19199295, upload-time = "2026-07-28T08:09:37.91Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/3c/9f/63a687163b2a9ba66af8b4206ae4a7ae226e9c54355298cbef15cc54bfa5/opendal-0.47.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:012eeed30870754e7edc4895ee75306e32ff82b1d340d897b504ab8b7d02cfaf", size = 17536831, upload-time = "2026-07-28T08:09:41.003Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/d5/d9/e1cb2dcb2c4d919f30b93ec6c3d777525e476074f6938b7aa9018f8305f9/opendal-0.47.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:99cadd469dcecb17866a20826e080e58c9dc0b4205340870603b58faae1e0f97", size = 16101302, upload-time = "2026-07-28T08:09:43.3Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/d9/c0/d179754422478d879a732a81d9ea6d89607bbc645d6ec07f7036fe4d528d/opendal-0.47.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4968311d0f483311273192b90c79e0731eea32598ef253bb41de9c51bff70af8", size = 16797051, upload-time = "2026-07-28T08:09:45.311Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/a0/2f/fcb7f079badbc7306d778f8b20a0ab83ff34d74bd4fe7e0c3a118dd5a05c/opendal-0.47.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47ecf76aa397cb06329d4e69e6c0afdfc34c5b6ae7c6da87efd33c006b38cde5", size = 17949096, upload-time = "2026-07-28T08:09:47.875Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/c1/0c/bfdd05726f5c1c15b0b4e26e6337b8d8a1cd2a0aa7f5cdfa617b1041c141/opendal-0.47.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b0a31a17f67ad59b7f3baf0c1cec7e60949ce3f7c7ebf5baba493015d8ac89e3", size = 17016235, upload-time = "2026-07-28T08:09:50.176Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/2c/ef/210fc93d5173cd7e5634b31ebee87466845b90eb92483c50d8f76d240ba7/opendal-0.47.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:307f095d31056162a16ce02a44253f1b3de60cf638f837c5bfca5bbc06c53bdb", size = 17369912, upload-time = "2026-07-28T08:09:52.298Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/31/8e/0a615f7344d85e07b39ba577ebe15211816611f032770d8c7ace6422338a/opendal-0.47.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:c526449e91b8442116acc77ec8f3dc7c87bbdd63f06acefc3e1ff880a06eb3a2", size = 17094062, upload-time = "2026-07-28T08:09:54.96Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/f1/89/103514cebe8fc760741378772657eadfcf156412d72064344e19f53f10a3/opendal-0.47.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:b77948ec4ae48b3e9ce4fc6e6e196f82324916458305a01d81282ed520f303a0", size = 18180436, upload-time = "2026-07-28T08:09:57.267Z" },
|
||||
{ url = "https://test-files.pythonhosted.org/packages/08/85/9e00b103f5c0bbc58e46c506d68b1179bafc6ab24104b62999dda3842082/opendal-0.47.5-cp314-cp314t-win_amd64.whl", hash = "sha256:d03f8dba373bcb1d080bf0b09165c9b6b1e546c30d0a1718bbe00d01e576688f", size = 19183036, upload-time = "2026-07-28T08:09:59.304Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+1
-7
@@ -281,13 +281,7 @@ DIFY_AGENT_E2B_TEMPLATE=difys-default-team/dify-agent-local-sandbox
|
||||
DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS=3600
|
||||
DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN=
|
||||
DIFY_AGENT_E2B_SHELLCTL_PORT=5004
|
||||
DIFY_AGENT_E2B_S3_BUCKET=
|
||||
DIFY_AGENT_E2B_S3_ROOT=dify-agent
|
||||
DIFY_AGENT_E2B_S3_REGION=
|
||||
DIFY_AGENT_E2B_S3_ENDPOINT=
|
||||
DIFY_AGENT_E2B_S3_ACCESS_KEY_ID=
|
||||
DIFY_AGENT_E2B_S3_SECRET_ACCESS_KEY=
|
||||
DIFY_AGENT_E2B_S3_SESSION_TOKEN=
|
||||
DIFY_AGENT_E2B_S3_URI=
|
||||
DIFY_AGENT_STUB_API_BASE_URL=http://agent_backend:5050/agent-stub
|
||||
# This is security-sensitive: it derives the JWE encryption key for Agent Stub bearer tokens.
|
||||
# Replace this development default in production.
|
||||
|
||||
@@ -676,13 +676,7 @@ services:
|
||||
DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS: ${DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS:-3600}
|
||||
DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN: ${DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN:-}
|
||||
DIFY_AGENT_E2B_SHELLCTL_PORT: ${DIFY_AGENT_E2B_SHELLCTL_PORT:-5004}
|
||||
DIFY_AGENT_E2B_S3_BUCKET: ${DIFY_AGENT_E2B_S3_BUCKET:-}
|
||||
DIFY_AGENT_E2B_S3_ROOT: ${DIFY_AGENT_E2B_S3_ROOT:-dify-agent}
|
||||
DIFY_AGENT_E2B_S3_REGION: ${DIFY_AGENT_E2B_S3_REGION:-}
|
||||
DIFY_AGENT_E2B_S3_ENDPOINT: ${DIFY_AGENT_E2B_S3_ENDPOINT:-}
|
||||
DIFY_AGENT_E2B_S3_ACCESS_KEY_ID: ${DIFY_AGENT_E2B_S3_ACCESS_KEY_ID:-}
|
||||
DIFY_AGENT_E2B_S3_SECRET_ACCESS_KEY: ${DIFY_AGENT_E2B_S3_SECRET_ACCESS_KEY:-}
|
||||
DIFY_AGENT_E2B_S3_SESSION_TOKEN: ${DIFY_AGENT_E2B_S3_SESSION_TOKEN:-}
|
||||
DIFY_AGENT_E2B_S3_URI: ${DIFY_AGENT_E2B_S3_URI:-}
|
||||
DIFY_AGENT_SANDBOX_FILE_UPLOAD_MAX_BYTES: ${PLUGIN_MAX_FILE_SIZE:-52428800}
|
||||
DIFY_AGENT_STUB_API_BASE_URL: ${DIFY_AGENT_STUB_API_BASE_URL:-http://agent_backend:5050/agent-stub}
|
||||
# This is security-sensitive: it derives the JWE encryption key for Agent Stub bearer tokens.
|
||||
|
||||
@@ -28,13 +28,7 @@ services:
|
||||
DIFY_AGENT_RUNTIME_BACKEND: ${DIFY_AGENT_RUNTIME_BACKEND:-e2b}
|
||||
DIFY_AGENT_E2B_API_KEY: ${DIFY_AGENT_E2B_API_KEY:-${E2B_API_KEY:-${E2B_API_TOKEN:-}}}
|
||||
DIFY_AGENT_E2B_TEMPLATE: ${DIFY_AGENT_E2B_TEMPLATE:-difys-default-team/dify-agent-local-sandbox}
|
||||
DIFY_AGENT_E2B_S3_BUCKET: ${DIFY_AGENT_E2B_S3_BUCKET:-}
|
||||
DIFY_AGENT_E2B_S3_ROOT: ${DIFY_AGENT_E2B_S3_ROOT:-dify-agent}
|
||||
DIFY_AGENT_E2B_S3_REGION: ${DIFY_AGENT_E2B_S3_REGION:-}
|
||||
DIFY_AGENT_E2B_S3_ENDPOINT: ${DIFY_AGENT_E2B_S3_ENDPOINT:-}
|
||||
DIFY_AGENT_E2B_S3_ACCESS_KEY_ID: ${DIFY_AGENT_E2B_S3_ACCESS_KEY_ID:-}
|
||||
DIFY_AGENT_E2B_S3_SECRET_ACCESS_KEY: ${DIFY_AGENT_E2B_S3_SECRET_ACCESS_KEY:-}
|
||||
DIFY_AGENT_E2B_S3_SESSION_TOKEN: ${DIFY_AGENT_E2B_S3_SESSION_TOKEN:-}
|
||||
DIFY_AGENT_E2B_S3_URI: ${DIFY_AGENT_E2B_S3_URI:-}
|
||||
ports:
|
||||
- "${EXPOSE_AGENT_BACKEND_PORT:-15050}:5050"
|
||||
|
||||
|
||||
@@ -682,13 +682,7 @@ services:
|
||||
DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS: ${DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS:-3600}
|
||||
DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN: ${DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN:-}
|
||||
DIFY_AGENT_E2B_SHELLCTL_PORT: ${DIFY_AGENT_E2B_SHELLCTL_PORT:-5004}
|
||||
DIFY_AGENT_E2B_S3_BUCKET: ${DIFY_AGENT_E2B_S3_BUCKET:-}
|
||||
DIFY_AGENT_E2B_S3_ROOT: ${DIFY_AGENT_E2B_S3_ROOT:-dify-agent}
|
||||
DIFY_AGENT_E2B_S3_REGION: ${DIFY_AGENT_E2B_S3_REGION:-}
|
||||
DIFY_AGENT_E2B_S3_ENDPOINT: ${DIFY_AGENT_E2B_S3_ENDPOINT:-}
|
||||
DIFY_AGENT_E2B_S3_ACCESS_KEY_ID: ${DIFY_AGENT_E2B_S3_ACCESS_KEY_ID:-}
|
||||
DIFY_AGENT_E2B_S3_SECRET_ACCESS_KEY: ${DIFY_AGENT_E2B_S3_SECRET_ACCESS_KEY:-}
|
||||
DIFY_AGENT_E2B_S3_SESSION_TOKEN: ${DIFY_AGENT_E2B_S3_SESSION_TOKEN:-}
|
||||
DIFY_AGENT_E2B_S3_URI: ${DIFY_AGENT_E2B_S3_URI:-}
|
||||
DIFY_AGENT_SANDBOX_FILE_UPLOAD_MAX_BYTES: ${PLUGIN_MAX_FILE_SIZE:-52428800}
|
||||
DIFY_AGENT_STUB_API_BASE_URL: ${DIFY_AGENT_STUB_API_BASE_URL:-http://agent_backend:5050/agent-stub}
|
||||
# This is security-sensitive: it derives the JWE encryption key for Agent Stub bearer tokens.
|
||||
|
||||
@@ -32,13 +32,7 @@ DIFY_AGENT_E2B_TEMPLATE=difys-default-team/dify-agent-local-sandbox
|
||||
DIFY_AGENT_E2B_ACTIVE_TIMEOUT_SECONDS=3600
|
||||
DIFY_AGENT_E2B_SHELLCTL_AUTH_TOKEN=
|
||||
DIFY_AGENT_E2B_SHELLCTL_PORT=5004
|
||||
DIFY_AGENT_E2B_S3_BUCKET=
|
||||
DIFY_AGENT_E2B_S3_ROOT=dify-agent
|
||||
DIFY_AGENT_E2B_S3_REGION=
|
||||
DIFY_AGENT_E2B_S3_ENDPOINT=
|
||||
DIFY_AGENT_E2B_S3_ACCESS_KEY_ID=
|
||||
DIFY_AGENT_E2B_S3_SECRET_ACCESS_KEY=
|
||||
DIFY_AGENT_E2B_S3_SESSION_TOKEN=
|
||||
DIFY_AGENT_E2B_S3_URI=
|
||||
# Standalone/direct-container byte limit. Full Docker Compose derives this from
|
||||
# PLUGIN_MAX_FILE_SIZE in docker/.env.
|
||||
DIFY_AGENT_SANDBOX_FILE_UPLOAD_MAX_BYTES=52428800
|
||||
|
||||
Reference in New Issue
Block a user