diff --git a/api/controllers/console/agent/roster.py b/api/controllers/console/agent/roster.py index 70aa7cc4c7..9d64b141d9 100644 --- a/api/controllers/console/agent/roster.py +++ b/api/controllers/console/agent/roster.py @@ -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) diff --git a/api/openapi/markdown/console-openapi.md b/api/openapi/markdown/console-openapi.md index 925d4bd84a..b413b1c017 100644 --- a/api/openapi/markdown/console-openapi.md +++ b/api/openapi/markdown/console-openapi.md @@ -311,7 +311,7 @@ Check if activation token is valid | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Agent app list | **application/json**: [AppPagination](#apppagination)
| +| 200 | Agent app list | **application/json**: [AgentAppPagination](#agentapppagination)
| ### [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 | diff --git a/api/services/agent/roster_service.py b/api/services/agent/roster_service.py index 69a2306cc8..85636a9609 100644 --- a/api/services/agent/roster_service.py +++ b/api/services/agent/roster_service.py @@ -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( diff --git a/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_workflow_run_cleanup_repository.py b/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_workflow_run_cleanup_repository.py index 1226885171..be659fac18 100644 --- a/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_workflow_run_cleanup_repository.py +++ b/api/tests/test_containers_integration_tests/repositories/test_sqlalchemy_workflow_run_cleanup_repository.py @@ -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] = [] diff --git a/api/tests/unit_tests/controllers/console/agent/test_agent_controllers.py b/api/tests/unit_tests/controllers/console/agent/test_agent_controllers.py index 429c3d0e2e..a6ae9e6933 100644 --- a/api/tests/unit_tests/controllers/console/agent/test_agent_controllers.py +++ b/api/tests/unit_tests/controllers/console/agent/test_agent_controllers.py @@ -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"]) diff --git a/packages/contracts/generated/api/console/agent/types.gen.ts b/packages/contracts/generated/api/console/agent/types.gen.ts index 5d123216d7..77b203fb95 100644 --- a/packages/contracts/generated/api/console/agent/types.gen.ts +++ b/packages/contracts/generated/api/console/agent/types.gen.ts @@ -4,8 +4,8 @@ export type ClientOptions = { baseUrl: `${string}://${string}/console/api` | (string & {}) } -export type AppPagination = { - data: Array +export type AgentAppPagination = { + data: Array has_more: boolean limit: number page: number @@ -272,7 +272,7 @@ export type AgentConfigSnapshotDetailResponse = { version_note?: string | null } -export type AppPartial = { +export type AgentAppPartial = { access_mode?: string | null active_config_is_published?: boolean app_id?: string | null @@ -293,6 +293,8 @@ export type AppPartial = { mode: string model_config?: ModelConfigPartial | null name: string + published_reference_count?: number + published_references?: Array role?: string | null tags?: Array updated_at?: number | null @@ -726,6 +728,14 @@ export type ModelConfigPartial = { updated_by?: string | null } +export type AgentAppPublishedReferenceResponse = { + app_icon?: string | null + app_icon_background?: string | null + app_icon_type?: string | null + app_id: string + app_name: string +} + export type LlmMode = 'chat' | 'completion' export type AgentKind = 'dify_agent' @@ -1361,8 +1371,8 @@ export type FileTransferMethod = 'datasource_file' | 'local_file' | 'remote_url' export type ValueSourceType = 'constant' | 'variable' -export type AppPaginationWritable = { - data: Array +export type AgentAppPaginationWritable = { + data: Array has_more: boolean limit: number page: number @@ -1399,7 +1409,7 @@ export type AppDetailWithSiteWritable = { workflow?: WorkflowPartial | null } -export type AppPartialWritable = { +export type AgentAppPartialWritable = { access_mode?: string | null active_config_is_published?: boolean app_id?: string | null @@ -1419,6 +1429,8 @@ export type AppPartialWritable = { mode: string model_config?: ModelConfigPartial | null name: string + published_reference_count?: number + published_references?: Array role?: string | null tags?: Array updated_at?: number | null @@ -1468,7 +1480,7 @@ export type GetAgentData = { } export type GetAgentResponses = { - 200: AppPagination + 200: AgentAppPagination } export type GetAgentResponse = GetAgentResponses[keyof GetAgentResponses] diff --git a/packages/contracts/generated/api/console/agent/zod.gen.ts b/packages/contracts/generated/api/console/agent/zod.gen.ts index 5232696ab5..7ea5583818 100644 --- a/packages/contracts/generated/api/console/agent/zod.gen.ts +++ b/packages/contracts/generated/api/console/agent/zod.gen.ts @@ -524,9 +524,20 @@ export const zModelConfigPartial = z.object({ }) /** - * AppPartial + * AgentAppPublishedReferenceResponse */ -export const zAppPartial = z.object({ +export const zAgentAppPublishedReferenceResponse = z.object({ + app_icon: z.string().nullish(), + app_icon_background: z.string().nullish(), + app_icon_type: z.string().nullish(), + app_id: z.string(), + app_name: z.string(), +}) + +/** + * AgentAppPartial + */ +export const zAgentAppPartial = z.object({ access_mode: z.string().nullish(), active_config_is_published: z.boolean().optional().default(false), app_id: z.string().nullish(), @@ -547,6 +558,8 @@ export const zAppPartial = z.object({ mode: z.string(), model_config: zModelConfigPartial.nullish(), name: z.string(), + published_reference_count: z.int().optional().default(0), + published_references: z.array(zAgentAppPublishedReferenceResponse).optional(), role: z.string().nullish(), tags: z.array(zTag).optional(), updated_at: z.int().nullish(), @@ -556,10 +569,10 @@ export const zAppPartial = z.object({ }) /** - * AppPagination + * AgentAppPagination */ -export const zAppPagination = z.object({ - data: z.array(zAppPartial), +export const zAgentAppPagination = z.object({ + data: z.array(zAgentAppPartial), has_more: z.boolean(), limit: z.int(), page: z.int(), @@ -1917,9 +1930,9 @@ export const zMessageInfiniteScrollPaginationResponse = z.object({ }) /** - * AppPartial + * AgentAppPartial */ -export const zAppPartialWritable = z.object({ +export const zAgentAppPartialWritable = z.object({ access_mode: z.string().nullish(), active_config_is_published: z.boolean().optional().default(false), app_id: z.string().nullish(), @@ -1939,6 +1952,8 @@ export const zAppPartialWritable = z.object({ mode: z.string(), model_config: zModelConfigPartial.nullish(), name: z.string(), + published_reference_count: z.int().optional().default(0), + published_references: z.array(zAgentAppPublishedReferenceResponse).optional(), role: z.string().nullish(), tags: z.array(zTag).optional(), updated_at: z.int().nullish(), @@ -1948,10 +1963,10 @@ export const zAppPartialWritable = z.object({ }) /** - * AppPagination + * AgentAppPagination */ -export const zAppPaginationWritable = z.object({ - data: z.array(zAppPartialWritable), +export const zAgentAppPaginationWritable = z.object({ + data: z.array(zAgentAppPartialWritable), has_more: z.boolean(), limit: z.int(), page: z.int(), @@ -2039,7 +2054,7 @@ export const zGetAgentQuery = z.object({ /** * Agent app list */ -export const zGetAgentResponse = zAppPagination +export const zGetAgentResponse = zAgentAppPagination export const zPostAgentBody = zAgentAppCreatePayload diff --git a/packages/contracts/generated/api/console/apps/types.gen.ts b/packages/contracts/generated/api/console/apps/types.gen.ts index 0c7633ba7e..8ccf899b45 100644 --- a/packages/contracts/generated/api/console/apps/types.gen.ts +++ b/packages/contracts/generated/api/console/apps/types.gen.ts @@ -5,10 +5,10 @@ export type ClientOptions = { } export type AppPagination = { - data: Array - has_more: boolean - limit: number + has_next: boolean + items: Array page: number + per_page: number total: number } @@ -1158,22 +1158,21 @@ export type AppPartial = { access_mode?: string | null active_config_is_published?: boolean app_id?: string | null + app_model_config?: ModelConfigPartial | null author_name?: string | null bound_agent_id?: string | null create_user_name?: string | null created_at?: number | null created_by?: string | null - description?: string | null + desc_or_prompt?: string | null has_draft_trigger?: boolean | null icon?: string | null icon_background?: string | null icon_type?: string | null - readonly icon_url: string | null id: string is_starred?: boolean max_active_requests?: number | null - mode: string - model_config?: ModelConfigPartial | null + mode_compatible_with_agent: string name: string role?: string | null tags?: Array @@ -2576,14 +2575,6 @@ export type AgentModerationIoConfig = { export type ValueSourceType = 'constant' | 'variable' -export type AppPaginationWritable = { - data: Array - has_more: boolean - limit: number - page: number - total: number -} - export type AppDetailWithSiteWritable = { access_mode?: string | null active_config_is_published?: boolean @@ -2637,34 +2628,6 @@ export type WorkflowCommentDetailWritable = { updated_at?: number | null } -export type AppPartialWritable = { - access_mode?: string | null - active_config_is_published?: boolean - app_id?: string | null - author_name?: string | null - bound_agent_id?: string | null - create_user_name?: string | null - created_at?: number | null - created_by?: string | null - description?: string | null - has_draft_trigger?: boolean | null - icon?: string | null - icon_background?: string | null - icon_type?: string | null - id: string - is_starred?: boolean - max_active_requests?: number | null - mode: string - model_config?: ModelConfigPartial | null - name: string - role?: string | null - tags?: Array - updated_at?: number | null - updated_by?: string | null - use_icon_as_answer_icon?: boolean | null - workflow?: WorkflowPartial | null -} - export type SiteWritable = { chat_color_theme?: string | null chat_color_theme_inverted: boolean diff --git a/packages/contracts/generated/api/console/apps/zod.gen.ts b/packages/contracts/generated/api/console/apps/zod.gen.ts index 475823246b..556f11f552 100644 --- a/packages/contracts/generated/api/console/apps/zod.gen.ts +++ b/packages/contracts/generated/api/console/apps/zod.gen.ts @@ -1948,22 +1948,21 @@ export const zAppPartial = z.object({ access_mode: z.string().nullish(), active_config_is_published: z.boolean().optional().default(false), app_id: z.string().nullish(), + app_model_config: zModelConfigPartial.nullish(), author_name: z.string().nullish(), bound_agent_id: z.string().nullish(), create_user_name: z.string().nullish(), created_at: z.int().nullish(), created_by: z.string().nullish(), - description: z.string().nullish(), + desc_or_prompt: z.string().nullish(), has_draft_trigger: z.boolean().nullish(), icon: z.string().nullish(), icon_background: z.string().nullish(), icon_type: z.string().nullish(), - icon_url: z.string().nullable(), id: z.string(), is_starred: z.boolean().optional().default(false), max_active_requests: z.int().nullish(), - mode: z.string(), - model_config: zModelConfigPartial.nullish(), + mode_compatible_with_agent: z.string(), name: z.string(), role: z.string().nullish(), tags: z.array(zTag).optional(), @@ -1977,10 +1976,10 @@ export const zAppPartial = z.object({ * AppPagination */ export const zAppPagination = z.object({ - data: z.array(zAppPartial), - has_more: z.boolean(), - limit: z.int(), + has_next: z.boolean(), + items: z.array(zAppPartial), page: z.int(), + per_page: z.int(), total: z.int(), }) @@ -3473,48 +3472,6 @@ export const zMessageInfiniteScrollPaginationResponse = z.object({ */ export const zGeneratedAppResponseWritable = zJsonValue -/** - * AppPartial - */ -export const zAppPartialWritable = z.object({ - access_mode: z.string().nullish(), - active_config_is_published: z.boolean().optional().default(false), - app_id: z.string().nullish(), - author_name: z.string().nullish(), - bound_agent_id: z.string().nullish(), - create_user_name: z.string().nullish(), - created_at: z.int().nullish(), - created_by: z.string().nullish(), - description: z.string().nullish(), - has_draft_trigger: z.boolean().nullish(), - icon: z.string().nullish(), - icon_background: z.string().nullish(), - icon_type: z.string().nullish(), - id: z.string(), - is_starred: z.boolean().optional().default(false), - max_active_requests: z.int().nullish(), - mode: z.string(), - model_config: zModelConfigPartial.nullish(), - name: z.string(), - role: z.string().nullish(), - tags: z.array(zTag).optional(), - updated_at: z.int().nullish(), - updated_by: z.string().nullish(), - use_icon_as_answer_icon: z.boolean().nullish(), - workflow: zWorkflowPartial.nullish(), -}) - -/** - * AppPagination - */ -export const zAppPaginationWritable = z.object({ - data: z.array(zAppPartialWritable), - has_more: z.boolean(), - limit: z.int(), - page: z.int(), - total: z.int(), -}) - /** * Site */