mirror of
https://github.com/langgenius/dify.git
synced 2026-10-16 15:35:32 +08:00
feat(api): add sandbox info endpoint for Agent App conversations (#38390)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
autofix-ci[bot]
parent
d3ed533716
commit
5e17af4530
@@ -9,13 +9,17 @@ from dify_agent.protocol import SandboxListResponse, SandboxReadResponse, Sandbo
|
||||
|
||||
from controllers.console import agent_app_sandbox as module
|
||||
from models.model import App, AppMode, IconType
|
||||
from services.agent_app_sandbox_service import AgentSandboxInspectorError
|
||||
from services.agent_app_sandbox_service import AgentSandboxInfo, AgentSandboxInspectorError
|
||||
|
||||
|
||||
class _AgentAppService:
|
||||
def __init__(self) -> None:
|
||||
self.calls: list[tuple[str, str, str, str, str]] = []
|
||||
|
||||
def get_info(self, *, tenant_id: str, app_id: str, conversation_id: str) -> AgentSandboxInfo:
|
||||
self.calls.append(("info", tenant_id, app_id, conversation_id, ""))
|
||||
return AgentSandboxInfo(session_id="abc1234", workspace_cwd="~/workspace/abc1234")
|
||||
|
||||
def list_files(self, *, tenant_id: str, app_id: str, conversation_id: str, path: str) -> SandboxListResponse:
|
||||
self.calls.append(("list", tenant_id, app_id, conversation_id, path))
|
||||
return SandboxListResponse(path=path, entries=[], truncated=False)
|
||||
@@ -131,14 +135,17 @@ def test_agent_app_sandbox_resources_proxy_service(monkeypatch: pytest.MonkeyPat
|
||||
SimpleNamespace(get_json=lambda silent=True: {"conversation_id": "conv-1", "path": "report.txt"}),
|
||||
)
|
||||
|
||||
info = unwrap(module.AgentAppSandboxInfoResource.get)(object(), "tenant-1", "agent-1")
|
||||
listing = unwrap(module.AgentAppSandboxListResource.get)(object(), "tenant-1", "agent-1")
|
||||
preview = unwrap(module.AgentAppSandboxReadResource.get)(object(), "tenant-1", "agent-1")
|
||||
upload = unwrap(module.AgentAppSandboxUploadResource.post)(object(), "tenant-1", "agent-1")
|
||||
|
||||
assert info == {"session_id": "abc1234", "workspace_cwd": "~/workspace/abc1234"}
|
||||
assert listing["path"] == "sub/report.txt"
|
||||
assert preview["text"] == "hello"
|
||||
assert upload["file"]["reference"] == "dify-file-ref:file-1"
|
||||
assert service.calls == [
|
||||
("info", "tenant-1", "app-1", "conv-1", ""),
|
||||
("list", "tenant-1", "app-1", "conv-1", "sub/report.txt"),
|
||||
("read", "tenant-1", "app-1", "conv-1", "sub/report.txt"),
|
||||
("upload", "tenant-1", "app-1", "conv-1", "report.txt"),
|
||||
@@ -147,6 +154,9 @@ def test_agent_app_sandbox_resources_proxy_service(monkeypatch: pytest.MonkeyPat
|
||||
|
||||
def test_agent_app_sandbox_resource_returns_normalized_errors(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
class FailingService:
|
||||
def get_info(self, **kwargs):
|
||||
raise AgentSandboxInspectorError("no_active_session", "no active session", status_code=404)
|
||||
|
||||
def list_files(self, **kwargs):
|
||||
raise AgentSandboxInspectorError("no_active_session", "no active session", status_code=404)
|
||||
|
||||
@@ -156,6 +166,10 @@ def test_agent_app_sandbox_resource_returns_normalized_errors(monkeypatch: pytes
|
||||
module, "query_params_from_request", lambda model: SimpleNamespace(conversation_id="conv-1", path=".")
|
||||
)
|
||||
|
||||
assert unwrap(module.AgentAppSandboxInfoResource.get)(object(), "tenant-1", "agent-1") == (
|
||||
{"code": "no_active_session", "message": "no active session"},
|
||||
404,
|
||||
)
|
||||
assert unwrap(module.AgentAppSandboxListResource.get)(object(), "tenant-1", "agent-1") == (
|
||||
{"code": "no_active_session", "message": "no active session"},
|
||||
404,
|
||||
|
||||
@@ -23,14 +23,23 @@ from services.agent_app_sandbox_service import (
|
||||
)
|
||||
|
||||
|
||||
def _snapshot(*, session_id: str = "abc1234") -> CompositorSessionSnapshot:
|
||||
def _snapshot(
|
||||
*,
|
||||
session_id: str = "abc1234",
|
||||
shell_runtime_state: dict[str, str] | None = None,
|
||||
) -> CompositorSessionSnapshot:
|
||||
runtime_state = (
|
||||
{"session_id": session_id, "workspace_cwd": f"~/workspace/{session_id}"}
|
||||
if shell_runtime_state is None
|
||||
else shell_runtime_state
|
||||
)
|
||||
return CompositorSessionSnapshot(
|
||||
layers=[
|
||||
LayerSessionSnapshot(name="execution_context", lifecycle_state=LifecycleState.SUSPENDED, runtime_state={}),
|
||||
LayerSessionSnapshot(
|
||||
name="shell",
|
||||
lifecycle_state=LifecycleState.SUSPENDED,
|
||||
runtime_state={"session_id": session_id, "workspace_cwd": f"~/workspace/{session_id}"},
|
||||
runtime_state=runtime_state,
|
||||
),
|
||||
]
|
||||
)
|
||||
@@ -77,7 +86,10 @@ class FakeClient:
|
||||
)
|
||||
|
||||
|
||||
def _stored_session() -> StoredAgentAppSession:
|
||||
def _stored_session(
|
||||
*,
|
||||
session_snapshot: CompositorSessionSnapshot | None = None,
|
||||
) -> StoredAgentAppSession:
|
||||
return StoredAgentAppSession(
|
||||
scope=AgentAppSessionScope(
|
||||
tenant_id="tenant-1",
|
||||
@@ -86,12 +98,25 @@ def _stored_session() -> StoredAgentAppSession:
|
||||
agent_id="agent-1",
|
||||
agent_config_snapshot_id="snapshot-1",
|
||||
),
|
||||
session_snapshot=_snapshot(),
|
||||
session_snapshot=_snapshot() if session_snapshot is None else session_snapshot,
|
||||
backend_run_id="run-1",
|
||||
runtime_layer_specs=_runtime_layer_specs(),
|
||||
)
|
||||
|
||||
|
||||
def test_agent_app_sandbox_service_get_info_returns_metadata() -> None:
|
||||
store = FakeStore(_stored_session())
|
||||
client = FakeClient()
|
||||
service = AgentAppSandboxService(session_store=store, client_factory=lambda: client) # type: ignore[arg-type]
|
||||
|
||||
result = service.get_info(tenant_id="tenant-1", app_id="app-1", conversation_id="conv-1")
|
||||
|
||||
assert result.session_id == "abc1234"
|
||||
assert result.workspace_cwd == "~/workspace/abc1234"
|
||||
assert client.calls == []
|
||||
assert store.scope == ("tenant-1", "app-1", "conv-1")
|
||||
|
||||
|
||||
def test_agent_app_sandbox_service_builds_locator_and_proxies() -> None:
|
||||
store = FakeStore(_stored_session())
|
||||
client = FakeClient()
|
||||
@@ -108,31 +133,21 @@ def test_agent_app_sandbox_service_raises_when_no_active_session() -> None:
|
||||
service = AgentAppSandboxService(session_store=FakeStore(None), client_factory=lambda: FakeClient()) # type: ignore[arg-type]
|
||||
|
||||
with pytest.raises(AgentSandboxInspectorError) as exc_info:
|
||||
service.read_file(tenant_id="tenant-1", app_id="app-1", conversation_id="conv-1", path="note.txt")
|
||||
service.get_info(tenant_id="tenant-1", app_id="app-1", conversation_id="conv-1")
|
||||
|
||||
assert exc_info.value.code == "no_active_session"
|
||||
assert exc_info.value.status_code == 404
|
||||
|
||||
|
||||
def test_agent_app_sandbox_service_raises_when_runtime_specs_cannot_build_locator() -> None:
|
||||
broken_session = StoredAgentAppSession(
|
||||
scope=AgentAppSessionScope(
|
||||
tenant_id="tenant-1",
|
||||
app_id="app-1",
|
||||
conversation_id="conv-1",
|
||||
agent_id="agent-1",
|
||||
agent_config_snapshot_id="snapshot-1",
|
||||
),
|
||||
session_snapshot=_snapshot(),
|
||||
backend_run_id="run-1",
|
||||
runtime_layer_specs=[],
|
||||
)
|
||||
def test_agent_app_sandbox_service_raises_when_shell_workspace_metadata_missing() -> None:
|
||||
broken_session = _stored_session(session_snapshot=_snapshot(shell_runtime_state={"session_id": "abc1234"}))
|
||||
service = AgentAppSandboxService(session_store=FakeStore(broken_session), client_factory=lambda: FakeClient()) # type: ignore[arg-type]
|
||||
|
||||
with pytest.raises(AgentSandboxInspectorError) as exc_info:
|
||||
service.list_files(tenant_id="tenant-1", app_id="app-1", conversation_id="conv-1", path=".")
|
||||
service.get_info(tenant_id="tenant-1", app_id="app-1", conversation_id="conv-1")
|
||||
|
||||
assert exc_info.value.code == "no_sandbox"
|
||||
assert exc_info.value.status_code == 404
|
||||
|
||||
|
||||
def test_default_client_factory_requires_agent_backend_base_url(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
||||
Reference in New Issue
Block a user