From 2cc567c6a30a1f8f5f09de4ed4063e9763a97b7c Mon Sep 17 00:00:00 2001 From: zyssyz123 <916125788@qq.com> Date: Fri, 29 May 2026 11:36:41 +0800 Subject: [PATCH] feat: add DTO for agent api (#36797) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/controllers/console/agent/composer.py | 125 +++-- api/controllers/console/agent/roster.py | 83 +++- api/fields/agent_fields.py | 192 ++++++++ api/openapi/markdown/console-swagger.md | 408 +++++++++++++-- .../console/agent/test_agent_controllers.py | 185 +++++-- cli/src/store/store.ts | 2 +- .../generated/api/console/agents/orpc.gen.ts | 54 +- .../generated/api/console/agents/types.gen.ts | 165 ++++++- .../generated/api/console/agents/zod.gen.ts | 218 ++++++++- .../generated/api/console/apps/orpc.gen.ts | 8 +- .../generated/api/console/apps/types.gen.ts | 260 +++++++--- .../generated/api/console/apps/zod.gen.ts | 463 ++++++++++++------ 12 files changed, 1696 insertions(+), 467 deletions(-) create mode 100644 api/fields/agent_fields.py diff --git a/api/controllers/console/agent/composer.py b/api/controllers/console/agent/composer.py index 85b54ce7cc..db8d7fec60 100644 --- a/api/controllers/console/agent/composer.py +++ b/api/controllers/console/agent/composer.py @@ -1,9 +1,17 @@ from flask_restx import Resource -from controllers.common.schema import register_schema_models +from controllers.common.schema import register_response_schema_models, register_schema_models from controllers.console import console_ns from controllers.console.app.wraps import get_app_model from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required +from fields.agent_fields import ( + AgentAppComposerResponse, + AgentComposerCandidatesResponse, + AgentComposerImpactResponse, + AgentComposerValidateResponse, + WorkflowAgentComposerResponse, +) +from libs.helper import dump_response from libs.login import current_account_with_tenant, login_required from models.model import App, AppMode from services.agent.composer_service import AgentComposerService @@ -11,23 +19,40 @@ from services.agent.composer_validator import ComposerConfigValidator from services.entities.agent_entities import ComposerSavePayload register_schema_models(console_ns, ComposerSavePayload) +register_response_schema_models( + console_ns, + AgentAppComposerResponse, + AgentComposerCandidatesResponse, + AgentComposerImpactResponse, + AgentComposerValidateResponse, + WorkflowAgentComposerResponse, +) @console_ns.route("/apps//workflows/draft/nodes//agent-composer") class WorkflowAgentComposerApi(Resource): + @console_ns.response( + 200, "Workflow agent composer state", console_ns.models[WorkflowAgentComposerResponse.__name__] + ) @setup_required @login_required @account_initialization_required @get_app_model(mode=[AppMode.WORKFLOW, AppMode.ADVANCED_CHAT]) def get(self, app_model: App, node_id: str): _, tenant_id = current_account_with_tenant() - return AgentComposerService.load_workflow_composer( - tenant_id=tenant_id, - app_id=app_model.id, - node_id=node_id, + return dump_response( + WorkflowAgentComposerResponse, + AgentComposerService.load_workflow_composer( + tenant_id=tenant_id, + app_id=app_model.id, + node_id=node_id, + ), ) @console_ns.expect(console_ns.models[ComposerSavePayload.__name__]) + @console_ns.response( + 200, "Workflow agent composer saved", console_ns.models[WorkflowAgentComposerResponse.__name__] + ) @setup_required @login_required @account_initialization_required @@ -36,18 +61,24 @@ class WorkflowAgentComposerApi(Resource): def put(self, app_model: App, node_id: str): account, tenant_id = current_account_with_tenant() payload = ComposerSavePayload.model_validate(console_ns.payload or {}) - return AgentComposerService.save_workflow_composer( - tenant_id=tenant_id, - app_id=app_model.id, - node_id=node_id, - account_id=account.id, - payload=payload, + return dump_response( + WorkflowAgentComposerResponse, + AgentComposerService.save_workflow_composer( + tenant_id=tenant_id, + app_id=app_model.id, + node_id=node_id, + account_id=account.id, + payload=payload, + ), ) @console_ns.route("/apps//workflows/draft/nodes//agent-composer/validate") class WorkflowAgentComposerValidateApi(Resource): @console_ns.expect(console_ns.models[ComposerSavePayload.__name__]) + @console_ns.response( + 200, "Workflow agent composer validation result", console_ns.models[AgentComposerValidateResponse.__name__] + ) @setup_required @login_required @account_initialization_required @@ -55,21 +86,29 @@ class WorkflowAgentComposerValidateApi(Resource): def post(self, app_model: App, node_id: str): payload = ComposerSavePayload.model_validate(console_ns.payload or {}) ComposerConfigValidator.validate_save_payload(payload) - return {"result": "success", "errors": []} + return dump_response(AgentComposerValidateResponse, {"result": "success", "errors": []}) @console_ns.route("/apps//workflows/draft/nodes//agent-composer/candidates") class WorkflowAgentComposerCandidatesApi(Resource): + @console_ns.response( + 200, "Workflow agent composer candidates", console_ns.models[AgentComposerCandidatesResponse.__name__] + ) @setup_required @login_required @account_initialization_required @get_app_model(mode=[AppMode.WORKFLOW, AppMode.ADVANCED_CHAT]) def get(self, app_model: App, node_id: str): - return AgentComposerService.get_workflow_candidates(app_id=app_model.id) + return dump_response( + AgentComposerCandidatesResponse, + AgentComposerService.get_workflow_candidates(app_id=app_model.id), + ) @console_ns.route("/apps//workflows/draft/nodes//agent-composer/impact") class WorkflowAgentComposerImpactApi(Resource): + @console_ns.expect(console_ns.models[ComposerSavePayload.__name__]) + @console_ns.response(200, "Workflow agent composer impact", console_ns.models[AgentComposerImpactResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -79,13 +118,21 @@ class WorkflowAgentComposerImpactApi(Resource): payload = ComposerSavePayload.model_validate(console_ns.payload or {}) current_snapshot_id = payload.binding.current_snapshot_id if payload.binding else None if not current_snapshot_id: - return {"current_snapshot_id": None, "workflow_node_count": 0, "bindings": []} - return AgentComposerService.calculate_impact(tenant_id=tenant_id, current_snapshot_id=current_snapshot_id) + return dump_response( + AgentComposerImpactResponse, {"current_snapshot_id": None, "workflow_node_count": 0, "bindings": []} + ) + return dump_response( + AgentComposerImpactResponse, + AgentComposerService.calculate_impact(tenant_id=tenant_id, current_snapshot_id=current_snapshot_id), + ) @console_ns.route("/apps//workflows/draft/nodes//agent-composer/save-to-roster") class WorkflowAgentComposerSaveToRosterApi(Resource): @console_ns.expect(console_ns.models[ComposerSavePayload.__name__]) + @console_ns.response( + 200, "Workflow agent composer saved to roster", console_ns.models[WorkflowAgentComposerResponse.__name__] + ) @setup_required @login_required @account_initialization_required @@ -94,26 +141,34 @@ class WorkflowAgentComposerSaveToRosterApi(Resource): def post(self, app_model: App, node_id: str): account, tenant_id = current_account_with_tenant() payload = ComposerSavePayload.model_validate(console_ns.payload or {}) - return AgentComposerService.save_workflow_composer( - tenant_id=tenant_id, - app_id=app_model.id, - node_id=node_id, - account_id=account.id, - payload=payload, + return dump_response( + WorkflowAgentComposerResponse, + AgentComposerService.save_workflow_composer( + tenant_id=tenant_id, + app_id=app_model.id, + node_id=node_id, + account_id=account.id, + payload=payload, + ), ) @console_ns.route("/apps//agent-composer") class AgentAppComposerApi(Resource): + @console_ns.response(200, "Agent app composer state", console_ns.models[AgentAppComposerResponse.__name__]) @setup_required @login_required @account_initialization_required @get_app_model() def get(self, app_model: App): _, tenant_id = current_account_with_tenant() - return AgentComposerService.load_agent_app_composer(tenant_id=tenant_id, app_id=app_model.id) + return dump_response( + AgentAppComposerResponse, + AgentComposerService.load_agent_app_composer(tenant_id=tenant_id, app_id=app_model.id), + ) @console_ns.expect(console_ns.models[ComposerSavePayload.__name__]) + @console_ns.response(200, "Agent app composer saved", console_ns.models[AgentAppComposerResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -122,17 +177,23 @@ class AgentAppComposerApi(Resource): def put(self, app_model: App): account, tenant_id = current_account_with_tenant() payload = ComposerSavePayload.model_validate(console_ns.payload or {}) - return AgentComposerService.save_agent_app_composer( - tenant_id=tenant_id, - app_id=app_model.id, - account_id=account.id, - payload=payload, + return dump_response( + AgentAppComposerResponse, + AgentComposerService.save_agent_app_composer( + tenant_id=tenant_id, + app_id=app_model.id, + account_id=account.id, + payload=payload, + ), ) @console_ns.route("/apps//agent-composer/validate") class AgentAppComposerValidateApi(Resource): @console_ns.expect(console_ns.models[ComposerSavePayload.__name__]) + @console_ns.response( + 200, "Agent app composer validation result", console_ns.models[AgentComposerValidateResponse.__name__] + ) @setup_required @login_required @account_initialization_required @@ -140,14 +201,20 @@ class AgentAppComposerValidateApi(Resource): def post(self, app_model: App): payload = ComposerSavePayload.model_validate(console_ns.payload or {}) ComposerConfigValidator.validate_save_payload(payload) - return {"result": "success", "errors": []} + return dump_response(AgentComposerValidateResponse, {"result": "success", "errors": []}) @console_ns.route("/apps//agent-composer/candidates") class AgentAppComposerCandidatesApi(Resource): + @console_ns.response( + 200, "Agent app composer candidates", console_ns.models[AgentComposerCandidatesResponse.__name__] + ) @setup_required @login_required @account_initialization_required @get_app_model() def get(self, app_model: App): - return AgentComposerService.get_agent_app_candidates(app_id=app_model.id) + return dump_response( + AgentComposerCandidatesResponse, + AgentComposerService.get_agent_app_candidates(app_id=app_model.id), + ) diff --git a/api/controllers/console/agent/roster.py b/api/controllers/console/agent/roster.py index daa0d97496..be41b4e3b3 100644 --- a/api/controllers/console/agent/roster.py +++ b/api/controllers/console/agent/roster.py @@ -4,10 +4,18 @@ from flask import request from flask_restx import Resource from pydantic import BaseModel, Field -from controllers.common.schema import register_schema_models +from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models from controllers.console import console_ns from controllers.console.wraps import account_initialization_required, edit_permission_required, setup_required from extensions.ext_database import db +from fields.agent_fields import ( + AgentConfigSnapshotDetailResponse, + AgentConfigSnapshotListResponse, + AgentInviteOptionsResponse, + AgentRosterListResponse, + AgentRosterResponse, +) +from libs.helper import dump_response from libs.login import current_account_with_tenant, login_required from services.agent.roster_service import AgentRosterService from services.entities.agent_entities import RosterAgentCreatePayload, RosterAgentUpdatePayload, RosterListQuery @@ -29,6 +37,14 @@ register_schema_models( RosterAgentUpdatePayload, RosterListQuery, ) +register_response_schema_models( + console_ns, + AgentConfigSnapshotDetailResponse, + AgentConfigSnapshotListResponse, + AgentInviteOptionsResponse, + AgentRosterListResponse, + AgentRosterResponse, +) def _agent_roster_service() -> AgentRosterService: @@ -37,17 +53,23 @@ def _agent_roster_service() -> AgentRosterService: @console_ns.route("/agents") class AgentRosterListApi(Resource): + @console_ns.doc(params=query_params_from_model(RosterListQuery)) + @console_ns.response(200, "Agent roster list", console_ns.models[AgentRosterListResponse.__name__]) @setup_required @login_required @account_initialization_required def get(self): _, tenant_id = current_account_with_tenant() query = RosterListQuery.model_validate(request.args.to_dict(flat=True)) - return _agent_roster_service().list_roster_agents( - tenant_id=tenant_id, page=query.page, limit=query.limit, keyword=query.keyword + return dump_response( + AgentRosterListResponse, + _agent_roster_service().list_roster_agents( + tenant_id=tenant_id, page=query.page, limit=query.limit, keyword=query.keyword + ), ) @console_ns.expect(console_ns.models[RosterAgentCreatePayload.__name__]) + @console_ns.response(201, "Agent created", console_ns.models[AgentRosterResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -57,36 +79,49 @@ class AgentRosterListApi(Resource): payload = RosterAgentCreatePayload.model_validate(console_ns.payload or {}) service = _agent_roster_service() agent = service.create_roster_agent(tenant_id=tenant_id, account_id=account.id, payload=payload) - return service.get_roster_agent_detail(tenant_id=tenant_id, agent_id=agent.id), 201 + return dump_response( + AgentRosterResponse, + service.get_roster_agent_detail(tenant_id=tenant_id, agent_id=agent.id), + ), 201 @console_ns.route("/agents/invite-options") class AgentInviteOptionsApi(Resource): + @console_ns.doc(params=query_params_from_model(AgentInviteOptionsQuery)) + @console_ns.response(200, "Agent invite options", console_ns.models[AgentInviteOptionsResponse.__name__]) @setup_required @login_required @account_initialization_required def get(self): _, tenant_id = current_account_with_tenant() query = AgentInviteOptionsQuery.model_validate(request.args.to_dict(flat=True)) - return _agent_roster_service().list_invite_options( - tenant_id=tenant_id, - page=query.page, - limit=query.limit, - keyword=query.keyword, - app_id=query.app_id, + return dump_response( + AgentInviteOptionsResponse, + _agent_roster_service().list_invite_options( + tenant_id=tenant_id, + page=query.page, + limit=query.limit, + keyword=query.keyword, + app_id=query.app_id, + ), ) @console_ns.route("/agents/") class AgentRosterDetailApi(Resource): + @console_ns.response(200, "Agent detail", console_ns.models[AgentRosterResponse.__name__]) @setup_required @login_required @account_initialization_required def get(self, agent_id: UUID): _, tenant_id = current_account_with_tenant() - return _agent_roster_service().get_roster_agent_detail(tenant_id=tenant_id, agent_id=str(agent_id)) + return dump_response( + AgentRosterResponse, + _agent_roster_service().get_roster_agent_detail(tenant_id=tenant_id, agent_id=str(agent_id)), + ) @console_ns.expect(console_ns.models[RosterAgentUpdatePayload.__name__]) + @console_ns.response(200, "Agent updated", console_ns.models[AgentRosterResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -94,10 +129,14 @@ class AgentRosterDetailApi(Resource): def patch(self, agent_id: UUID): account, tenant_id = current_account_with_tenant() payload = RosterAgentUpdatePayload.model_validate(console_ns.payload or {}) - return _agent_roster_service().update_roster_agent( - tenant_id=tenant_id, agent_id=str(agent_id), account_id=account.id, payload=payload + return dump_response( + AgentRosterResponse, + _agent_roster_service().update_roster_agent( + tenant_id=tenant_id, agent_id=str(agent_id), account_id=account.id, payload=payload + ), ) + @console_ns.response(204, "Agent archived") @setup_required @login_required @account_initialization_required @@ -110,23 +149,31 @@ class AgentRosterDetailApi(Resource): @console_ns.route("/agents//versions") class AgentRosterVersionsApi(Resource): + @console_ns.response(200, "Agent versions", console_ns.models[AgentConfigSnapshotListResponse.__name__]) @setup_required @login_required @account_initialization_required def get(self, agent_id: UUID): _, tenant_id = current_account_with_tenant() - return {"data": _agent_roster_service().list_agent_versions(tenant_id=tenant_id, agent_id=str(agent_id))} + return dump_response( + AgentConfigSnapshotListResponse, + {"data": _agent_roster_service().list_agent_versions(tenant_id=tenant_id, agent_id=str(agent_id))}, + ) @console_ns.route("/agents//versions/") class AgentRosterVersionDetailApi(Resource): + @console_ns.response(200, "Agent version detail", console_ns.models[AgentConfigSnapshotDetailResponse.__name__]) @setup_required @login_required @account_initialization_required def get(self, agent_id: UUID, version_id: UUID): _, tenant_id = current_account_with_tenant() - return _agent_roster_service().get_agent_version_detail( - tenant_id=tenant_id, - agent_id=str(agent_id), - version_id=str(version_id), + return dump_response( + AgentConfigSnapshotDetailResponse, + _agent_roster_service().get_agent_version_detail( + tenant_id=tenant_id, + agent_id=str(agent_id), + version_id=str(version_id), + ), ) diff --git a/api/fields/agent_fields.py b/api/fields/agent_fields.py new file mode 100644 index 0000000000..9590032383 --- /dev/null +++ b/api/fields/agent_fields.py @@ -0,0 +1,192 @@ +from typing import Any, Literal + +from pydantic import Field + +from fields.base import ResponseModel +from models.agent import ( + AgentConfigRevisionOperation, + AgentIconType, + AgentKind, + AgentScope, + AgentSource, + AgentStatus, + WorkflowAgentBindingType, +) +from models.agent_config_entities import ( + AgentSoulConfig, + DeclaredOutputConfig, + DeclaredOutputType, + WorkflowNodeJobConfig, +) +from services.entities.agent_entities import ( + ComposerCandidateCapabilities, + ComposerSaveStrategy, + ComposerVariant, +) + + +class AgentConfigSnapshotSummaryResponse(ResponseModel): + id: str + agent_id: str | None = None + version: int + summary: str | None = None + version_note: str | None = None + created_by: str | None = None + created_at: str | None = None + + +class AgentRosterResponse(ResponseModel): + id: str + name: str + description: str + icon_type: AgentIconType | None = None + icon: str | None = None + icon_background: str | None = None + agent_kind: AgentKind + scope: AgentScope + source: AgentSource + app_id: str | None = None + workflow_id: str | None = None + workflow_node_id: str | None = None + active_config_snapshot_id: str | None = None + active_config_snapshot: AgentConfigSnapshotSummaryResponse | None = None + status: AgentStatus + created_by: str | None = None + updated_by: str | None = None + archived_by: str | None = None + archived_at: str | None = None + created_at: str | None = None + updated_at: str | None = None + + +class AgentInviteOptionResponse(AgentRosterResponse): + is_in_current_workflow: bool = False + in_current_workflow_count: int = 0 + existing_node_ids: list[str] = Field(default_factory=list) + + +class AgentRosterListResponse(ResponseModel): + data: list[AgentRosterResponse] + page: int + limit: int + total: int + has_more: bool + + +class AgentInviteOptionsResponse(ResponseModel): + data: list[AgentInviteOptionResponse] + page: int + limit: int + total: int + has_more: bool + + +class AgentConfigRevisionResponse(ResponseModel): + id: str + previous_snapshot_id: str | None = None + current_snapshot_id: str + revision: int + operation: AgentConfigRevisionOperation + summary: str | None = None + version_note: str | None = None + created_by: str | None = None + created_at: str | None = None + + +class AgentConfigSnapshotDetailResponse(AgentConfigSnapshotSummaryResponse): + config_snapshot: AgentSoulConfig + revisions: list[AgentConfigRevisionResponse] = Field(default_factory=list) + + +class AgentConfigSnapshotListResponse(ResponseModel): + data: list[AgentConfigSnapshotSummaryResponse] + + +class AgentComposerAgentResponse(ResponseModel): + id: str + name: str + description: str + scope: AgentScope + status: AgentStatus + active_config_snapshot_id: str | None = None + + +class AgentComposerBindingResponse(ResponseModel): + id: str + binding_type: WorkflowAgentBindingType + agent_id: str | None = None + current_snapshot_id: str | None = None + workflow_id: str + node_id: str + + +class AgentComposerSoulLockResponse(ResponseModel): + locked: bool + can_unlock: bool = False + reason: str | None = None + + +class AgentComposerImpactBindingResponse(ResponseModel): + app_id: str + workflow_id: str + node_id: str + + +class AgentComposerImpactResponse(ResponseModel): + current_snapshot_id: str | None = None + workflow_node_count: int + bindings: list[AgentComposerImpactBindingResponse] = Field(default_factory=list) + + +class WorkflowAgentComposerResponse(ResponseModel): + variant: Literal[ComposerVariant.WORKFLOW] + agent: AgentComposerAgentResponse | None = None + active_config_snapshot: AgentConfigSnapshotSummaryResponse | None = None + binding: AgentComposerBindingResponse | None = None + soul_lock: AgentComposerSoulLockResponse + agent_soul: AgentSoulConfig + node_job: WorkflowNodeJobConfig + effective_declared_outputs: list[DeclaredOutputConfig] = Field(default_factory=list) + save_options: list[ComposerSaveStrategy] + impact_summary: AgentComposerImpactResponse | None = None + app_id: str | None = None + workflow_id: str | None = None + node_id: str | None = None + + +class AgentAppComposerResponse(ResponseModel): + variant: Literal[ComposerVariant.AGENT_APP] + agent: AgentComposerAgentResponse + active_config_snapshot: AgentConfigSnapshotSummaryResponse + agent_soul: AgentSoulConfig + save_options: list[ComposerSaveStrategy] + + +class AgentComposerValidateResponse(ResponseModel): + result: Literal["success"] + errors: list[str] = Field(default_factory=list) + + +class AgentComposerNodeJobCandidatesResponse(ResponseModel): + previous_node_outputs: list[dict[str, Any]] = Field(default_factory=list) + declare_output_types: list[DeclaredOutputType] = Field(default_factory=list) + human_contacts: list[dict[str, Any]] = Field(default_factory=list) + + +class AgentComposerSoulCandidatesResponse(ResponseModel): + skills_files: list[dict[str, Any]] = Field(default_factory=list) + dify_tools: list[dict[str, Any]] = Field(default_factory=list) + cli_tools: list[dict[str, Any]] = Field(default_factory=list) + knowledge_datasets: list[dict[str, Any]] = Field(default_factory=list) + human_contacts: list[dict[str, Any]] = Field(default_factory=list) + + +class AgentComposerCandidatesResponse(ResponseModel): + variant: ComposerVariant + allowed_node_job_candidates: AgentComposerNodeJobCandidatesResponse = Field( + default_factory=AgentComposerNodeJobCandidatesResponse + ) + allowed_soul_candidates: AgentComposerSoulCandidatesResponse = Field( + default_factory=AgentComposerSoulCandidatesResponse + ) + capabilities: ComposerCandidateCapabilities = Field(default_factory=ComposerCandidateCapabilities) diff --git a/api/openapi/markdown/console-swagger.md b/api/openapi/markdown/console-swagger.md index a2302be32c..a84c3a4cad 100644 --- a/api/openapi/markdown/console-swagger.md +++ b/api/openapi/markdown/console-swagger.md @@ -343,11 +343,19 @@ Check if activation token is valid ### /agents #### GET +##### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ------ | +| keyword | query | | No | string | +| limit | query | | No | integer | +| page | query | | No | integer | + ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Agent roster list | [AgentRosterListResponse](#agentrosterlistresponse) | #### POST ##### Parameters @@ -358,18 +366,27 @@ Check if activation token is valid ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 201 | Agent created | [AgentRosterResponse](#agentrosterresponse) | ### /agents/invite-options #### GET +##### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ------ | +| app_id | query | Workflow app id for in-current-workflow markers | No | string | +| keyword | query | | No | string | +| limit | query | | No | integer | +| page | query | | No | integer | + ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Agent invite options | [AgentInviteOptionsResponse](#agentinviteoptionsresponse) | ### /agents/{agent_id} @@ -384,7 +401,7 @@ Check if activation token is valid | Code | Description | | ---- | ----------- | -| 200 | Success | +| 204 | Agent archived | #### GET ##### Parameters @@ -395,9 +412,9 @@ Check if activation token is valid ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Agent detail | [AgentRosterResponse](#agentrosterresponse) | #### PATCH ##### Parameters @@ -409,9 +426,9 @@ Check if activation token is valid ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Agent updated | [AgentRosterResponse](#agentrosterresponse) | ### /agents/{agent_id}/versions @@ -424,9 +441,9 @@ Check if activation token is valid ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Agent versions | [AgentConfigSnapshotListResponse](#agentconfigsnapshotlistresponse) | ### /agents/{agent_id}/versions/{version_id} @@ -440,9 +457,9 @@ Check if activation token is valid ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Agent version detail | [AgentConfigSnapshotDetailResponse](#agentconfigsnapshotdetailresponse) | ### /all-workspaces @@ -978,9 +995,9 @@ Run draft workflow for advanced chat application ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Agent app composer state | [AgentAppComposerResponse](#agentappcomposerresponse) | #### PUT ##### Parameters @@ -992,9 +1009,9 @@ Run draft workflow for advanced chat application ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Agent app composer saved | [AgentAppComposerResponse](#agentappcomposerresponse) | ### /apps/{app_id}/agent-composer/candidates @@ -1007,9 +1024,9 @@ Run draft workflow for advanced chat application ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Agent app composer candidates | [AgentComposerCandidatesResponse](#agentcomposercandidatesresponse) | ### /apps/{app_id}/agent-composer/validate @@ -1023,9 +1040,9 @@ Run draft workflow for advanced chat application ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Agent app composer validation result | [AgentComposerValidateResponse](#agentcomposervalidateresponse) | ### /apps/{app_id}/agent/logs @@ -3224,9 +3241,9 @@ Run draft workflow loop node ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Workflow agent composer state | [WorkflowAgentComposerResponse](#workflowagentcomposerresponse) | #### PUT ##### Parameters @@ -3239,9 +3256,9 @@ Run draft workflow loop node ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Workflow agent composer saved | [WorkflowAgentComposerResponse](#workflowagentcomposerresponse) | ### /apps/{app_id}/workflows/draft/nodes/{node_id}/agent-composer/candidates @@ -3255,9 +3272,9 @@ Run draft workflow loop node ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Workflow agent composer candidates | [AgentComposerCandidatesResponse](#agentcomposercandidatesresponse) | ### /apps/{app_id}/workflows/draft/nodes/{node_id}/agent-composer/impact @@ -3268,12 +3285,13 @@ Run draft workflow loop node | ---- | ---------- | ----------- | -------- | ------ | | app_id | path | | Yes | string | | node_id | path | | Yes | string | +| payload | body | | Yes | [ComposerSavePayload](#composersavepayload) | ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Workflow agent composer impact | [AgentComposerImpactResponse](#agentcomposerimpactresponse) | ### /apps/{app_id}/workflows/draft/nodes/{node_id}/agent-composer/save-to-roster @@ -3288,9 +3306,9 @@ Run draft workflow loop node ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Workflow agent composer saved to roster | [WorkflowAgentComposerResponse](#workflowagentcomposerresponse) | ### /apps/{app_id}/workflows/draft/nodes/{node_id}/agent-composer/validate @@ -3305,9 +3323,9 @@ Run draft workflow loop node ##### Responses -| Code | Description | -| ---- | ----------- | -| 200 | Success | +| Code | Description | Schema | +| ---- | ----------- | ------ | +| 200 | Workflow agent composer validation result | [AgentComposerValidateResponse](#agentcomposervalidateresponse) | ### /apps/{app_id}/workflows/draft/nodes/{node_id}/last-run @@ -10651,6 +10669,150 @@ Get banner list | model_mode | string | Model mode | Yes | | model_name | string | Model name | Yes | +#### AgentAppComposerResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| active_config_snapshot | [AgentConfigSnapshotSummaryResponse](#agentconfigsnapshotsummaryresponse) | | Yes | +| agent | [AgentComposerAgentResponse](#agentcomposeragentresponse) | | Yes | +| agent_soul | [AgentSoulConfig](#agentsoulconfig) | | Yes | +| save_options | [ [ComposerSaveStrategy](#composersavestrategy) ] | | Yes | +| variant | string | | Yes | + +#### AgentComposerAgentResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| active_config_snapshot_id | string | | No | +| description | string | | Yes | +| id | string | | Yes | +| name | string | | Yes | +| scope | [AgentScope](#agentscope) | | Yes | +| status | [AgentStatus](#agentstatus) | | Yes | + +#### AgentComposerBindingResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| agent_id | string | | No | +| binding_type | [WorkflowAgentBindingType](#workflowagentbindingtype) | | Yes | +| current_snapshot_id | string | | No | +| id | string | | Yes | +| node_id | string | | Yes | +| workflow_id | string | | Yes | + +#### AgentComposerCandidatesResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| allowed_node_job_candidates | [AgentComposerNodeJobCandidatesResponse](#agentcomposernodejobcandidatesresponse) | | No | +| allowed_soul_candidates | [AgentComposerSoulCandidatesResponse](#agentcomposersoulcandidatesresponse) | | No | +| capabilities | [ComposerCandidateCapabilities](#composercandidatecapabilities) | | No | +| variant | [ComposerVariant](#composervariant) | | Yes | + +#### AgentComposerImpactBindingResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| app_id | string | | Yes | +| node_id | string | | Yes | +| workflow_id | string | | Yes | + +#### AgentComposerImpactResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| bindings | [ [AgentComposerImpactBindingResponse](#agentcomposerimpactbindingresponse) ] | | No | +| current_snapshot_id | string | | No | +| workflow_node_count | integer | | Yes | + +#### AgentComposerNodeJobCandidatesResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| declare_output_types | [ [DeclaredOutputType](#declaredoutputtype) ] | | No | +| human_contacts | [ object ] | | No | +| previous_node_outputs | [ object ] | | No | + +#### AgentComposerSoulCandidatesResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| cli_tools | [ object ] | | No | +| dify_tools | [ object ] | | No | +| human_contacts | [ object ] | | No | +| knowledge_datasets | [ object ] | | No | +| skills_files | [ object ] | | No | + +#### AgentComposerSoulLockResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| can_unlock | boolean | | No | +| locked | boolean | | Yes | +| reason | string | | No | + +#### AgentComposerValidateResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| errors | [ string ] | | No | +| result | string | | Yes | + +#### AgentConfigRevisionOperation + +Audit operation recorded for Agent Soul version/revision changes. + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| AgentConfigRevisionOperation | string | Audit operation recorded for Agent Soul version/revision changes. | | + +#### AgentConfigRevisionResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| created_at | string | | No | +| created_by | string | | No | +| current_snapshot_id | string | | Yes | +| id | string | | Yes | +| operation | [AgentConfigRevisionOperation](#agentconfigrevisionoperation) | | Yes | +| previous_snapshot_id | string | | No | +| revision | integer | | Yes | +| summary | string | | No | +| version_note | string | | No | + +#### AgentConfigSnapshotDetailResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| agent_id | string | | No | +| config_snapshot | [AgentSoulConfig](#agentsoulconfig) | | Yes | +| created_at | string | | No | +| created_by | string | | No | +| id | string | | Yes | +| revisions | [ [AgentConfigRevisionResponse](#agentconfigrevisionresponse) ] | | No | +| summary | string | | No | +| version | integer | | Yes | +| version_note | string | | No | + +#### AgentConfigSnapshotListResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| data | [ [AgentConfigSnapshotSummaryResponse](#agentconfigsnapshotsummaryresponse) ] | | Yes | + +#### AgentConfigSnapshotSummaryResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| agent_id | string | | No | +| created_at | string | | No | +| created_by | string | | No | +| id | string | | Yes | +| summary | string | | No | +| version | integer | | Yes | +| version_note | string | | No | + #### AgentIconType Supported icon storage formats for Agent roster entries. @@ -10665,6 +10827,35 @@ Supported icon storage formats for Agent roster entries. | ---- | ---- | ----------- | -------- | | agent_id | string | | Yes | +#### AgentInviteOptionResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| active_config_snapshot | [AgentConfigSnapshotSummaryResponse](#agentconfigsnapshotsummaryresponse) | | No | +| active_config_snapshot_id | string | | No | +| agent_kind | [AgentKind](#agentkind) | | Yes | +| app_id | string | | No | +| archived_at | string | | No | +| archived_by | string | | No | +| created_at | string | | No | +| created_by | string | | No | +| description | string | | Yes | +| existing_node_ids | [ string ] | | No | +| icon | string | | No | +| icon_background | string | | No | +| icon_type | [AgentIconType](#agenticontype) | | No | +| id | string | | Yes | +| in_current_workflow_count | integer | | No | +| is_in_current_workflow | boolean | | No | +| name | string | | Yes | +| scope | [AgentScope](#agentscope) | | Yes | +| source | [AgentSource](#agentsource) | | Yes | +| status | [AgentStatus](#agentstatus) | | Yes | +| updated_at | string | | No | +| updated_by | string | | No | +| workflow_id | string | | No | +| workflow_node_id | string | | No | + #### AgentInviteOptionsQuery | Name | Type | Description | Required | @@ -10674,6 +10865,27 @@ Supported icon storage formats for Agent roster entries. | limit | integer | | No | | page | integer | | No | +#### AgentInviteOptionsResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| data | [ [AgentInviteOptionResponse](#agentinviteoptionresponse) ] | | Yes | +| has_more | boolean | | Yes | +| limit | integer | | Yes | +| page | integer | | Yes | +| total | integer | | Yes | + +#### AgentKind + +Agent implementation family. + +This leaves room for future non-Dify agent implementations while keeping +the current roster/workflow APIs scoped to Dify Agent. + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| AgentKind | string | Agent implementation family. This leaves room for future non-Dify agent implementations while keeping the current roster/workflow APIs scoped to Dify Agent. | | + #### AgentKnowledgeQueryMode | Name | Type | Description | Required | @@ -10687,6 +10899,50 @@ Supported icon storage formats for Agent roster entries. | conversation_id | string | Conversation UUID | Yes | | message_id | string | Message UUID | Yes | +#### AgentRosterListResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| data | [ [AgentRosterResponse](#agentrosterresponse) ] | | Yes | +| has_more | boolean | | Yes | +| limit | integer | | Yes | +| page | integer | | Yes | +| total | integer | | Yes | + +#### AgentRosterResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| active_config_snapshot | [AgentConfigSnapshotSummaryResponse](#agentconfigsnapshotsummaryresponse) | | No | +| active_config_snapshot_id | string | | No | +| agent_kind | [AgentKind](#agentkind) | | Yes | +| app_id | string | | No | +| archived_at | string | | No | +| archived_by | string | | No | +| created_at | string | | No | +| created_by | string | | No | +| description | string | | Yes | +| icon | string | | No | +| icon_background | string | | No | +| icon_type | [AgentIconType](#agenticontype) | | No | +| id | string | | Yes | +| name | string | | Yes | +| scope | [AgentScope](#agentscope) | | Yes | +| source | [AgentSource](#agentsource) | | Yes | +| status | [AgentStatus](#agentstatus) | | Yes | +| updated_at | string | | No | +| updated_by | string | | No | +| workflow_id | string | | No | +| workflow_node_id | string | | No | + +#### AgentScope + +Visibility and lifecycle scope of an Agent record. + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| AgentScope | string | Visibility and lifecycle scope of an Agent record. | | + #### AgentSoulConfig | Name | Type | Description | Required | @@ -10821,6 +11077,22 @@ Reference to model credentials resolved only at runtime. | cli_tools | [ object ] | | No | | dify_tools | [ [AgentSoulDifyToolConfig](#agentsouldifytoolconfig) ] | | No | +#### AgentSource + +Origin that created or imported the Agent. + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| AgentSource | string | Origin that created or imported the Agent. | | + +#### AgentStatus + +Soft lifecycle state for Agent records. + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| AgentStatus | string | Soft lifecycle state for Agent records. | | + #### AgentThought | Name | Type | Description | Required | @@ -11528,6 +11800,12 @@ Button styles for user actions. | binding_type | string | *Enum:* `"inline_agent"`, `"roster_agent"` | Yes | | current_snapshot_id | string | | No | +#### ComposerCandidateCapabilities + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| human_roster_available | boolean | | No | + #### ComposerSavePayload | Name | Type | Description | Required | @@ -15369,6 +15647,32 @@ in form definiton, or a variable while the workflow is running. | embedding_provider_name | string | | Yes | | vector_weight | number | | Yes | +#### WorkflowAgentBindingType + +How a workflow node is bound to an Agent. + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| WorkflowAgentBindingType | string | How a workflow node is bound to an Agent. | | + +#### WorkflowAgentComposerResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| active_config_snapshot | [AgentConfigSnapshotSummaryResponse](#agentconfigsnapshotsummaryresponse) | | No | +| agent | [AgentComposerAgentResponse](#agentcomposeragentresponse) | | No | +| agent_soul | [AgentSoulConfig](#agentsoulconfig) | | Yes | +| app_id | string | | No | +| binding | [AgentComposerBindingResponse](#agentcomposerbindingresponse) | | No | +| effective_declared_outputs | [ [DeclaredOutputConfig](#declaredoutputconfig) ] | | No | +| impact_summary | [AgentComposerImpactResponse](#agentcomposerimpactresponse) | | No | +| node_id | string | | No | +| node_job | [WorkflowNodeJobConfig](#workflownodejobconfig) | | Yes | +| save_options | [ [ComposerSaveStrategy](#composersavestrategy) ] | | Yes | +| soul_lock | [AgentComposerSoulLockResponse](#agentcomposersoullockresponse) | | Yes | +| variant | string | | Yes | +| workflow_id | string | | No | + #### WorkflowAppLogPaginationResponse | Name | Type | Description | Required | 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 0e38bfc103..718f44cdf2 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 @@ -30,6 +30,90 @@ def _unwrap(method): return method +def _agent_response(agent_id: str = "agent-1") -> dict: + return { + "id": agent_id, + "name": "Analyst", + "description": "", + "icon_type": None, + "icon": None, + "icon_background": None, + "agent_kind": "dify_agent", + "scope": "roster", + "source": "agent_app", + "app_id": None, + "workflow_id": None, + "workflow_node_id": None, + "active_config_snapshot_id": "version-1", + "active_config_snapshot": _version_response(), + "status": "active", + "created_by": "account-1", + "updated_by": "account-1", + "archived_by": None, + "archived_at": None, + "created_at": None, + "updated_at": None, + } + + +def _version_response(version_id: str = "version-1") -> dict: + return { + "id": version_id, + "agent_id": "agent-1", + "version": 1, + "summary": None, + "version_note": None, + "created_by": "account-1", + "created_at": None, + } + + +def _workflow_composer_response(**overrides) -> dict: + response = { + "variant": "workflow", + "agent": None, + "active_config_snapshot": None, + "binding": None, + "soul_lock": {"locked": False, "can_unlock": False, "reason": "workflow_only_empty"}, + "agent_soul": {}, + "node_job": {}, + "effective_declared_outputs": [], + "save_options": ["node_job_only"], + "impact_summary": None, + "app_id": "app-1", + "workflow_id": "workflow-1", + "node_id": "node-1", + } + response.update(overrides) + return response + + +def _agent_app_composer_response() -> dict: + return { + "variant": "agent_app", + "agent": { + "id": "agent-1", + "name": "Analyst", + "description": "", + "scope": "roster", + "status": "active", + "active_config_snapshot_id": "version-1", + }, + "active_config_snapshot": _version_response(), + "agent_soul": {}, + "save_options": ["save_to_current_version", "save_as_new_version"], + } + + +def _candidates_response(variant: str) -> dict: + return { + "variant": variant, + "allowed_node_job_candidates": {}, + "allowed_soul_candidates": {}, + "capabilities": {"human_roster_available": False}, + } + + @pytest.fixture def account(): return SimpleNamespace(id="account-1") @@ -67,14 +151,15 @@ def test_roster_list_post_creates_agent_and_returns_detail(app, monkeypatch): monkeypatch.setattr( roster_controller.AgentRosterService, "get_roster_agent_detail", - lambda _self, **kwargs: {"id": kwargs["agent_id"], "tenant_id": kwargs["tenant_id"]}, + lambda _self, **kwargs: _agent_response(kwargs["agent_id"]), ) with app.test_request_context(json={"name": "Analyst", "agent_soul": {"prompt": {"system_prompt": "x"}}}): result, status = _unwrap(AgentRosterListApi.post)(AgentRosterListApi()) assert status == 201 - assert result == {"id": "agent-1", "tenant_id": "tenant-1"} + assert result["id"] == "agent-1" + assert result["agent_kind"] == "dify_agent" def test_invite_options_get_parses_app_id(app, monkeypatch): @@ -82,14 +167,14 @@ def test_invite_options_get_parses_app_id(app, monkeypatch): def list_invite_options(_self, **kwargs): captured.update(kwargs) - return {"data": []} + return {"data": [], "page": kwargs["page"], "limit": kwargs["limit"], "total": 0, "has_more": False} monkeypatch.setattr(roster_controller.AgentRosterService, "list_invite_options", list_invite_options) with app.test_request_context("/console/api/agents/invite-options?page=1&limit=10&app_id=app-1"): result = _unwrap(AgentInviteOptionsApi.get)(AgentInviteOptionsApi()) - assert result == {"data": []} + assert result == {"data": [], "page": 1, "limit": 10, "total": 0, "has_more": False} assert captured == {"tenant_id": "tenant-1", "page": 1, "limit": 10, "keyword": None, "app_id": "app-1"} @@ -100,12 +185,12 @@ def test_roster_detail_patch_delete_and_versions_call_services(app, monkeypatch) monkeypatch.setattr( roster_controller.AgentRosterService, "get_roster_agent_detail", - lambda _self, **kwargs: {"id": kwargs["agent_id"]}, + lambda _self, **kwargs: _agent_response(kwargs["agent_id"]), ) monkeypatch.setattr( roster_controller.AgentRosterService, "update_roster_agent", - lambda _self, **kwargs: {"id": kwargs["agent_id"], "description": kwargs["payload"].description}, + lambda _self, **kwargs: {**_agent_response(kwargs["agent_id"]), "description": kwargs["payload"].description}, ) monkeypatch.setattr( roster_controller.AgentRosterService, @@ -115,12 +200,29 @@ def test_roster_detail_patch_delete_and_versions_call_services(app, monkeypatch) monkeypatch.setattr( roster_controller.AgentRosterService, "list_agent_versions", - lambda _self, **kwargs: [{"id": "version-1"}], + lambda _self, **kwargs: [_version_response()], ) monkeypatch.setattr( roster_controller.AgentRosterService, "get_agent_version_detail", - lambda _self, **kwargs: {"id": kwargs["version_id"], "agent_id": kwargs["agent_id"]}, + lambda _self, **kwargs: { + **_version_response(kwargs["version_id"]), + "agent_id": kwargs["agent_id"], + "config_snapshot": {}, + "revisions": [ + { + "id": "revision-1", + "previous_snapshot_id": None, + "current_snapshot_id": kwargs["version_id"], + "revision": 1, + "operation": "create_version", + "summary": None, + "version_note": None, + "created_by": "account-1", + "created_at": None, + } + ], + }, ) assert _unwrap(AgentRosterDetailApi.get)(AgentRosterDetailApi(), agent_id)["id"] == agent_id @@ -128,11 +230,10 @@ def test_roster_detail_patch_delete_and_versions_call_services(app, monkeypatch) assert _unwrap(AgentRosterDetailApi.patch)(AgentRosterDetailApi(), agent_id)["description"] == "updated" assert _unwrap(AgentRosterDetailApi.delete)(AgentRosterDetailApi(), agent_id) == ("", 204) assert archived["account_id"] == "account-1" - assert _unwrap(AgentRosterVersionsApi.get)(AgentRosterVersionsApi(), agent_id) == {"data": [{"id": "version-1"}]} - assert _unwrap(AgentRosterVersionDetailApi.get)(AgentRosterVersionDetailApi(), agent_id, version_id) == { - "id": version_id, - "agent_id": agent_id, - } + assert _unwrap(AgentRosterVersionsApi.get)(AgentRosterVersionsApi(), agent_id)["data"][0]["id"] == "version-1" + version_detail = _unwrap(AgentRosterVersionDetailApi.get)(AgentRosterVersionDetailApi(), agent_id, version_id) + assert version_detail["id"] == version_id + assert version_detail["agent_id"] == agent_id def test_workflow_composer_get_put_validate_candidates_impact_and_save(app, monkeypatch): @@ -145,50 +246,52 @@ def test_workflow_composer_get_put_validate_candidates_impact_and_save(app, monk monkeypatch.setattr( composer_controller.AgentComposerService, "load_workflow_composer", - lambda **kwargs: {"node_id": kwargs["node_id"]}, + lambda **kwargs: _workflow_composer_response(node_id=kwargs["node_id"]), ) monkeypatch.setattr( composer_controller.AgentComposerService, "save_workflow_composer", - lambda **kwargs: {"saved": kwargs["payload"].save_strategy.value, "account_id": kwargs["account_id"]}, + lambda **kwargs: _workflow_composer_response(save_options=[kwargs["payload"].save_strategy.value]), ) monkeypatch.setattr(composer_controller.ComposerConfigValidator, "validate_save_payload", lambda payload: None) monkeypatch.setattr( composer_controller.AgentComposerService, "get_workflow_candidates", - lambda **kwargs: {"data": []}, + lambda **kwargs: _candidates_response("workflow"), ) monkeypatch.setattr( composer_controller.AgentComposerService, "calculate_impact", - lambda **kwargs: {"current_snapshot_id": kwargs["current_snapshot_id"], "workflow_node_count": 1}, + lambda **kwargs: { + "current_snapshot_id": kwargs["current_snapshot_id"], + "workflow_node_count": 1, + "bindings": [], + }, ) - assert _unwrap(WorkflowAgentComposerApi.get)(WorkflowAgentComposerApi(), app_model, "node-1") == { - "node_id": "node-1" - } + workflow_state = _unwrap(WorkflowAgentComposerApi.get)(WorkflowAgentComposerApi(), app_model, "node-1") + assert workflow_state["node_id"] == "node-1" with app.test_request_context(json=payload): - assert _unwrap(WorkflowAgentComposerApi.put)(WorkflowAgentComposerApi(), app_model, "node-1") == { - "saved": "node_job_only", - "account_id": "account-1", - } + saved_state = _unwrap(WorkflowAgentComposerApi.put)(WorkflowAgentComposerApi(), app_model, "node-1") + assert saved_state["save_options"] == ["node_job_only"] assert _unwrap(WorkflowAgentComposerValidateApi.post)( WorkflowAgentComposerValidateApi(), app_model, "node-1" ) == {"result": "success", "errors": []} - assert _unwrap(WorkflowAgentComposerCandidatesApi.get)( - WorkflowAgentComposerCandidatesApi(), app_model, "node-1" - ) == {"data": []} + assert ( + _unwrap(WorkflowAgentComposerCandidatesApi.get)(WorkflowAgentComposerCandidatesApi(), app_model, "node-1")[ + "variant" + ] + == "workflow" + ) with app.test_request_context(json=payload): assert _unwrap(WorkflowAgentComposerImpactApi.post)(WorkflowAgentComposerImpactApi(), app_model, "node-1") == { "current_snapshot_id": "version-1", "workflow_node_count": 1, + "bindings": [], } - assert ( - _unwrap(WorkflowAgentComposerSaveToRosterApi.post)( - WorkflowAgentComposerSaveToRosterApi(), app_model, "node-1" - )["saved"] - == "node_job_only" - ) + assert _unwrap(WorkflowAgentComposerSaveToRosterApi.post)( + WorkflowAgentComposerSaveToRosterApi(), app_model, "node-1" + )["save_options"] == ["node_job_only"] def test_workflow_impact_returns_empty_without_version(app): @@ -212,28 +315,26 @@ def test_agent_app_composer_get_put_validate_and_candidates(app, monkeypatch): monkeypatch.setattr( composer_controller.AgentComposerService, "load_agent_app_composer", - lambda **kwargs: {"loaded": True}, + lambda **kwargs: _agent_app_composer_response(), ) monkeypatch.setattr( composer_controller.AgentComposerService, "save_agent_app_composer", - lambda **kwargs: {"saved": kwargs["payload"].variant.value, "account_id": kwargs["account_id"]}, + lambda **kwargs: _agent_app_composer_response(), ) monkeypatch.setattr(composer_controller.ComposerConfigValidator, "validate_save_payload", lambda payload: None) monkeypatch.setattr( composer_controller.AgentComposerService, "get_agent_app_candidates", - lambda **kwargs: {"data": []}, + lambda **kwargs: _candidates_response("agent_app"), ) - assert _unwrap(AgentAppComposerApi.get)(AgentAppComposerApi(), app_model) == {"loaded": True} + assert _unwrap(AgentAppComposerApi.get)(AgentAppComposerApi(), app_model)["variant"] == "agent_app" with app.test_request_context(json=payload): - assert _unwrap(AgentAppComposerApi.put)(AgentAppComposerApi(), app_model) == { - "saved": "agent_app", - "account_id": "account-1", - } + assert _unwrap(AgentAppComposerApi.put)(AgentAppComposerApi(), app_model)["variant"] == "agent_app" assert _unwrap(AgentAppComposerValidateApi.post)(AgentAppComposerValidateApi(), app_model) == { "result": "success", "errors": [], } - assert _unwrap(AgentAppComposerCandidatesApi.get)(AgentAppComposerCandidatesApi(), app_model) == {"data": []} + agent_app_candidates = _unwrap(AgentAppComposerCandidatesApi.get)(AgentAppComposerCandidatesApi(), app_model) + assert agent_app_candidates["variant"] == "agent_app" diff --git a/cli/src/store/store.ts b/cli/src/store/store.ts index c7201fe292..408b43eb52 100644 --- a/cli/src/store/store.ts +++ b/cli/src/store/store.ts @@ -44,7 +44,7 @@ abstract class FileBasedStore implements Store { flush(): void { fs.mkdirSync(dirname(this.filePath), { recursive: true, mode: DIR_PERM }) - // we don't handle A-B-A scenario, + // we don't handle A-B-A scenario, // which is not likely to happen in cli if (!this.dirty) { return diff --git a/packages/contracts/generated/api/console/agents/orpc.gen.ts b/packages/contracts/generated/api/console/agents/orpc.gen.ts index 9df6c64ea7..067dd6fc93 100644 --- a/packages/contracts/generated/api/console/agents/orpc.gen.ts +++ b/packages/contracts/generated/api/console/agents/orpc.gen.ts @@ -12,7 +12,9 @@ import { zGetAgentsByAgentIdVersionsByVersionIdResponse, zGetAgentsByAgentIdVersionsPath, zGetAgentsByAgentIdVersionsResponse, + zGetAgentsInviteOptionsQuery, zGetAgentsInviteOptionsResponse, + zGetAgentsQuery, zGetAgentsResponse, zPatchAgentsByAgentIdBody, zPatchAgentsByAgentIdPath, @@ -21,22 +23,15 @@ import { zPostAgentsResponse, } from './zod.gen' -/** - * Generated contract types may be inaccurate because backend OpenAPI annotations are incomplete. Do not migrate callers until the generated contract is accurate. - * - * @deprecated - */ export const get = oc .route({ - deprecated: true, - description: - 'Generated contract types may be inaccurate because backend OpenAPI annotations are incomplete. Do not migrate callers until the generated contract is accurate.', inputStructure: 'detailed', method: 'GET', operationId: 'getAgentsInviteOptions', path: '/agents/invite-options', tags: ['console'], }) + .input(z.object({ query: zGetAgentsInviteOptionsQuery.optional() })) .output(zGetAgentsInviteOptionsResponse) export const inviteOptions = { @@ -66,16 +61,8 @@ export const byVersionId = { get: get2, } -/** - * Generated contract types may be inaccurate because backend OpenAPI annotations are incomplete. Do not migrate callers until the generated contract is accurate. - * - * @deprecated - */ export const get3 = oc .route({ - deprecated: true, - description: - 'Generated contract types may be inaccurate because backend OpenAPI annotations are incomplete. Do not migrate callers until the generated contract is accurate.', inputStructure: 'detailed', method: 'GET', operationId: 'getAgentsByAgentIdVersions', @@ -90,35 +77,20 @@ export const versions = { byVersionId, } -/** - * Generated contract types may be inaccurate because backend OpenAPI annotations are incomplete. Do not migrate callers until the generated contract is accurate. - * - * @deprecated - */ export const delete_ = oc .route({ - deprecated: true, - description: - 'Generated contract types may be inaccurate because backend OpenAPI annotations are incomplete. Do not migrate callers until the generated contract is accurate.', inputStructure: 'detailed', method: 'DELETE', operationId: 'deleteAgentsByAgentId', path: '/agents/{agent_id}', + successStatus: 204, tags: ['console'], }) .input(z.object({ params: zDeleteAgentsByAgentIdPath })) .output(zDeleteAgentsByAgentIdResponse) -/** - * Generated contract types may be inaccurate because backend OpenAPI annotations are incomplete. Do not migrate callers until the generated contract is accurate. - * - * @deprecated - */ export const get4 = oc .route({ - deprecated: true, - description: - 'Generated contract types may be inaccurate because backend OpenAPI annotations are incomplete. Do not migrate callers until the generated contract is accurate.', inputStructure: 'detailed', method: 'GET', operationId: 'getAgentsByAgentId', @@ -128,16 +100,8 @@ export const get4 = oc .input(z.object({ params: zGetAgentsByAgentIdPath })) .output(zGetAgentsByAgentIdResponse) -/** - * Generated contract types may be inaccurate because backend OpenAPI annotations are incomplete. Do not migrate callers until the generated contract is accurate. - * - * @deprecated - */ export const patch = oc .route({ - deprecated: true, - description: - 'Generated contract types may be inaccurate because backend OpenAPI annotations are incomplete. Do not migrate callers until the generated contract is accurate.', inputStructure: 'detailed', method: 'PATCH', operationId: 'patchAgentsByAgentId', @@ -154,22 +118,15 @@ export const byAgentId = { versions, } -/** - * Generated contract types may be inaccurate because backend OpenAPI annotations are incomplete. Do not migrate callers until the generated contract is accurate. - * - * @deprecated - */ export const get5 = oc .route({ - deprecated: true, - description: - 'Generated contract types may be inaccurate because backend OpenAPI annotations are incomplete. Do not migrate callers until the generated contract is accurate.', inputStructure: 'detailed', method: 'GET', operationId: 'getAgents', path: '/agents', tags: ['console'], }) + .input(z.object({ query: zGetAgentsQuery.optional() })) .output(zGetAgentsResponse) /** @@ -186,6 +143,7 @@ export const post = oc method: 'POST', operationId: 'postAgents', path: '/agents', + successStatus: 201, tags: ['console'], }) .input(z.object({ body: zPostAgentsBody })) diff --git a/packages/contracts/generated/api/console/agents/types.gen.ts b/packages/contracts/generated/api/console/agents/types.gen.ts index e63c57f03b..3ab55fe03a 100644 --- a/packages/contracts/generated/api/console/agents/types.gen.ts +++ b/packages/contracts/generated/api/console/agents/types.gen.ts @@ -4,6 +4,14 @@ export type ClientOptions = { baseUrl: `${string}://${string}/console/api` | (string & {}) } +export type AgentRosterListResponse = { + data: Array + has_more: boolean + limit: number + page: number + total: number +} + export type RosterAgentCreatePayload = { agent_soul?: AgentSoulConfig description?: string @@ -14,6 +22,38 @@ export type RosterAgentCreatePayload = { version_note?: string | null } +export type AgentRosterResponse = { + active_config_snapshot?: AgentConfigSnapshotSummaryResponse + active_config_snapshot_id?: string | null + agent_kind: AgentKind + app_id?: string | null + archived_at?: string | null + archived_by?: string | null + created_at?: string | null + created_by?: string | null + description: string + icon?: string | null + icon_background?: string | null + icon_type?: AgentIconType + id: string + name: string + scope: AgentScope + source: AgentSource + status: AgentStatus + updated_at?: string | null + updated_by?: string | null + workflow_id?: string | null + workflow_node_id?: string | null +} + +export type AgentInviteOptionsResponse = { + data: Array + has_more: boolean + limit: number + page: number + total: number +} + export type RosterAgentUpdatePayload = { description?: string | null icon?: string | null @@ -22,6 +62,22 @@ export type RosterAgentUpdatePayload = { name?: string | null } +export type AgentConfigSnapshotListResponse = { + data: Array +} + +export type AgentConfigSnapshotDetailResponse = { + agent_id?: string | null + config_snapshot: AgentSoulConfig + created_at?: string | null + created_by?: string | null + id: string + revisions?: Array + summary?: string | null + version: number + version_note?: string | null +} + export type AgentSoulConfig = { app_features?: { [key: string]: unknown @@ -44,6 +100,63 @@ export type AgentSoulConfig = { export type AgentIconType = 'emoji' | 'image' | 'link' +export type AgentConfigSnapshotSummaryResponse = { + agent_id?: string | null + created_at?: string | null + created_by?: string | null + id: string + summary?: string | null + version: number + version_note?: string | null +} + +export type AgentKind = 'dify_agent' + +export type AgentScope = 'roster' | 'workflow_only' + +export type AgentSource = 'agent_app' | 'imported' | 'system' | 'workflow' + +export type AgentStatus = 'active' | 'archived' + +export type AgentInviteOptionResponse = { + active_config_snapshot?: AgentConfigSnapshotSummaryResponse + active_config_snapshot_id?: string | null + agent_kind: AgentKind + app_id?: string | null + archived_at?: string | null + archived_by?: string | null + created_at?: string | null + created_by?: string | null + description: string + existing_node_ids?: Array + icon?: string | null + icon_background?: string | null + icon_type?: AgentIconType + id: string + in_current_workflow_count?: number + is_in_current_workflow?: boolean + name: string + scope: AgentScope + source: AgentSource + status: AgentStatus + updated_at?: string | null + updated_by?: string | null + workflow_id?: string | null + workflow_node_id?: string | null +} + +export type AgentConfigRevisionResponse = { + created_at?: string | null + created_by?: string | null + current_snapshot_id: string + id: string + operation: AgentConfigRevisionOperation + previous_snapshot_id?: string | null + revision: number + summary?: string | null + version_note?: string | null +} + export type AppVariableConfig = { default?: unknown name: string @@ -124,6 +237,13 @@ export type AgentSoulToolsConfig = { dify_tools?: Array } +export type AgentConfigRevisionOperation + = | 'create_version' + | 'save_current_version' + | 'save_new_agent' + | 'save_new_version' + | 'save_to_roster' + export type AgentKnowledgeQueryMode = 'generated_query' | 'user_query' export type AgentSoulModelCredentialRef = { @@ -157,14 +277,16 @@ export type AgentSoulDifyToolCredentialRef = { export type GetAgentsData = { body?: never path?: never - query?: never + query?: { + keyword?: string + limit?: number + page?: number + } url: '/agents' } export type GetAgentsResponses = { - 200: { - [key: string]: unknown - } + 200: AgentRosterListResponse } export type GetAgentsResponse = GetAgentsResponses[keyof GetAgentsResponses] @@ -177,9 +299,7 @@ export type PostAgentsData = { } export type PostAgentsResponses = { - 200: { - [key: string]: unknown - } + 201: AgentRosterResponse } export type PostAgentsResponse = PostAgentsResponses[keyof PostAgentsResponses] @@ -187,14 +307,17 @@ export type PostAgentsResponse = PostAgentsResponses[keyof PostAgentsResponses] export type GetAgentsInviteOptionsData = { body?: never path?: never - query?: never + query?: { + app_id?: string + keyword?: string + limit?: number + page?: number + } url: '/agents/invite-options' } export type GetAgentsInviteOptionsResponses = { - 200: { - [key: string]: unknown - } + 200: AgentInviteOptionsResponse } export type GetAgentsInviteOptionsResponse @@ -210,8 +333,8 @@ export type DeleteAgentsByAgentIdData = { } export type DeleteAgentsByAgentIdResponses = { - 200: { - [key: string]: unknown + 204: { + [key: string]: never } } @@ -228,9 +351,7 @@ export type GetAgentsByAgentIdData = { } export type GetAgentsByAgentIdResponses = { - 200: { - [key: string]: unknown - } + 200: AgentRosterResponse } export type GetAgentsByAgentIdResponse @@ -246,9 +367,7 @@ export type PatchAgentsByAgentIdData = { } export type PatchAgentsByAgentIdResponses = { - 200: { - [key: string]: unknown - } + 200: AgentRosterResponse } export type PatchAgentsByAgentIdResponse @@ -264,9 +383,7 @@ export type GetAgentsByAgentIdVersionsData = { } export type GetAgentsByAgentIdVersionsResponses = { - 200: { - [key: string]: unknown - } + 200: AgentConfigSnapshotListResponse } export type GetAgentsByAgentIdVersionsResponse @@ -283,9 +400,7 @@ export type GetAgentsByAgentIdVersionsByVersionIdData = { } export type GetAgentsByAgentIdVersionsByVersionIdResponses = { - 200: { - [key: string]: unknown - } + 200: AgentConfigSnapshotDetailResponse } export type GetAgentsByAgentIdVersionsByVersionIdResponse diff --git a/packages/contracts/generated/api/console/agents/zod.gen.ts b/packages/contracts/generated/api/console/agents/zod.gen.ts index 130144d48d..58e7f1c894 100644 --- a/packages/contracts/generated/api/console/agents/zod.gen.ts +++ b/packages/contracts/generated/api/console/agents/zod.gen.ts @@ -20,6 +20,136 @@ export const zRosterAgentUpdatePayload = z.object({ name: z.string().min(1).max(255).nullish(), }) +/** + * AgentConfigSnapshotSummaryResponse + */ +export const zAgentConfigSnapshotSummaryResponse = z.object({ + agent_id: z.string().nullish(), + created_at: z.string().nullish(), + created_by: z.string().nullish(), + id: z.string(), + summary: z.string().nullish(), + version: z.int(), + version_note: z.string().nullish(), +}) + +/** + * AgentConfigSnapshotListResponse + */ +export const zAgentConfigSnapshotListResponse = z.object({ + data: z.array(zAgentConfigSnapshotSummaryResponse), +}) + +/** + * AgentKind + * + * Agent implementation family. + * + * This leaves room for future non-Dify agent implementations while keeping + * the current roster/workflow APIs scoped to Dify Agent. + */ +export const zAgentKind = z.enum(['dify_agent']) + +/** + * AgentScope + * + * Visibility and lifecycle scope of an Agent record. + */ +export const zAgentScope = z.enum(['roster', 'workflow_only']) + +/** + * AgentSource + * + * Origin that created or imported the Agent. + */ +export const zAgentSource = z.enum(['agent_app', 'imported', 'system', 'workflow']) + +/** + * AgentStatus + * + * Soft lifecycle state for Agent records. + */ +export const zAgentStatus = z.enum(['active', 'archived']) + +/** + * AgentRosterResponse + */ +export const zAgentRosterResponse = z.object({ + active_config_snapshot: zAgentConfigSnapshotSummaryResponse.optional(), + active_config_snapshot_id: z.string().nullish(), + agent_kind: zAgentKind, + app_id: z.string().nullish(), + archived_at: z.string().nullish(), + archived_by: z.string().nullish(), + created_at: z.string().nullish(), + created_by: z.string().nullish(), + description: z.string(), + icon: z.string().nullish(), + icon_background: z.string().nullish(), + icon_type: zAgentIconType.optional(), + id: z.string(), + name: z.string(), + scope: zAgentScope, + source: zAgentSource, + status: zAgentStatus, + updated_at: z.string().nullish(), + updated_by: z.string().nullish(), + workflow_id: z.string().nullish(), + workflow_node_id: z.string().nullish(), +}) + +/** + * AgentRosterListResponse + */ +export const zAgentRosterListResponse = z.object({ + data: z.array(zAgentRosterResponse), + has_more: z.boolean(), + limit: z.int(), + page: z.int(), + total: z.int(), +}) + +/** + * AgentInviteOptionResponse + */ +export const zAgentInviteOptionResponse = z.object({ + active_config_snapshot: zAgentConfigSnapshotSummaryResponse.optional(), + active_config_snapshot_id: z.string().nullish(), + agent_kind: zAgentKind, + app_id: z.string().nullish(), + archived_at: z.string().nullish(), + archived_by: z.string().nullish(), + created_at: z.string().nullish(), + created_by: z.string().nullish(), + description: z.string(), + existing_node_ids: z.array(z.string()).optional(), + icon: z.string().nullish(), + icon_background: z.string().nullish(), + icon_type: zAgentIconType.optional(), + id: z.string(), + in_current_workflow_count: z.int().optional().default(0), + is_in_current_workflow: z.boolean().optional().default(false), + name: z.string(), + scope: zAgentScope, + source: zAgentSource, + status: zAgentStatus, + updated_at: z.string().nullish(), + updated_by: z.string().nullish(), + workflow_id: z.string().nullish(), + workflow_node_id: z.string().nullish(), +}) + +/** + * AgentInviteOptionsResponse + */ +export const zAgentInviteOptionsResponse = z.object({ + data: z.array(zAgentInviteOptionResponse), + has_more: z.boolean(), + limit: z.int(), + page: z.int(), + total: z.int(), +}) + /** * AppVariableConfig */ @@ -78,6 +208,34 @@ export const zAgentSoulSkillsFilesConfig = z.object({ skills: z.array(z.record(z.string(), z.unknown())).optional(), }) +/** + * AgentConfigRevisionOperation + * + * Audit operation recorded for Agent Soul version/revision changes. + */ +export const zAgentConfigRevisionOperation = z.enum([ + 'create_version', + 'save_current_version', + 'save_new_agent', + 'save_new_version', + 'save_to_roster', +]) + +/** + * AgentConfigRevisionResponse + */ +export const zAgentConfigRevisionResponse = z.object({ + created_at: z.string().nullish(), + created_by: z.string().nullish(), + current_snapshot_id: z.string(), + id: z.string(), + operation: zAgentConfigRevisionOperation, + previous_snapshot_id: z.string().nullish(), + revision: z.int(), + summary: z.string().nullish(), + version_note: z.string().nullish(), +}) + /** * AgentKnowledgeQueryMode */ @@ -196,39 +354,67 @@ export const zRosterAgentCreatePayload = z.object({ }) /** - * Success + * AgentConfigSnapshotDetailResponse */ -export const zGetAgentsResponse = z.record(z.string(), z.unknown()) +export const zAgentConfigSnapshotDetailResponse = z.object({ + agent_id: z.string().nullish(), + config_snapshot: zAgentSoulConfig, + created_at: z.string().nullish(), + created_by: z.string().nullish(), + id: z.string(), + revisions: z.array(zAgentConfigRevisionResponse).optional(), + summary: z.string().nullish(), + version: z.int(), + version_note: z.string().nullish(), +}) + +export const zGetAgentsQuery = z.object({ + keyword: z.string().optional(), + limit: z.int().gte(1).lte(100).optional().default(20), + page: z.int().gte(1).optional().default(1), +}) + +/** + * Agent roster list + */ +export const zGetAgentsResponse = zAgentRosterListResponse export const zPostAgentsBody = zRosterAgentCreatePayload /** - * Success + * Agent created */ -export const zPostAgentsResponse = z.record(z.string(), z.unknown()) +export const zPostAgentsResponse = zAgentRosterResponse + +export const zGetAgentsInviteOptionsQuery = z.object({ + app_id: z.string().optional(), + keyword: z.string().optional(), + limit: z.int().gte(1).lte(100).optional().default(20), + page: z.int().gte(1).optional().default(1), +}) /** - * Success + * Agent invite options */ -export const zGetAgentsInviteOptionsResponse = z.record(z.string(), z.unknown()) +export const zGetAgentsInviteOptionsResponse = zAgentInviteOptionsResponse export const zDeleteAgentsByAgentIdPath = z.object({ agent_id: z.string(), }) /** - * Success + * Agent archived */ -export const zDeleteAgentsByAgentIdResponse = z.record(z.string(), z.unknown()) +export const zDeleteAgentsByAgentIdResponse = z.record(z.string(), z.never()) export const zGetAgentsByAgentIdPath = z.object({ agent_id: z.string(), }) /** - * Success + * Agent detail */ -export const zGetAgentsByAgentIdResponse = z.record(z.string(), z.unknown()) +export const zGetAgentsByAgentIdResponse = zAgentRosterResponse export const zPatchAgentsByAgentIdBody = zRosterAgentUpdatePayload @@ -237,18 +423,18 @@ export const zPatchAgentsByAgentIdPath = z.object({ }) /** - * Success + * Agent updated */ -export const zPatchAgentsByAgentIdResponse = z.record(z.string(), z.unknown()) +export const zPatchAgentsByAgentIdResponse = zAgentRosterResponse export const zGetAgentsByAgentIdVersionsPath = z.object({ agent_id: z.string(), }) /** - * Success + * Agent versions */ -export const zGetAgentsByAgentIdVersionsResponse = z.record(z.string(), z.unknown()) +export const zGetAgentsByAgentIdVersionsResponse = zAgentConfigSnapshotListResponse export const zGetAgentsByAgentIdVersionsByVersionIdPath = z.object({ agent_id: z.string(), @@ -256,6 +442,6 @@ export const zGetAgentsByAgentIdVersionsByVersionIdPath = z.object({ }) /** - * Success + * Agent version detail */ -export const zGetAgentsByAgentIdVersionsByVersionIdResponse = z.record(z.string(), z.unknown()) +export const zGetAgentsByAgentIdVersionsByVersionIdResponse = zAgentConfigSnapshotDetailResponse diff --git a/packages/contracts/generated/api/console/apps/orpc.gen.ts b/packages/contracts/generated/api/console/apps/orpc.gen.ts index a4f6183130..f1f973ecab 100644 --- a/packages/contracts/generated/api/console/apps/orpc.gen.ts +++ b/packages/contracts/generated/api/console/apps/orpc.gen.ts @@ -352,6 +352,7 @@ import { zPostAppsByAppIdWorkflowsDraftLoopNodesByNodeIdRunBody, zPostAppsByAppIdWorkflowsDraftLoopNodesByNodeIdRunPath, zPostAppsByAppIdWorkflowsDraftLoopNodesByNodeIdRunResponse, + zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactBody, zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactPath, zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactResponse, zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerSaveToRosterBody, @@ -3516,7 +3517,12 @@ export const post47 = oc path: '/apps/{app_id}/workflows/draft/nodes/{node_id}/agent-composer/impact', tags: ['console'], }) - .input(z.object({ params: zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactPath })) + .input( + z.object({ + body: zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactBody, + params: zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactPath, + }), + ) .output(zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactResponse) export const impact = { diff --git a/packages/contracts/generated/api/console/apps/types.gen.ts b/packages/contracts/generated/api/console/apps/types.gen.ts index c62f34bb44..1b85de8add 100644 --- a/packages/contracts/generated/api/console/apps/types.gen.ts +++ b/packages/contracts/generated/api/console/apps/types.gen.ts @@ -167,6 +167,14 @@ export type AdvancedChatWorkflowRunPayload = { query?: string } +export type AgentAppComposerResponse = { + active_config_snapshot: AgentConfigSnapshotSummaryResponse + agent: AgentComposerAgentResponse + agent_soul: AgentSoulConfig + save_options: Array + variant: string +} + export type ComposerSavePayload = { agent_soul?: AgentSoulConfig binding?: ComposerBindingPayload @@ -180,6 +188,18 @@ export type ComposerSavePayload = { version_note?: string | null } +export type AgentComposerCandidatesResponse = { + allowed_node_job_candidates?: AgentComposerNodeJobCandidatesResponse + allowed_soul_candidates?: AgentComposerSoulCandidatesResponse + capabilities?: ComposerCandidateCapabilities + variant: ComposerVariant +} + +export type AgentComposerValidateResponse = { + errors?: Array + result: string +} + export type AnnotationReplyPayload = { embedding_model_name: string embedding_provider_name: string @@ -736,6 +756,28 @@ export type HumanInputDeliveryTestPayload = { } } +export type WorkflowAgentComposerResponse = { + active_config_snapshot?: AgentConfigSnapshotSummaryResponse + agent?: AgentComposerAgentResponse + agent_soul: AgentSoulConfig + app_id?: string | null + binding?: AgentComposerBindingResponse + effective_declared_outputs?: Array + impact_summary?: AgentComposerImpactResponse + node_id?: string | null + node_job: WorkflowNodeJobConfig + save_options: Array + soul_lock: AgentComposerSoulLockResponse + variant: string + workflow_id?: string | null +} + +export type AgentComposerImpactResponse = { + bindings?: Array + current_snapshot_id?: string | null + workflow_node_count: number +} + export type WorkflowRunNodeExecutionResponse = { created_at?: number | null created_by_account?: SimpleAccount @@ -961,6 +1003,25 @@ export type AdvancedChatWorkflowRunForListResponse = { version?: string | null } +export type AgentConfigSnapshotSummaryResponse = { + agent_id?: string | null + created_at?: string | null + created_by?: string | null + id: string + summary?: string | null + version: number + version_note?: string | null +} + +export type AgentComposerAgentResponse = { + active_config_snapshot_id?: string | null + description: string + id: string + name: string + scope: AgentScope + status: AgentStatus +} + export type AgentSoulConfig = { app_features?: { [key: string]: unknown @@ -981,6 +1042,13 @@ export type AgentSoulConfig = { tools?: AgentSoulToolsConfig } +export type ComposerSaveStrategy + = | 'node_job_only' + | 'save_as_new_agent' + | 'save_as_new_version' + | 'save_to_current_version' + | 'save_to_roster' + export type ComposerBindingPayload = { agent_id?: string | null binding_type: 'inline_agent' | 'roster_agent' @@ -1003,13 +1071,6 @@ export type WorkflowNodeJobConfig = { workflow_prompt?: string } -export type ComposerSaveStrategy - = | 'node_job_only' - | 'save_as_new_agent' - | 'save_as_new_version' - | 'save_to_current_version' - | 'save_to_roster' - export type ComposerSoulLockPayload = { locked?: boolean unlocked_from_version_id?: string | null @@ -1017,6 +1078,38 @@ export type ComposerSoulLockPayload = { export type ComposerVariant = 'agent_app' | 'workflow' +export type AgentComposerNodeJobCandidatesResponse = { + declare_output_types?: Array + human_contacts?: Array<{ + [key: string]: unknown + }> + previous_node_outputs?: Array<{ + [key: string]: unknown + }> +} + +export type AgentComposerSoulCandidatesResponse = { + cli_tools?: Array<{ + [key: string]: unknown + }> + dify_tools?: Array<{ + [key: string]: unknown + }> + human_contacts?: Array<{ + [key: string]: unknown + }> + knowledge_datasets?: Array<{ + [key: string]: unknown + }> + skills_files?: Array<{ + [key: string]: unknown + }> +} + +export type ComposerCandidateCapabilities = { + human_roster_available?: boolean +} + export type AnnotationHitHistory = { annotation_content?: string | null annotation_question?: string | null @@ -1305,6 +1398,39 @@ export type PipelineVariableResponse = { variable: string } +export type AgentComposerBindingResponse = { + agent_id?: string | null + binding_type: WorkflowAgentBindingType + current_snapshot_id?: string | null + id: string + node_id: string + workflow_id: string +} + +export type DeclaredOutputConfig = { + array_item?: DeclaredArrayItem + check?: DeclaredOutputCheckConfig + description?: string | null + failure_strategy?: DeclaredOutputFailureStrategy + file?: DeclaredOutputFileConfig + id?: string | null + name: string + required?: boolean + type: DeclaredOutputType +} + +export type AgentComposerSoulLockResponse = { + can_unlock?: boolean + locked: boolean + reason?: string | null +} + +export type AgentComposerImpactBindingResponse = { + app_id: string + node_id: string + workflow_id: string +} + export type WorkflowDraftVariableWithoutValue = { description?: string edited?: boolean @@ -1353,6 +1479,10 @@ export type WorkflowOnlineUser = { username: string } +export type AgentScope = 'roster' | 'workflow_only' + +export type AgentStatus = 'active' | 'archived' + export type AppVariableConfig = { default?: unknown name: string @@ -1433,20 +1563,10 @@ export type AgentSoulToolsConfig = { dify_tools?: Array } -export type DeclaredOutputConfig = { - array_item?: DeclaredArrayItem - check?: DeclaredOutputCheckConfig - description?: string | null - failure_strategy?: DeclaredOutputFailureStrategy - file?: DeclaredOutputFileConfig - id?: string | null - name: string - required?: boolean - type: DeclaredOutputType -} - export type WorkflowNodeJobMode = 'let_agent_figure_it_out' | 'tell_agent_what_to_do' +export type DeclaredOutputType = 'array' | 'boolean' | 'file' | 'number' | 'object' | 'string' + export type SimpleModelConfig = { model_dict?: JsonValue pre_prompt?: string | null @@ -1515,29 +1635,7 @@ export type WorkflowRunForArchivedLogResponse = { triggered_from?: string | null } -export type AgentKnowledgeQueryMode = 'generated_query' | 'user_query' - -export type AgentSoulModelCredentialRef = { - id?: string | null - provider?: string | null - type: string -} - -export type AgentSoulDifyToolConfig = { - credential_ref?: AgentSoulDifyToolCredentialRef - credential_type?: 'api-key' | 'oauth2' | 'unauthorized' - description?: string | null - enabled?: boolean - name?: string | null - plugin_id?: string | null - provider?: string | null - provider_id?: string | null - provider_type?: string - runtime_parameters?: { - [key: string]: unknown - } - tool_name: string -} +export type WorkflowAgentBindingType = 'inline_agent' | 'roster_agent' export type DeclaredArrayItem = { description?: string | null @@ -1566,7 +1664,29 @@ export type DeclaredOutputFileConfig = { mime_types?: Array } -export type DeclaredOutputType = 'array' | 'boolean' | 'file' | 'number' | 'object' | 'string' +export type AgentKnowledgeQueryMode = 'generated_query' | 'user_query' + +export type AgentSoulModelCredentialRef = { + id?: string | null + provider?: string | null + type: string +} + +export type AgentSoulDifyToolConfig = { + credential_ref?: AgentSoulDifyToolCredentialRef + credential_type?: 'api-key' | 'oauth2' | 'unauthorized' + description?: string | null + enabled?: boolean + name?: string | null + plugin_id?: string | null + provider?: string | null + provider_id?: string | null + provider_type?: string + runtime_parameters?: { + [key: string]: unknown + } + tool_name: string +} export type UserActionConfig = { button_style?: ButtonStyle @@ -1576,12 +1696,6 @@ export type UserActionConfig = { export type FormInputConfig = unknown -export type AgentSoulDifyToolCredentialRef = { - id?: string | null - provider?: string | null - type?: 'provider' | 'tool' -} - export type OutputErrorStrategy = 'default_value' | 'fail_branch' | 'stop' export type DeclaredOutputRetryConfig = { @@ -1590,6 +1704,12 @@ export type DeclaredOutputRetryConfig = { retry_interval_ms?: number } +export type AgentSoulDifyToolCredentialRef = { + id?: string | null + provider?: string | null + type?: 'provider' | 'tool' +} + export type ButtonStyle = 'accent' | 'default' | 'ghost' | 'primary' export type ParagraphInputConfig = { @@ -2062,9 +2182,7 @@ export type GetAppsByAppIdAgentComposerData = { } export type GetAppsByAppIdAgentComposerResponses = { - 200: { - [key: string]: unknown - } + 200: AgentAppComposerResponse } export type GetAppsByAppIdAgentComposerResponse @@ -2080,9 +2198,7 @@ export type PutAppsByAppIdAgentComposerData = { } export type PutAppsByAppIdAgentComposerResponses = { - 200: { - [key: string]: unknown - } + 200: AgentAppComposerResponse } export type PutAppsByAppIdAgentComposerResponse @@ -2098,9 +2214,7 @@ export type GetAppsByAppIdAgentComposerCandidatesData = { } export type GetAppsByAppIdAgentComposerCandidatesResponses = { - 200: { - [key: string]: unknown - } + 200: AgentComposerCandidatesResponse } export type GetAppsByAppIdAgentComposerCandidatesResponse @@ -2116,9 +2230,7 @@ export type PostAppsByAppIdAgentComposerValidateData = { } export type PostAppsByAppIdAgentComposerValidateResponses = { - 200: { - [key: string]: unknown - } + 200: AgentComposerValidateResponse } export type PostAppsByAppIdAgentComposerValidateResponse @@ -4515,9 +4627,7 @@ export type GetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerData = { } export type GetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerResponses = { - 200: { - [key: string]: unknown - } + 200: WorkflowAgentComposerResponse } export type GetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerResponse @@ -4534,9 +4644,7 @@ export type PutAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerData = { } export type PutAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerResponses = { - 200: { - [key: string]: unknown - } + 200: WorkflowAgentComposerResponse } export type PutAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerResponse @@ -4553,16 +4661,14 @@ export type GetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerCandidatesData } export type GetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerCandidatesResponses = { - 200: { - [key: string]: unknown - } + 200: AgentComposerCandidatesResponse } export type GetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerCandidatesResponse = GetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerCandidatesResponses[keyof GetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerCandidatesResponses] export type PostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactData = { - body?: never + body: ComposerSavePayload path: { app_id: string node_id: string @@ -4572,9 +4678,7 @@ export type PostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactData = } export type PostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactResponses = { - 200: { - [key: string]: unknown - } + 200: AgentComposerImpactResponse } export type PostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactResponse @@ -4591,9 +4695,7 @@ export type PostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerSaveToRosterD } export type PostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerSaveToRosterResponses = { - 200: { - [key: string]: unknown - } + 200: WorkflowAgentComposerResponse } export type PostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerSaveToRosterResponse @@ -4610,9 +4712,7 @@ export type PostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerValidateData } export type PostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerValidateResponses = { - 200: { - [key: string]: unknown - } + 200: AgentComposerValidateResponse } export type PostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerValidateResponse diff --git a/packages/contracts/generated/api/console/apps/zod.gen.ts b/packages/contracts/generated/api/console/apps/zod.gen.ts index 2016a802cf..0a81aebc61 100644 --- a/packages/contracts/generated/api/console/apps/zod.gen.ts +++ b/packages/contracts/generated/api/console/apps/zod.gen.ts @@ -77,6 +77,14 @@ export const zAdvancedChatWorkflowRunPayload = z.object({ query: z.string().optional().default(''), }) +/** + * AgentComposerValidateResponse + */ +export const zAgentComposerValidateResponse = z.object({ + errors: z.array(z.string()).optional(), + result: z.string(), +}) + /** * AnnotationReplyPayload */ @@ -730,12 +738,16 @@ export const zSite = z.object({ }) /** - * ComposerBindingPayload + * AgentConfigSnapshotSummaryResponse */ -export const zComposerBindingPayload = z.object({ +export const zAgentConfigSnapshotSummaryResponse = z.object({ agent_id: z.string().nullish(), - binding_type: z.enum(['inline_agent', 'roster_agent']), - current_snapshot_id: z.string().nullish(), + created_at: z.string().nullish(), + created_by: z.string().nullish(), + id: z.string(), + summary: z.string().nullish(), + version: z.int(), + version_note: z.string().nullish(), }) /** @@ -749,6 +761,15 @@ export const zComposerSaveStrategy = z.enum([ 'save_to_roster', ]) +/** + * ComposerBindingPayload + */ +export const zComposerBindingPayload = z.object({ + agent_id: z.string().nullish(), + binding_type: z.enum(['inline_agent', 'roster_agent']), + current_snapshot_id: z.string().nullish(), +}) + /** * ComposerSoulLockPayload */ @@ -762,6 +783,24 @@ export const zComposerSoulLockPayload = z.object({ */ export const zComposerVariant = z.enum(['agent_app', 'workflow']) +/** + * AgentComposerSoulCandidatesResponse + */ +export const zAgentComposerSoulCandidatesResponse = z.object({ + cli_tools: z.array(z.record(z.string(), z.unknown())).optional(), + dify_tools: z.array(z.record(z.string(), z.unknown())).optional(), + human_contacts: z.array(z.record(z.string(), z.unknown())).optional(), + knowledge_datasets: z.array(z.record(z.string(), z.unknown())).optional(), + skills_files: z.array(z.record(z.string(), z.unknown())).optional(), +}) + +/** + * ComposerCandidateCapabilities + */ +export const zComposerCandidateCapabilities = z.object({ + human_roster_available: z.boolean().optional().default(false), +}) + /** * AnnotationHitHistory */ @@ -1235,6 +1274,33 @@ export const zWorkflowPaginationResponse = z.object({ page: z.int(), }) +/** + * AgentComposerSoulLockResponse + */ +export const zAgentComposerSoulLockResponse = z.object({ + can_unlock: z.boolean().optional().default(false), + locked: z.boolean(), + reason: z.string().nullish(), +}) + +/** + * AgentComposerImpactBindingResponse + */ +export const zAgentComposerImpactBindingResponse = z.object({ + app_id: z.string(), + node_id: z.string(), + workflow_id: z.string(), +}) + +/** + * AgentComposerImpactResponse + */ +export const zAgentComposerImpactResponse = z.object({ + bindings: z.array(zAgentComposerImpactBindingResponse).optional(), + current_snapshot_id: z.string().nullish(), + workflow_node_count: z.int(), +}) + export const zWorkflowDraftVariableWithoutValue = z.object({ description: z.string().optional(), edited: z.boolean().optional(), @@ -1475,6 +1541,32 @@ export const zWorkflowOnlineUsersResponse = z.object({ data: z.array(zWorkflowOnlineUsersByApp), }) +/** + * AgentScope + * + * Visibility and lifecycle scope of an Agent record. + */ +export const zAgentScope = z.enum(['roster', 'workflow_only']) + +/** + * AgentStatus + * + * Soft lifecycle state for Agent records. + */ +export const zAgentStatus = z.enum(['active', 'archived']) + +/** + * AgentComposerAgentResponse + */ +export const zAgentComposerAgentResponse = z.object({ + active_config_snapshot_id: z.string().nullish(), + description: z.string(), + id: z.string(), + name: z.string(), + scope: zAgentScope, + status: zAgentStatus, +}) + /** * AppVariableConfig */ @@ -1538,6 +1630,37 @@ export const zAgentSoulSkillsFilesConfig = z.object({ */ export const zWorkflowNodeJobMode = z.enum(['let_agent_figure_it_out', 'tell_agent_what_to_do']) +/** + * DeclaredOutputType + */ +export const zDeclaredOutputType = z.enum([ + 'array', + 'boolean', + 'file', + 'number', + 'object', + 'string', +]) + +/** + * AgentComposerNodeJobCandidatesResponse + */ +export const zAgentComposerNodeJobCandidatesResponse = z.object({ + declare_output_types: z.array(zDeclaredOutputType).optional(), + human_contacts: z.array(z.record(z.string(), z.unknown())).optional(), + previous_node_outputs: z.array(z.record(z.string(), z.unknown())).optional(), +}) + +/** + * AgentComposerCandidatesResponse + */ +export const zAgentComposerCandidatesResponse = z.object({ + allowed_node_job_candidates: zAgentComposerNodeJobCandidatesResponse.optional(), + allowed_soul_candidates: zAgentComposerSoulCandidatesResponse.optional(), + capabilities: zComposerCandidateCapabilities.optional(), + variant: zComposerVariant, +}) + /** * SimpleModelConfig */ @@ -1725,6 +1848,63 @@ export const zWorkflowArchivedLogPaginationResponse = z.object({ total: z.int(), }) +/** + * WorkflowAgentBindingType + * + * How a workflow node is bound to an Agent. + */ +export const zWorkflowAgentBindingType = z.enum(['inline_agent', 'roster_agent']) + +/** + * AgentComposerBindingResponse + */ +export const zAgentComposerBindingResponse = z.object({ + agent_id: z.string().nullish(), + binding_type: zWorkflowAgentBindingType, + current_snapshot_id: z.string().nullish(), + id: z.string(), + node_id: z.string(), + workflow_id: z.string(), +}) + +/** + * DeclaredArrayItem + * + * Per-item shape for an ``array``-typed declared output. + * + * PRD §OUTPUT 配置框 keeps arrays one level deep on first version; nested arrays + * are rejected so the runtime type checker and JSON Schema stay easy to reason + * about. Stage 4 §4.2. + */ +export const zDeclaredArrayItem = z.object({ + description: z.string().nullish(), + type: zDeclaredOutputType, +}) + +/** + * DeclaredOutputCheckConfig + * + * File-output content check via a model-based comparison against a benchmark file. + * + * Per PRD §OUTPUT 配置框, output check is **file-only** and optional. Stage 4 §4.3. + */ +export const zDeclaredOutputCheckConfig = z.object({ + benchmark_file_ref: z.record(z.string(), z.unknown()).nullish(), + enabled: z.boolean().optional().default(false), + model_ref: z.record(z.string(), z.unknown()).nullish(), + prompt: z.string().nullish(), +}) + +/** + * DeclaredOutputFileConfig + * + * File-type output metadata. Both lists empty means "any file accepted". + */ +export const zDeclaredOutputFileConfig = z.object({ + extensions: z.array(z.string()).optional(), + mime_types: z.array(z.string()).optional(), +}) + /** * AgentKnowledgeQueryMode */ @@ -1763,124 +1943,8 @@ export const zAgentSoulModelConfig = z.object({ plugin_id: z.string().min(1).max(255), }) -/** - * DeclaredOutputCheckConfig - * - * File-output content check via a model-based comparison against a benchmark file. - * - * Per PRD §OUTPUT 配置框, output check is **file-only** and optional. Stage 4 §4.3. - */ -export const zDeclaredOutputCheckConfig = z.object({ - benchmark_file_ref: z.record(z.string(), z.unknown()).nullish(), - enabled: z.boolean().optional().default(false), - model_ref: z.record(z.string(), z.unknown()).nullish(), - prompt: z.string().nullish(), -}) - -/** - * DeclaredOutputFileConfig - * - * File-type output metadata. Both lists empty means "any file accepted". - */ -export const zDeclaredOutputFileConfig = z.object({ - extensions: z.array(z.string()).optional(), - mime_types: z.array(z.string()).optional(), -}) - -/** - * DeclaredOutputType - */ -export const zDeclaredOutputType = z.enum([ - 'array', - 'boolean', - 'file', - 'number', - 'object', - 'string', -]) - -/** - * DeclaredArrayItem - * - * Per-item shape for an ``array``-typed declared output. - * - * PRD §OUTPUT 配置框 keeps arrays one level deep on first version; nested arrays - * are rejected so the runtime type checker and JSON Schema stay easy to reason - * about. Stage 4 §4.2. - */ -export const zDeclaredArrayItem = z.object({ - description: z.string().nullish(), - type: zDeclaredOutputType, -}) - export const zFormInputConfig = z.unknown() -/** - * AgentSoulDifyToolCredentialRef - * - * Reference to a stored Dify Plugin Tool credential. - * - * Secret values are resolved only at runtime. The legacy ``credential_id`` - * field is accepted by :class:`AgentSoulDifyToolConfig` and normalized here so - * old Agent tool payloads can be read while new payloads stay explicit. - */ -export const zAgentSoulDifyToolCredentialRef = z.object({ - id: z.string().max(255).nullish(), - provider: z.string().max(255).nullish(), - type: z.enum(['provider', 'tool']).optional().default('tool'), -}) - -/** - * AgentSoulDifyToolConfig - * - * One Dify Plugin Tool configured on Agent Soul. - * - * The API backend prepares this persisted product shape into - * ``DifyPluginToolConfig`` before sending a run request to Agent backend. - * ``provider_id`` keeps compatibility with existing Agent tool config payloads; - * new callers should send ``plugin_id`` + ``provider`` when available. - */ -export const zAgentSoulDifyToolConfig = z.object({ - credential_ref: zAgentSoulDifyToolCredentialRef.optional(), - credential_type: z.enum(['api-key', 'oauth2', 'unauthorized']).optional().default('api-key'), - description: z.string().nullish(), - enabled: z.boolean().optional().default(true), - name: z.string().max(255).nullish(), - plugin_id: z.string().max(255).nullish(), - provider: z.string().max(255).nullish(), - provider_id: z.string().max(255).nullish(), - provider_type: z.string().optional().default('plugin'), - runtime_parameters: z.record(z.string(), z.unknown()).optional(), - tool_name: z.string().min(1).max(255), -}) - -/** - * AgentSoulToolsConfig - */ -export const zAgentSoulToolsConfig = z.object({ - cli_tools: z.array(z.record(z.string(), z.unknown())).optional(), - dify_tools: z.array(zAgentSoulDifyToolConfig).optional(), -}) - -/** - * AgentSoulConfig - */ -export const zAgentSoulConfig = z.object({ - app_features: z.record(z.string(), z.unknown()).optional(), - app_variables: z.array(zAppVariableConfig).optional(), - env: zAgentSoulEnvConfig.optional(), - human: zAgentSoulHumanConfig.optional(), - knowledge: zAgentSoulKnowledgeConfig.optional(), - memory: zAgentSoulMemoryConfig.optional(), - misc_legacy: z.record(z.string(), z.unknown()).optional(), - model: zAgentSoulModelConfig.optional(), - prompt: zAgentSoulPromptConfig.optional(), - sandbox: zAgentSoulSandboxConfig.optional(), - schema_version: z.int().optional().default(1), - skills_files: zAgentSoulSkillsFilesConfig.optional(), - tools: zAgentSoulToolsConfig.optional(), -}) - /** * OutputErrorStrategy * @@ -1951,6 +2015,83 @@ export const zWorkflowNodeJobConfig = z.object({ workflow_prompt: z.string().optional().default(''), }) +/** + * AgentSoulDifyToolCredentialRef + * + * Reference to a stored Dify Plugin Tool credential. + * + * Secret values are resolved only at runtime. The legacy ``credential_id`` + * field is accepted by :class:`AgentSoulDifyToolConfig` and normalized here so + * old Agent tool payloads can be read while new payloads stay explicit. + */ +export const zAgentSoulDifyToolCredentialRef = z.object({ + id: z.string().max(255).nullish(), + provider: z.string().max(255).nullish(), + type: z.enum(['provider', 'tool']).optional().default('tool'), +}) + +/** + * AgentSoulDifyToolConfig + * + * One Dify Plugin Tool configured on Agent Soul. + * + * The API backend prepares this persisted product shape into + * ``DifyPluginToolConfig`` before sending a run request to Agent backend. + * ``provider_id`` keeps compatibility with existing Agent tool config payloads; + * new callers should send ``plugin_id`` + ``provider`` when available. + */ +export const zAgentSoulDifyToolConfig = z.object({ + credential_ref: zAgentSoulDifyToolCredentialRef.optional(), + credential_type: z.enum(['api-key', 'oauth2', 'unauthorized']).optional().default('api-key'), + description: z.string().nullish(), + enabled: z.boolean().optional().default(true), + name: z.string().max(255).nullish(), + plugin_id: z.string().max(255).nullish(), + provider: z.string().max(255).nullish(), + provider_id: z.string().max(255).nullish(), + provider_type: z.string().optional().default('plugin'), + runtime_parameters: z.record(z.string(), z.unknown()).optional(), + tool_name: z.string().min(1).max(255), +}) + +/** + * AgentSoulToolsConfig + */ +export const zAgentSoulToolsConfig = z.object({ + cli_tools: z.array(z.record(z.string(), z.unknown())).optional(), + dify_tools: z.array(zAgentSoulDifyToolConfig).optional(), +}) + +/** + * AgentSoulConfig + */ +export const zAgentSoulConfig = z.object({ + app_features: z.record(z.string(), z.unknown()).optional(), + app_variables: z.array(zAppVariableConfig).optional(), + env: zAgentSoulEnvConfig.optional(), + human: zAgentSoulHumanConfig.optional(), + knowledge: zAgentSoulKnowledgeConfig.optional(), + memory: zAgentSoulMemoryConfig.optional(), + misc_legacy: z.record(z.string(), z.unknown()).optional(), + model: zAgentSoulModelConfig.optional(), + prompt: zAgentSoulPromptConfig.optional(), + sandbox: zAgentSoulSandboxConfig.optional(), + schema_version: z.int().optional().default(1), + skills_files: zAgentSoulSkillsFilesConfig.optional(), + tools: zAgentSoulToolsConfig.optional(), +}) + +/** + * AgentAppComposerResponse + */ +export const zAgentAppComposerResponse = z.object({ + active_config_snapshot: zAgentConfigSnapshotSummaryResponse, + agent: zAgentComposerAgentResponse, + agent_soul: zAgentSoulConfig, + save_options: z.array(zComposerSaveStrategy), + variant: z.string(), +}) + /** * ComposerSavePayload */ @@ -1967,6 +2108,25 @@ export const zComposerSavePayload = z.object({ version_note: z.string().nullish(), }) +/** + * WorkflowAgentComposerResponse + */ +export const zWorkflowAgentComposerResponse = z.object({ + active_config_snapshot: zAgentConfigSnapshotSummaryResponse.optional(), + agent: zAgentComposerAgentResponse.optional(), + agent_soul: zAgentSoulConfig, + app_id: z.string().nullish(), + binding: zAgentComposerBindingResponse.optional(), + effective_declared_outputs: z.array(zDeclaredOutputConfig).optional(), + impact_summary: zAgentComposerImpactResponse.optional(), + node_id: z.string().nullish(), + node_job: zWorkflowNodeJobConfig, + save_options: z.array(zComposerSaveStrategy), + soul_lock: zAgentComposerSoulLockResponse, + variant: z.string(), + workflow_id: z.string().nullish(), +}) + /** * ButtonStyle * @@ -2412,9 +2572,9 @@ export const zGetAppsByAppIdAgentComposerPath = z.object({ }) /** - * Success + * Agent app composer state */ -export const zGetAppsByAppIdAgentComposerResponse = z.record(z.string(), z.unknown()) +export const zGetAppsByAppIdAgentComposerResponse = zAgentAppComposerResponse export const zPutAppsByAppIdAgentComposerBody = zComposerSavePayload @@ -2423,18 +2583,18 @@ export const zPutAppsByAppIdAgentComposerPath = z.object({ }) /** - * Success + * Agent app composer saved */ -export const zPutAppsByAppIdAgentComposerResponse = z.record(z.string(), z.unknown()) +export const zPutAppsByAppIdAgentComposerResponse = zAgentAppComposerResponse export const zGetAppsByAppIdAgentComposerCandidatesPath = z.object({ app_id: z.string(), }) /** - * Success + * Agent app composer candidates */ -export const zGetAppsByAppIdAgentComposerCandidatesResponse = z.record(z.string(), z.unknown()) +export const zGetAppsByAppIdAgentComposerCandidatesResponse = zAgentComposerCandidatesResponse export const zPostAppsByAppIdAgentComposerValidateBody = zComposerSavePayload @@ -2443,9 +2603,9 @@ export const zPostAppsByAppIdAgentComposerValidatePath = z.object({ }) /** - * Success + * Agent app composer validation result */ -export const zPostAppsByAppIdAgentComposerValidateResponse = z.record(z.string(), z.unknown()) +export const zPostAppsByAppIdAgentComposerValidateResponse = zAgentComposerValidateResponse export const zGetAppsByAppIdAgentLogsPath = z.object({ app_id: z.string(), @@ -3723,12 +3883,10 @@ export const zGetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerPath = z.obj }) /** - * Success + * Workflow agent composer state */ -export const zGetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerResponse = z.record( - z.string(), - z.unknown(), -) +export const zGetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerResponse + = zWorkflowAgentComposerResponse export const zPutAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerBody = zComposerSavePayload @@ -3738,12 +3896,10 @@ export const zPutAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerPath = z.obj }) /** - * Success + * Workflow agent composer saved */ -export const zPutAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerResponse = z.record( - z.string(), - z.unknown(), -) +export const zPutAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerResponse + = zWorkflowAgentComposerResponse export const zGetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerCandidatesPath = z.object({ app_id: z.string(), @@ -3751,12 +3907,13 @@ export const zGetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerCandidatesPa }) /** - * Success + * Workflow agent composer candidates */ -export const zGetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerCandidatesResponse = z.record( - z.string(), - z.unknown(), -) +export const zGetAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerCandidatesResponse + = zAgentComposerCandidatesResponse + +export const zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactBody + = zComposerSavePayload export const zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactPath = z.object({ app_id: z.string(), @@ -3764,12 +3921,10 @@ export const zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactPath }) /** - * Success + * Workflow agent composer impact */ -export const zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactResponse = z.record( - z.string(), - z.unknown(), -) +export const zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerImpactResponse + = zAgentComposerImpactResponse export const zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerSaveToRosterBody = zComposerSavePayload @@ -3780,10 +3935,10 @@ export const zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerSaveToRoste }) /** - * Success + * Workflow agent composer saved to roster */ export const zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerSaveToRosterResponse - = z.record(z.string(), z.unknown()) + = zWorkflowAgentComposerResponse export const zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerValidateBody = zComposerSavePayload @@ -3794,12 +3949,10 @@ export const zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerValidatePat }) /** - * Success + * Workflow agent composer validation result */ -export const zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerValidateResponse = z.record( - z.string(), - z.unknown(), -) +export const zPostAppsByAppIdWorkflowsDraftNodesByNodeIdAgentComposerValidateResponse + = zAgentComposerValidateResponse export const zGetAppsByAppIdWorkflowsDraftNodesByNodeIdLastRunPath = z.object({ app_id: z.string(),