Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions python/packages/devui/agent_framework_devui/_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,27 @@ def _extract_workflow_hil_responses(self, input_data: Any) -> dict[str, Any] | N
Returns:
Dict of {request_id: response_value} if found, None otherwise
"""
# Handle case where input_data might be a JSON string (from streamWorkflowExecutionOpenAI)
# The input field type is: str | list[Any] | dict[str, Any]
if isinstance(input_data, str):
try:
parsed = json.loads(input_data)
# Only use parsed value if it's a list (ResponseInputParam format expected for HIL)
if isinstance(parsed, list):
input_data = parsed
else:
# Parsed to dict, string, or primitive - not HIL response format
return None
except (json.JSONDecodeError, TypeError):
# Plain text string, not valid JSON - not HIL format
return None

# At this point, input_data should be a list or dict
# HIL responses are always in list format (ResponseInputParam)
if isinstance(input_data, dict):
# This is structured workflow input (dict), not HIL responses
return None

if not isinstance(input_data, list):
return None

Expand Down
21 changes: 21 additions & 0 deletions python/packages/devui/tests/test_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,27 @@ class WorkflowInput(BaseModel):
assert parsed.metadata == {"key": "value"}


def test_extract_workflow_hil_responses_handles_stringified_json():
"""Test HIL response extraction handles both stringified and parsed JSON (regression test)."""
from agent_framework_devui._discovery import EntityDiscovery
from agent_framework_devui._executor import AgentFrameworkExecutor
from agent_framework_devui._mapper import MessageMapper

executor = AgentFrameworkExecutor(EntityDiscovery(None), MessageMapper())

# Regression test: Frontend sends stringified JSON via streamWorkflowExecutionOpenAI
stringified = '[{"type":"message","content":[{"type":"workflow_hil_response","responses":{"req_1":"spam"}}]}]'
assert executor._extract_workflow_hil_responses(stringified) == {"req_1": "spam"}

# Ensure parsed format still works
parsed = [{"type": "message", "content": [{"type": "workflow_hil_response", "responses": {"req_2": "ham"}}]}]
assert executor._extract_workflow_hil_responses(parsed) == {"req_2": "ham"}

# Non-HIL inputs should return None
assert executor._extract_workflow_hil_responses("plain text") is None
assert executor._extract_workflow_hil_responses({"email": "test"}) is None


async def test_executor_handles_non_streaming_agent():
"""Test executor can handle agents with only run() method (no run_stream)."""
from agent_framework import AgentRunResponse, AgentThread, ChatMessage, Role, TextContent
Expand Down
17 changes: 7 additions & 10 deletions python/packages/devui/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,18 +280,14 @@ async def test_api_restrictions_in_user_mode():
assert dev_client.get("/v1/entities").status_code == 200
assert user_client.get("/v1/entities").status_code == 200

# Test 4: Entity info should be restricted in user mode
# Test 4: Entity info should be accessible in both modes (UI needs this)
dev_response = dev_client.get("/v1/entities/test_agent/info")
assert dev_response.status_code in [200, 404, 500] # Not 403

user_response = user_client.get("/v1/entities/test_agent/info")
assert user_response.status_code == 403
error_data = user_response.json()
# FastAPI wraps HTTPException detail in 'detail' field
error = error_data.get("detail", {}).get("error") or error_data.get("error")
assert error is not None
assert "developer mode" in error["message"].lower()
assert error["code"] == "developer_mode_required"
# Should return 404 (entity doesn't exist) or 500 (other error), but NOT 403 (forbidden)
# User mode needs entity info to display workflows/agents in the UI
assert user_response.status_code in [200, 404, 500] # Not 403

# Test 5: Hot reload should be restricted in user mode
dev_response = dev_client.post("/v1/entities/test_agent/reload")
Expand Down Expand Up @@ -329,10 +325,11 @@ async def test_api_restrictions_in_user_mode():
# Test 8: Chat endpoint should work in both modes
chat_payload = {"model": "test_agent", "input": "Hello"}
dev_response = dev_client.post("/v1/responses", json=chat_payload)
assert dev_response.status_code in [200, 404] # 404 if agent doesn't exist
# 200=success, 400=missing entity_id in metadata, 404=entity not found
assert dev_response.status_code in [200, 400, 404]

user_response = user_client.post("/v1/responses", json=chat_payload)
assert user_response.status_code in [200, 404]
assert user_response.status_code in [200, 400, 404]


if __name__ == "__main__":
Expand Down