fix(agent-v2): include workflow references in agent list (#37567)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
yyh
2026-06-17 05:56:53 +00:00
committed by GitHub
co-authored by autofix-ci[bot]
parent e970cbde0f
commit f203ab7f1d
9 changed files with 202 additions and 125 deletions
+46 -4
View File
@@ -11,6 +11,7 @@ from controllers.console.app.app import (
AppDetailWithSite,
AppListQuery,
AppPagination,
AppPartial,
UpdateAppPayload,
_normalize_app_list_query_args,
)
@@ -72,6 +73,27 @@ class AgentAppUpdatePayload(UpdateAppPayload):
role: str | None = Field(default=None, description="Agent role", max_length=255)
class AgentAppPublishedReferenceResponse(BaseModel):
app_id: str
app_name: str
app_icon_type: str | None = None
app_icon: str | None = None
app_icon_background: str | None = None
class AgentAppPartial(AppPartial):
published_reference_count: int = 0
published_references: list[AgentAppPublishedReferenceResponse] = Field(default_factory=list)
class AgentAppPagination(BaseModel):
page: int
limit: int
total: int
has_more: bool
data: list[AgentAppPartial]
class AgentLogsQuery(BaseModel):
page: int = Field(default=1, ge=1, description="Page number")
limit: int = Field(default=20, ge=1, le=100, description="Page size")
@@ -123,7 +145,8 @@ register_schema_models(
register_response_schema_models(
console_ns,
AppDetailWithSite,
AppPagination,
AgentAppPagination,
AgentAppPublishedReferenceResponse,
AgentConfigSnapshotDetailResponse,
AgentConfigSnapshotListResponse,
AgentInviteOptionsResponse,
@@ -171,6 +194,10 @@ def _serialize_agent_app_pagination(app_pagination, *, tenant_id: str) -> dict:
tenant_id=tenant_id,
agents=list(agents_by_app_id.values()),
)
published_references_by_agent_id = roster_service.load_published_references_by_agent_id(
tenant_id=tenant_id,
agent_ids=[agent.id for agent in agents_by_app_id.values()],
)
payload = AppPagination.model_validate(app_pagination, from_attributes=True).model_dump(mode="json")
for item in payload["data"]:
app_id = item["id"]
@@ -181,7 +208,22 @@ def _serialize_agent_app_pagination(app_pagination, *, tenant_id: str) -> dict:
item["id"] = agent.id
item["role"] = agent.role or ""
item["active_config_is_published"] = active_config_is_published_by_agent_id.get(agent.id, False)
return payload
published_references = published_references_by_agent_id.get(agent.id, [])
item["published_reference_count"] = len(published_references)
item["published_references"] = [
{
"app_id": reference["app_id"],
"app_name": reference["app_name"],
"app_icon_type": reference["app_icon_type"],
"app_icon": reference["app_icon"],
"app_icon_background": reference["app_icon_background"],
}
for reference in published_references
]
return AgentAppPagination.model_validate(payload).model_dump(
mode="json",
exclude={"data": {"__all__": {"bound_agent_id"}}},
)
def _resolve_agent_app_model(*, tenant_id: str, agent_id: UUID):
@@ -203,7 +245,7 @@ def _parse_observability_time_range(start: str | None, end: str | None, account:
@console_ns.route("/agent")
class AgentAppListApi(Resource):
@console_ns.doc(params=query_params_from_model(AppListQuery))
@console_ns.response(200, "Agent app list", console_ns.models[AppPagination.__name__])
@console_ns.response(200, "Agent app list", console_ns.models[AgentAppPagination.__name__])
@setup_required
@login_required
@account_initialization_required
@@ -224,7 +266,7 @@ class AgentAppListApi(Resource):
app_pagination = AppService().get_paginate_apps(current_user.id, current_tenant_id, params)
if app_pagination is None:
empty = AppPagination(page=args.page, limit=args.limit, total=0, has_more=False, data=[])
empty = AgentAppPagination(page=args.page, limit=args.limit, total=0, has_more=False, data=[])
return empty.model_dump(mode="json")
return _serialize_agent_app_pagination(app_pagination, tenant_id=current_tenant_id)
+60 -8
View File
@@ -311,7 +311,7 @@ Check if activation token is valid
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Agent app list | **application/json**: [AppPagination](#apppagination)<br> |
| 200 | Agent app list | **application/json**: [AgentAppPagination](#agentapppagination)<br> |
### [POST] /agent
#### Request Body
@@ -11341,6 +11341,59 @@ default (the config form sends the full desired feature state on save).
| suggested_questions_after_answer | [AgentSuggestedQuestionsAfterAnswerFeatureConfig](#agentsuggestedquestionsafteranswerfeatureconfig) | Follow-up suggestions config, e.g. {'enabled': true} | No |
| text_to_speech | [AgentTextToSpeechFeatureConfig](#agenttexttospeechfeatureconfig) | Text-to-speech config | No |
#### AgentAppPagination
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| data | [ [AgentAppPartial](#agentapppartial) ] | | Yes |
| has_more | boolean | | Yes |
| limit | integer | | Yes |
| page | integer | | Yes |
| total | integer | | Yes |
#### AgentAppPartial
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| access_mode | string | | No |
| active_config_is_published | boolean | | No |
| app_id | string | | No |
| author_name | string | | No |
| bound_agent_id | string | | No |
| create_user_name | string | | No |
| created_at | integer | | No |
| created_by | string | | No |
| description | string | | No |
| has_draft_trigger | boolean | | No |
| icon | string | | No |
| icon_background | string | | No |
| icon_type | string | | No |
| icon_url | string | | Yes |
| id | string | | Yes |
| is_starred | boolean | | No |
| max_active_requests | integer | | No |
| mode | string | | Yes |
| model_config | [ModelConfigPartial](#modelconfigpartial) | | No |
| name | string | | Yes |
| published_reference_count | integer | | No |
| published_references | [ [AgentAppPublishedReferenceResponse](#agentapppublishedreferenceresponse) ] | | No |
| role | string | | No |
| tags | [ [Tag](#tag) ] | | No |
| updated_at | integer | | No |
| updated_by | string | | No |
| use_icon_as_answer_icon | boolean | | No |
| workflow | [WorkflowPartial](#workflowpartial) | | No |
#### AgentAppPublishedReferenceResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| app_icon | string | | No |
| app_icon_background | string | | No |
| app_icon_type | string | | No |
| app_id | string | | Yes |
| app_name | string | | Yes |
#### AgentAppUpdatePayload
| Name | Type | Description | Required |
@@ -12816,10 +12869,10 @@ AppMCPServer Status Enum
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| data | [ [AppPartial](#apppartial) ] | | Yes |
| has_more | boolean | | Yes |
| limit | integer | | Yes |
| has_next | boolean | | Yes |
| items | [ [AppPartial](#apppartial) ] | | Yes |
| page | integer | | Yes |
| per_page | integer | | Yes |
| total | integer | | Yes |
#### AppPartial
@@ -12829,22 +12882,21 @@ AppMCPServer Status Enum
| access_mode | string | | No |
| active_config_is_published | boolean | | No |
| app_id | string | | No |
| app_model_config | [ModelConfigPartial](#modelconfigpartial) | | No |
| author_name | string | | No |
| bound_agent_id | string | | No |
| create_user_name | string | | No |
| created_at | integer | | No |
| created_by | string | | No |
| description | string | | No |
| desc_or_prompt | string | | No |
| has_draft_trigger | boolean | | No |
| icon | string | | No |
| icon_background | string | | No |
| icon_type | string | | No |
| icon_url | string | | Yes |
| id | string | | Yes |
| is_starred | boolean | | No |
| max_active_requests | integer | | No |
| mode | string | | Yes |
| model_config | [ModelConfigPartial](#modelconfigpartial) | | No |
| mode_compatible_with_agent | string | | Yes |
| name | string | | Yes |
| role | string | | No |
| tags | [ [Tag](#tag) ] | | No |
+6
View File
@@ -432,6 +432,12 @@ class AgentRosterService:
return self._load_published_references_by_agent_id(tenant_id=tenant_id, agent_ids=[agent.id]).get(agent.id, [])
def load_published_references_by_agent_id(
self, *, tenant_id: str, agent_ids: list[str]
) -> dict[str, list[AgentReferencingWorkflow]]:
"""Return published workflow references grouped by roster Agent id."""
return self._load_published_references_by_agent_id(tenant_id=tenant_id, agent_ids=agent_ids)
def get_roster_agent_detail(self, *, tenant_id: str, agent_id: str) -> dict[str, Any]:
agent = self._get_agent(tenant_id=tenant_id, agent_id=agent_id, roster_only=True)
active_version = self._get_version(
@@ -87,7 +87,7 @@ def _add_app_log(session: Session, scope: _TestScope, workflow_run: WorkflowRun)
session.commit()
def _add_pause_with_reason(session: Session, _scope: _TestScope, workflow_run: WorkflowRun) -> WorkflowPause:
def _add_pause_with_reason(session: Session, workflow_run: WorkflowRun) -> WorkflowPause:
pause = WorkflowPause(
workflow_id=workflow_run.workflow_id,
workflow_run_id=workflow_run.id,
@@ -185,7 +185,7 @@ class TestCountRunsWithRelatedByIds:
)
missing_run_id = str(uuid4())
_add_app_log(db_session_with_containers, scope, workflow_run)
_add_pause_with_reason(db_session_with_containers, scope, workflow_run)
_add_pause_with_reason(db_session_with_containers, workflow_run)
counted_node_run_ids: list[str] = []
counted_trigger_run_ids: list[str] = []
@@ -239,7 +239,7 @@ class TestDeleteRunsWithRelatedByIds:
created_at=datetime(2024, 1, 1, 12, 0, 0),
)
_add_app_log(db_session_with_containers, scope, workflow_run)
pause = _add_pause_with_reason(db_session_with_containers, scope, workflow_run)
pause = _add_pause_with_reason(db_session_with_containers, workflow_run)
pause_id = pause.id
deleted_node_run_ids: list[str] = []
deleted_trigger_run_ids: list[str] = []
@@ -214,6 +214,26 @@ def test_agent_app_list_and_create_use_agent_route(
id="agent-created", role="Created role", active_config_snapshot_id=None
),
)
monkeypatch.setattr(
roster_controller.AgentRosterService,
"load_published_references_by_agent_id",
lambda _self, **kwargs: {
"agent-list": [
{
"app_id": "workflow-app-id",
"app_name": "RFP Review Flow",
"app_icon_type": "emoji",
"app_icon": "A",
"app_icon_background": "#fff",
"app_mode": "workflow",
"app_updated_at": 1781660000,
"workflow_id": "workflow-1",
"workflow_version": "v1",
"node_ids": ["node-1", "node-2"],
}
]
},
)
monkeypatch.setattr(
roster_controller.FeatureService,
"get_system_features",
@@ -230,6 +250,16 @@ def test_agent_app_list_and_create_use_agent_route(
assert listed["data"][0]["app_id"] == "app-list"
assert listed["data"][0]["role"] == "List role"
assert listed["data"][0]["active_config_is_published"] is False
assert listed["data"][0]["published_reference_count"] == 1
assert listed["data"][0]["published_references"] == [
{
"app_id": "workflow-app-id",
"app_name": "RFP Review Flow",
"app_icon_type": "emoji",
"app_icon": "A",
"app_icon_background": "#fff",
}
]
assert "bound_agent_id" not in listed["data"][0]
list_call = cast(dict[str, object], captured["list"])
list_params = cast(Any, list_call["params"])