fix: align agent app backing roster API (#37442)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
zyssyz123 2026-06-15 13:21:38 +08:00 committed by GitHub
parent 12159d6313
commit d21bf291bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 388 additions and 502 deletions

View File

@ -8,10 +8,8 @@ from controllers.common.schema import query_params_from_model, register_response
from controllers.console import console_ns
from controllers.console.wraps import (
account_initialization_required,
edit_permission_required,
setup_required,
with_current_tenant_id,
with_current_user_id,
)
from extensions.ext_database import db
from fields.agent_fields import (
@ -25,7 +23,7 @@ from fields.agent_fields import (
from libs.helper import dump_response
from libs.login import login_required
from services.agent.roster_service import AgentRosterService
from services.entities.agent_entities import RosterAgentCreatePayload, RosterAgentUpdatePayload, RosterListQuery
from services.entities.agent_entities import RosterListQuery
class AgentInviteOptionsQuery(RosterListQuery):
@ -40,8 +38,6 @@ register_schema_models(
console_ns,
AgentInviteOptionsQuery,
AgentIdPath,
RosterAgentCreatePayload,
RosterAgentUpdatePayload,
RosterListQuery,
)
register_response_schema_models(
@ -76,23 +72,6 @@ class AgentRosterListApi(Resource):
),
)
@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
@edit_permission_required
@with_current_user_id
@with_current_tenant_id
def post(self, tenant_id: str, account_id: str):
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 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):
@ -129,34 +108,6 @@ class AgentRosterDetailApi(Resource):
_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
@edit_permission_required
@with_current_user_id
@with_current_tenant_id
def patch(self, tenant_id: str, account_id: str, agent_id: UUID):
payload = RosterAgentUpdatePayload.model_validate(console_ns.payload or {})
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
@edit_permission_required
@with_current_user_id
@with_current_tenant_id
def delete(self, tenant_id: str, account_id: str, agent_id: UUID):
_agent_roster_service().archive_roster_agent(tenant_id=tenant_id, agent_id=str(agent_id), account_id=account_id)
return "", 204
@console_ns.route("/agents/<uuid:agent_id>/versions")
class AgentRosterVersionsApi(Resource):

View File

@ -581,7 +581,7 @@ class AppListApi(Resource):
@console_ns.doc("create_app")
@console_ns.doc(description="Create a new application")
@console_ns.expect(console_ns.models[CreateAppPayload.__name__])
@console_ns.response(201, "App created successfully", console_ns.models[AppDetail.__name__])
@console_ns.response(201, "App created successfully", console_ns.models[AppDetailWithSite.__name__])
@console_ns.response(403, "Insufficient permissions")
@console_ns.response(400, "Invalid request parameters")
@setup_required
@ -605,7 +605,7 @@ class AppListApi(Resource):
app_service = AppService()
app = app_service.create_app(current_tenant_id, params, current_user)
app_detail = AppDetail.model_validate(app, from_attributes=True)
app_detail = AppDetailWithSite.model_validate(app, from_attributes=True)
return app_detail.model_dump(mode="json"), 201

View File

@ -38,6 +38,8 @@ class AgentScope(StrEnum):
class AgentSource(StrEnum):
"""Origin that created or imported the Agent."""
# Created directly as a reusable Agent Roster asset.
ROSTER = "roster"
# Created from an Agent App composer.
AGENT_APP = "agent_app"
# Created from a Workflow Agent Composer flow.

View File

@ -308,19 +308,6 @@ Check if activation token is valid
| ---- | ----------- | ------ |
| 200 | Agent roster list | **application/json**: [AgentRosterListResponse](#agentrosterlistresponse)<br> |
### [POST] /agents
#### Request Body
| Required | Schema |
| -------- | ------ |
| Yes | **application/json**: [RosterAgentCreatePayload](#rosteragentcreatepayload)<br> |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 201 | Agent created | **application/json**: [AgentRosterResponse](#agentrosterresponse)<br> |
### [GET] /agents/invite-options
#### Parameters
@ -337,19 +324,6 @@ Check if activation token is valid
| ---- | ----------- | ------ |
| 200 | Agent invite options | **application/json**: [AgentInviteOptionsResponse](#agentinviteoptionsresponse)<br> |
### [DELETE] /agents/{agent_id}
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| agent_id | path | | Yes | string |
#### Responses
| Code | Description |
| ---- | ----------- |
| 204 | Agent archived |
### [GET] /agents/{agent_id}
#### Parameters
@ -363,25 +337,6 @@ Check if activation token is valid
| ---- | ----------- | ------ |
| 200 | Agent detail | **application/json**: [AgentRosterResponse](#agentrosterresponse)<br> |
### [PATCH] /agents/{agent_id}
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| agent_id | path | | Yes | string |
#### Request Body
| Required | Schema |
| -------- | ------ |
| Yes | **application/json**: [RosterAgentUpdatePayload](#rosteragentupdatepayload)<br> |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Agent updated | **application/json**: [AgentRosterResponse](#agentrosterresponse)<br> |
### [GET] /agents/{agent_id}/versions
#### Parameters
@ -599,7 +554,7 @@ Create a new application
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 201 | App created successfully | **application/json**: [AppDetail](#appdetail)<br> |
| 201 | App created successfully | **application/json**: [AppDetailWithSite](#appdetailwithsite)<br> |
| 400 | Invalid request parameters | |
| 403 | Insufficient permissions | |
@ -16965,30 +16920,6 @@ Model class for provider quota configuration.
| summary | string | | No |
| word_count | integer | | No |
#### RosterAgentCreatePayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| agent_soul | [AgentSoulConfig](#agentsoulconfig) | | No |
| description | string | | No |
| icon | string | | No |
| icon_background | string | | No |
| icon_type | [AgentIconType](#agenticontype) | | No |
| name | string | | Yes |
| role | string | | No |
| version_note | string | | No |
#### RosterAgentUpdatePayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| description | string | | No |
| icon | string | | No |
| icon_background | string | | No |
| icon_type | [AgentIconType](#agenticontype) | | No |
| name | string | | No |
| role | string | | No |
#### RosterListQuery
| Name | Type | Description | Required |

View File

@ -213,7 +213,7 @@ class AgentRosterService:
tenant_id: str,
account_id: str,
payload: RosterAgentCreatePayload,
source: AgentSource = AgentSource.AGENT_APP,
source: AgentSource = AgentSource.ROSTER,
) -> Agent:
ComposerConfigValidator.validate_agent_soul(payload.agent_soul)

View File

@ -1,6 +1,7 @@
import json
import logging
from collections.abc import Sequence
from datetime import datetime
from typing import Any, Literal, TypedDict, cast, override
import sqlalchemy as sa
@ -23,7 +24,7 @@ from graphon.model_runtime.model_providers.base.large_language_model import Larg
from libs.datetime_utils import naive_utc_now
from libs.login import current_user
from models import Account
from models.agent import AgentIconType
from models.agent import Agent, AgentIconType, AgentScope, AgentSource, AgentStatus
from models.model import App, AppMode, AppModelConfig, IconType, Site
from models.tools import ApiToolProvider
from services.billing_service import BillingService
@ -377,6 +378,62 @@ class AppService:
use_icon_as_answer_icon: bool
max_active_requests: int
@staticmethod
def _get_backing_agent_for_update(app: App) -> Agent | None:
if app.mode != AppMode.AGENT:
return None
return db.session.scalar(
select(Agent).where(
Agent.tenant_id == app.tenant_id,
Agent.app_id == app.id,
Agent.scope == AgentScope.ROSTER,
Agent.source == AgentSource.AGENT_APP,
Agent.status == AgentStatus.ACTIVE,
)
)
@staticmethod
def _to_agent_icon_type(icon_type: IconType | str | None) -> AgentIconType | None:
if icon_type is None:
return None
value = icon_type.value if isinstance(icon_type, IconType) else icon_type
return AgentIconType(value)
def _sync_backing_agent_identity(
self,
app: App,
*,
name: str | None = None,
description: str | None = None,
icon_type: IconType | str | None = None,
icon: str | None = None,
icon_background: str | None = None,
account_id: str | None = None,
updated_at: datetime | None = None,
) -> None:
"""Keep the Roster identity aligned with its Agent App shell.
Agent Soul remains versioned through Composer. This helper only mirrors
user-facing identity fields so Roster and Agent Console do not drift.
"""
agent = self._get_backing_agent_for_update(app)
if agent is None:
return
if name is not None:
agent.name = name
if description is not None:
agent.description = description
if icon_type is not None:
agent.icon_type = self._to_agent_icon_type(icon_type)
if icon is not None:
agent.icon = icon
if icon_background is not None:
agent.icon_background = icon_background
agent.updated_by = account_id
if updated_at is not None:
agent.updated_at = updated_at
def update_app(self, app: App, args: ArgsDict) -> App:
"""
Update app
@ -400,6 +457,16 @@ class AppService:
app.max_active_requests = args.get("max_active_requests")
app.updated_by = current_user.id
app.updated_at = naive_utc_now()
self._sync_backing_agent_identity(
app,
name=app.name,
description=app.description,
icon_type=app.icon_type,
icon=app.icon,
icon_background=app.icon_background,
account_id=current_user.id,
updated_at=app.updated_at,
)
db.session.commit()
app_was_updated.send(app)
@ -417,6 +484,12 @@ class AppService:
app.name = name
app.updated_by = current_user.id
app.updated_at = naive_utc_now()
self._sync_backing_agent_identity(
app,
name=app.name,
account_id=current_user.id,
updated_at=app.updated_at,
)
db.session.commit()
app_was_updated.send(app)
@ -441,6 +514,14 @@ class AppService:
app.icon_type = icon_type if isinstance(icon_type, IconType) else IconType(icon_type)
app.updated_by = current_user.id
app.updated_at = naive_utc_now()
self._sync_backing_agent_identity(
app,
icon_type=app.icon_type,
icon=app.icon,
icon_background=app.icon_background,
account_id=current_user.id,
updated_at=app.updated_at,
)
db.session.commit()
app_was_updated.send(app)
@ -493,6 +574,16 @@ class AppService:
"""
app_was_deleted.send(app)
backing_agent = self._get_backing_agent_for_update(app)
if backing_agent is not None:
now = naive_utc_now()
account_id = getattr(current_user, "id", None)
backing_agent.status = AgentStatus.ARCHIVED
backing_agent.archived_by = account_id
backing_agent.archived_at = now
backing_agent.updated_by = account_id
backing_agent.updated_at = now
db.session.delete(app)
db.session.commit()

View File

@ -1,6 +1,6 @@
from inspect import unwrap
from types import SimpleNamespace
from typing import Protocol, cast
from typing import cast
import pytest
from flask import Flask
@ -128,10 +128,6 @@ def _get_app_model_modes(view) -> list[AppMode]:
return []
class _PayloadWithDescription(Protocol):
description: object
@pytest.fixture
def account_id() -> str:
return "account-1"
@ -153,27 +149,10 @@ def test_roster_list_get_parses_query_and_calls_service(app: Flask, monkeypatch:
assert captured == {"tenant_id": "tenant-1", "page": 2, "limit": 5, "keyword": "analyst"}
def test_roster_list_post_creates_agent_and_returns_detail(
app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str
) -> None:
created_agent = SimpleNamespace(id="agent-1")
monkeypatch.setattr(
roster_controller.AgentRosterService,
"create_roster_agent",
lambda _self, **kwargs: created_agent,
)
monkeypatch.setattr(
roster_controller.AgentRosterService,
"get_roster_agent_detail",
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(), "tenant-1", account_id)
assert status == 201
assert result["id"] == "agent-1"
assert result["agent_kind"] == "dify_agent"
def test_roster_direct_mutation_endpoints_are_not_exposed() -> None:
assert not hasattr(AgentRosterListApi, "post")
assert not hasattr(AgentRosterDetailApi, "patch")
assert not hasattr(AgentRosterDetailApi, "delete")
def test_invite_options_get_parses_app_id(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
@ -192,30 +171,14 @@ def test_invite_options_get_parses_app_id(app: Flask, monkeypatch: pytest.Monkey
assert captured == {"tenant_id": "tenant-1", "page": 1, "limit": 10, "keyword": None, "app_id": "app-1"}
def test_roster_detail_patch_delete_and_versions_call_services(
app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str
) -> None:
def test_roster_detail_and_versions_call_services(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None:
agent_id = "00000000-0000-0000-0000-000000000001"
version_id = "00000000-0000-0000-0000-000000000002"
archived: dict[str, object] = {}
monkeypatch.setattr(
roster_controller.AgentRosterService,
"get_roster_agent_detail",
lambda _self, **kwargs: _agent_response(cast(str, kwargs["agent_id"])),
)
monkeypatch.setattr(
roster_controller.AgentRosterService,
"update_roster_agent",
lambda _self, **kwargs: {
**_agent_response(cast(str, kwargs["agent_id"])),
"description": cast(_PayloadWithDescription, kwargs["payload"]).description,
},
)
monkeypatch.setattr(
roster_controller.AgentRosterService,
"archive_roster_agent",
lambda _self, **kwargs: archived.update(kwargs),
)
monkeypatch.setattr(
roster_controller.AgentRosterService,
"list_agent_versions",
@ -245,13 +208,6 @@ def test_roster_detail_patch_delete_and_versions_call_services(
)
assert unwrap(AgentRosterDetailApi.get)(AgentRosterDetailApi(), "tenant-1", agent_id)["id"] == agent_id
with app.test_request_context(json={"description": "updated"}):
assert (
unwrap(AgentRosterDetailApi.patch)(AgentRosterDetailApi(), "tenant-1", account_id, agent_id)["description"]
== "updated"
)
assert unwrap(AgentRosterDetailApi.delete)(AgentRosterDetailApi(), "tenant-1", account_id, agent_id) == ("", 204)
assert archived["account_id"] == "account-1"
assert (
unwrap(AgentRosterVersionsApi.get)(AgentRosterVersionsApi(), "tenant-1", agent_id)["data"][0]["id"]
== "version-1"

View File

@ -314,6 +314,39 @@ def test_app_list_query_rejects_flat_tag_ids(app_module):
app_module.AppListQuery.model_validate(normalized)
def test_create_agent_app_response_includes_bound_agent_id(app_module, monkeypatch: pytest.MonkeyPatch):
payload = {"name": "Iris", "mode": "agent", "description": "Agent app"}
app_obj = SimpleNamespace(
id="app-1",
name="Iris",
description="Agent app",
mode_compatible_with_agent="agent",
icon_type="emoji",
icon="robot",
icon_background="#fff",
enable_site=False,
enable_api=False,
created_at=_ts(),
updated_at=_ts(),
bound_agent_id="agent-1",
)
app_service = MagicMock()
app_service.create_app.return_value = app_obj
monkeypatch.setattr(app_module, "AppService", lambda: app_service)
app_module.console_ns.payload = payload
try:
response, status = _unwrap(app_module.AppListApi().post)("tenant-1", SimpleNamespace(id="account-1"))
finally:
app_module.console_ns.payload = None
assert status == 201
assert response["id"] == "app-1"
assert response["bound_agent_id"] == "agent-1"
created_params = app_service.create_app.call_args.args[1]
assert created_params.mode == "agent"
def test_app_partial_serialization_uses_aliases(app_models):
AppPartial = app_models.AppPartial
created_at = _ts()
@ -389,6 +422,7 @@ def test_app_detail_with_site_includes_nested_serialization(app_models):
max_active_requests=5,
deleted_tools=[{"type": "api", "tool_name": "search", "provider_id": "prov"}],
site=site,
bound_agent_id="agent-1",
)
serialized = AppDetailWithSite.model_validate(app_obj, from_attributes=True).model_dump(mode="json")
@ -398,6 +432,7 @@ def test_app_detail_with_site_includes_nested_serialization(app_models):
assert serialized["deleted_tools"][0]["tool_name"] == "search"
assert serialized["site"]["icon_url"] == "signed:site-icon"
assert serialized["site"]["created_at"] == int(timestamp.timestamp())
assert serialized["bound_agent_id"] == "agent-1"
def test_app_pagination_aliases_per_page_and_has_next(app_models):

View File

@ -27,6 +27,7 @@ def test_agent_enums_match_prd_boundaries():
assert AgentIconType.EMOJI.value == "emoji"
assert AgentScope.ROSTER.value == "roster"
assert AgentScope.WORKFLOW_ONLY.value == "workflow_only"
assert AgentSource.ROSTER.value == "roster"
assert AgentSource.AGENT_APP.value == "agent_app"
assert AgentSource.WORKFLOW.value == "workflow"
assert AgentStatus.ACTIVE.value == "active"

View File

@ -787,6 +787,7 @@ def test_roster_create_detail_and_lookup_helpers(monkeypatch):
assert service._load_versions_by_id([]) == {}
assert created.name == "Analyst"
assert created.source == AgentSource.ROSTER
assert created.active_config_snapshot_id is not None
assert created.active_config_has_model is False
assert backing_agent.active_config_snapshot_id is not None

View File

@ -1,5 +1,6 @@
from __future__ import annotations
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
from models.model import App
@ -155,3 +156,83 @@ class TestAgentAppType:
app = App()
app.mode = AppMode.CHAT
assert app.bound_agent_id is None
def test_update_agent_app_syncs_backing_agent_identity(self):
from models.agent import AgentIconType
from models.model import AppMode, IconType
from services.app_service import AppService
app = SimpleNamespace(
id="app-1",
tenant_id="tenant-1",
mode=AppMode.AGENT,
name="Old",
description="old",
icon_type=IconType.EMOJI,
icon="robot",
icon_background="#fff",
use_icon_as_answer_icon=False,
max_active_requests=None,
created_by="account-1",
)
backing_agent = SimpleNamespace(
name="Old",
description="old",
icon_type=AgentIconType.EMOJI,
icon="robot",
icon_background="#fff",
updated_by=None,
updated_at=None,
)
with (
patch("services.app_service.db") as mock_db,
patch("services.app_service.current_user", SimpleNamespace(id="account-2")),
):
mock_db.session.scalar.return_value = backing_agent
updated_app = AppService().update_app(
app, # type: ignore[arg-type]
{
"name": "Iris",
"description": "agent app",
"icon_type": "image",
"icon": "file-id",
"icon_background": "#123456",
"use_icon_as_answer_icon": False,
"max_active_requests": 0,
},
)
assert updated_app.name == "Iris"
assert backing_agent.name == "Iris"
assert backing_agent.description == "agent app"
assert backing_agent.icon_type == AgentIconType.IMAGE
assert backing_agent.icon == "file-id"
assert backing_agent.icon_background == "#123456"
assert backing_agent.updated_by == "account-2"
assert backing_agent.updated_at == updated_app.updated_at
def test_delete_agent_app_archives_backing_agent(self):
from models.agent import AgentStatus
from models.model import AppMode
from services.app_service import AppService
app = SimpleNamespace(id="app-1", tenant_id="tenant-1", mode=AppMode.AGENT)
backing_agent = SimpleNamespace(status=AgentStatus.ACTIVE, archived_by=None, archived_at=None)
with (
patch("services.app_service.db") as mock_db,
patch("services.app_service.current_user", SimpleNamespace(id="account-2")),
patch("services.app_service.BillingService"),
patch("services.app_service.EnterpriseService"),
patch("services.app_service.FeatureService"),
patch("services.app_service.dify_config"),
patch("services.app_service.remove_app_and_related_data_task"),
):
mock_db.session.scalar.return_value = backing_agent
AppService().delete_app(app) # type: ignore[arg-type]
assert backing_agent.status == AgentStatus.ARCHIVED
assert backing_agent.archived_by == "account-2"
assert backing_agent.archived_at is not None
mock_db.session.delete.assert_called_once_with(app)

View File

@ -4,8 +4,6 @@ import { oc } from '@orpc/contract'
import * as z from 'zod'
import {
zDeleteAgentsByAgentIdPath,
zDeleteAgentsByAgentIdResponse,
zGetAgentsByAgentIdPath,
zGetAgentsByAgentIdResponse,
zGetAgentsByAgentIdVersionsByVersionIdPath,
@ -16,11 +14,6 @@ import {
zGetAgentsInviteOptionsResponse,
zGetAgentsQuery,
zGetAgentsResponse,
zPatchAgentsByAgentIdBody,
zPatchAgentsByAgentIdPath,
zPatchAgentsByAgentIdResponse,
zPostAgentsBody,
zPostAgentsResponse,
} from './zod.gen'
export const get = oc
@ -69,18 +62,6 @@ export const versions = {
byVersionId,
}
export const delete_ = oc
.route({
inputStructure: 'detailed',
method: 'DELETE',
operationId: 'deleteAgentsByAgentId',
path: '/agents/{agent_id}',
successStatus: 204,
tags: ['console'],
})
.input(z.object({ params: zDeleteAgentsByAgentIdPath }))
.output(zDeleteAgentsByAgentIdResponse)
export const get4 = oc
.route({
inputStructure: 'detailed',
@ -92,21 +73,8 @@ export const get4 = oc
.input(z.object({ params: zGetAgentsByAgentIdPath }))
.output(zGetAgentsByAgentIdResponse)
export const patch = oc
.route({
inputStructure: 'detailed',
method: 'PATCH',
operationId: 'patchAgentsByAgentId',
path: '/agents/{agent_id}',
tags: ['console'],
})
.input(z.object({ body: zPatchAgentsByAgentIdBody, params: zPatchAgentsByAgentIdPath }))
.output(zPatchAgentsByAgentIdResponse)
export const byAgentId = {
delete: delete_,
get: get4,
patch,
versions,
}
@ -121,21 +89,8 @@ export const get5 = oc
.input(z.object({ query: zGetAgentsQuery.optional() }))
.output(zGetAgentsResponse)
export const post = oc
.route({
inputStructure: 'detailed',
method: 'POST',
operationId: 'postAgents',
path: '/agents',
successStatus: 201,
tags: ['console'],
})
.input(z.object({ body: zPostAgentsBody }))
.output(zPostAgentsResponse)
export const agents = {
get: get5,
post,
inviteOptions,
byAgentId,
}

View File

@ -12,15 +12,12 @@ export type AgentRosterListResponse = {
total: number
}
export type RosterAgentCreatePayload = {
agent_soul?: AgentSoulConfig
description?: string
icon?: string | null
icon_background?: string | null
icon_type?: AgentIconType | null
name: string
role?: string
version_note?: string | null
export type AgentInviteOptionsResponse = {
data: Array<AgentInviteOptionResponse>
has_more: boolean
limit: number
page: number
total: number
}
export type AgentRosterResponse = {
@ -51,23 +48,6 @@ export type AgentRosterResponse = {
workflow_node_id?: string | null
}
export type AgentInviteOptionsResponse = {
data: Array<AgentInviteOptionResponse>
has_more: boolean
limit: number
page: number
total: number
}
export type RosterAgentUpdatePayload = {
description?: string | null
icon?: string | null
icon_background?: string | null
icon_type?: AgentIconType | null
name?: string | null
role?: string | null
}
export type AgentConfigSnapshotListResponse = {
data: Array<AgentConfigSnapshotSummaryResponse>
}
@ -84,51 +64,6 @@ export type AgentConfigSnapshotDetailResponse = {
version_note?: string | null
}
export type AgentSoulConfig = {
app_features?: AgentSoulAppFeaturesConfig
app_variables?: Array<AppVariableConfig>
env?: AgentSoulEnvConfig
human?: AgentSoulHumanConfig
knowledge?: AgentSoulKnowledgeConfig
memory?: AgentSoulMemoryConfig
misc_legacy?: AgentSoulAppFeaturesConfig
model?: AgentSoulModelConfig | null
prompt?: AgentSoulPromptConfig
sandbox?: AgentSoulSandboxConfig
schema_version?: number
skills_files?: AgentSoulSkillsFilesConfig
tools?: AgentSoulToolsConfig
}
export type AgentIconType = 'emoji' | 'image' | 'link'
export type AgentConfigSnapshotSummaryResponse = {
agent_id?: string | null
created_at?: number | null
created_by?: string | null
id: string
summary?: string | null
version: number
version_note?: string | null
}
export type AgentKind = 'dify_agent'
export type AgentPublishedReferenceResponse = {
app_id: string
app_mode: string
app_name: string
node_ids?: Array<string>
workflow_id: string
workflow_version: string
}
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 | null
active_config_snapshot_id?: string | null
@ -160,6 +95,51 @@ export type AgentInviteOptionResponse = {
workflow_node_id?: string | null
}
export type AgentConfigSnapshotSummaryResponse = {
agent_id?: string | null
created_at?: number | null
created_by?: string | null
id: string
summary?: string | null
version: number
version_note?: string | null
}
export type AgentKind = 'dify_agent'
export type AgentIconType = 'emoji' | 'image' | 'link'
export type AgentPublishedReferenceResponse = {
app_id: string
app_mode: string
app_name: string
node_ids?: Array<string>
workflow_id: string
workflow_version: string
}
export type AgentScope = 'roster' | 'workflow_only'
export type AgentSource = 'agent_app' | 'imported' | 'roster' | 'system' | 'workflow'
export type AgentStatus = 'active' | 'archived'
export type AgentSoulConfig = {
app_features?: AgentSoulAppFeaturesConfig
app_variables?: Array<AppVariableConfig>
env?: AgentSoulEnvConfig
human?: AgentSoulHumanConfig
knowledge?: AgentSoulKnowledgeConfig
memory?: AgentSoulMemoryConfig
misc_legacy?: AgentSoulAppFeaturesConfig
model?: AgentSoulModelConfig | null
prompt?: AgentSoulPromptConfig
sandbox?: AgentSoulSandboxConfig
schema_version?: number
skills_files?: AgentSoulSkillsFilesConfig
tools?: AgentSoulToolsConfig
}
export type AgentConfigRevisionResponse = {
created_at?: number | null
created_by?: string | null
@ -538,19 +518,6 @@ export type GetAgentsResponses = {
export type GetAgentsResponse = GetAgentsResponses[keyof GetAgentsResponses]
export type PostAgentsData = {
body: RosterAgentCreatePayload
path?: never
query?: never
url: '/agents'
}
export type PostAgentsResponses = {
201: AgentRosterResponse
}
export type PostAgentsResponse = PostAgentsResponses[keyof PostAgentsResponses]
export type GetAgentsInviteOptionsData = {
body?: never
path?: never
@ -570,22 +537,6 @@ export type GetAgentsInviteOptionsResponses = {
export type GetAgentsInviteOptionsResponse
= GetAgentsInviteOptionsResponses[keyof GetAgentsInviteOptionsResponses]
export type DeleteAgentsByAgentIdData = {
body?: never
path: {
agent_id: string
}
query?: never
url: '/agents/{agent_id}'
}
export type DeleteAgentsByAgentIdResponses = {
204: void
}
export type DeleteAgentsByAgentIdResponse
= DeleteAgentsByAgentIdResponses[keyof DeleteAgentsByAgentIdResponses]
export type GetAgentsByAgentIdData = {
body?: never
path: {
@ -602,22 +553,6 @@ export type GetAgentsByAgentIdResponses = {
export type GetAgentsByAgentIdResponse
= GetAgentsByAgentIdResponses[keyof GetAgentsByAgentIdResponses]
export type PatchAgentsByAgentIdData = {
body: RosterAgentUpdatePayload
path: {
agent_id: string
}
query?: never
url: '/agents/{agent_id}'
}
export type PatchAgentsByAgentIdResponses = {
200: AgentRosterResponse
}
export type PatchAgentsByAgentIdResponse
= PatchAgentsByAgentIdResponses[keyof PatchAgentsByAgentIdResponses]
export type GetAgentsByAgentIdVersionsData = {
body?: never
path: {

View File

@ -2,25 +2,6 @@
import * as z from 'zod'
/**
* AgentIconType
*
* Supported icon storage formats for Agent roster entries.
*/
export const zAgentIconType = z.enum(['emoji', 'image', 'link'])
/**
* RosterAgentUpdatePayload
*/
export const zRosterAgentUpdatePayload = z.object({
description: z.string().nullish(),
icon: z.string().max(255).nullish(),
icon_background: z.string().max(255).nullish(),
icon_type: zAgentIconType.nullish(),
name: z.string().min(1).max(255).nullish(),
role: z.string().max(255).nullish(),
})
/**
* AgentConfigSnapshotSummaryResponse
*/
@ -51,6 +32,13 @@ export const zAgentConfigSnapshotListResponse = z.object({
*/
export const zAgentKind = z.enum(['dify_agent'])
/**
* AgentIconType
*
* Supported icon storage formats for Agent roster entries.
*/
export const zAgentIconType = z.enum(['emoji', 'image', 'link'])
/**
* AgentPublishedReferenceResponse
*/
@ -75,7 +63,7 @@ export const zAgentScope = z.enum(['roster', 'workflow_only'])
*
* Origin that created or imported the Agent.
*/
export const zAgentSource = z.enum(['agent_app', 'imported', 'system', 'workflow'])
export const zAgentSource = z.enum(['agent_app', 'imported', 'roster', 'system', 'workflow'])
/**
* AgentStatus
@ -688,20 +676,6 @@ export const zAgentSoulConfig = z.object({
tools: zAgentSoulToolsConfig.optional(),
})
/**
* RosterAgentCreatePayload
*/
export const zRosterAgentCreatePayload = z.object({
agent_soul: zAgentSoulConfig.optional(),
description: z.string().optional().default(''),
icon: z.string().max(255).nullish(),
icon_background: z.string().max(255).nullish(),
icon_type: zAgentIconType.nullish(),
name: z.string().min(1).max(255),
role: z.string().max(255).optional().default(''),
version_note: z.string().nullish(),
})
/**
* AgentConfigSnapshotDetailResponse
*/
@ -728,13 +702,6 @@ export const zGetAgentsQuery = z.object({
*/
export const zGetAgentsResponse = zAgentRosterListResponse
export const zPostAgentsBody = zRosterAgentCreatePayload
/**
* Agent created
*/
export const zPostAgentsResponse = zAgentRosterResponse
export const zGetAgentsInviteOptionsQuery = z.object({
app_id: z.string().optional(),
keyword: z.string().optional(),
@ -747,15 +714,6 @@ export const zGetAgentsInviteOptionsQuery = z.object({
*/
export const zGetAgentsInviteOptionsResponse = zAgentInviteOptionsResponse
export const zDeleteAgentsByAgentIdPath = z.object({
agent_id: z.string(),
})
/**
* Agent archived
*/
export const zDeleteAgentsByAgentIdResponse = z.void()
export const zGetAgentsByAgentIdPath = z.object({
agent_id: z.string(),
})
@ -765,17 +723,6 @@ export const zGetAgentsByAgentIdPath = z.object({
*/
export const zGetAgentsByAgentIdResponse = zAgentRosterResponse
export const zPatchAgentsByAgentIdBody = zRosterAgentUpdatePayload
export const zPatchAgentsByAgentIdPath = z.object({
agent_id: z.string(),
})
/**
* Agent updated
*/
export const zPatchAgentsByAgentIdResponse = zAgentRosterResponse
export const zGetAgentsByAgentIdVersionsPath = z.object({
agent_id: z.string(),
})

View File

@ -21,19 +21,25 @@ export type CreateAppPayload = {
name: string
}
export type AppDetail = {
export type AppDetailWithSite = {
access_mode?: string | null
api_base_url?: string | null
app_model_config?: ModelConfig | null
bound_agent_id?: string | null
created_at?: number | null
created_by?: string | null
deleted_tools?: Array<DeletedTool>
description?: string | null
enable_api: boolean
enable_site: boolean
icon?: string | null
icon_background?: string | null
icon_type?: string | null
id: string
max_active_requests?: number | null
mode_compatible_with_agent: string
name: string
site?: Site | null
tags?: Array<Tag>
tracing?: JsonValue | null
updated_at?: number | null
@ -76,33 +82,6 @@ export type WorkflowOnlineUsersResponse = {
data: Array<WorkflowOnlineUsersByApp>
}
export type AppDetailWithSite = {
access_mode?: string | null
api_base_url?: string | null
app_model_config?: ModelConfig | null
bound_agent_id?: string | null
created_at?: number | null
created_by?: string | null
deleted_tools?: Array<DeletedTool>
description?: string | null
enable_api: boolean
enable_site: boolean
icon?: string | null
icon_background?: string | null
icon_type?: string | null
id: string
max_active_requests?: number | null
mode_compatible_with_agent: string
name: string
site?: Site | null
tags?: Array<Tag>
tracing?: JsonValue | null
updated_at?: number | null
updated_by?: string | null
use_icon_as_answer_icon?: boolean | null
workflow?: WorkflowPartial | null
}
export type UpdateAppPayload = {
description?: string | null
icon?: string | null
@ -404,6 +383,27 @@ export type AppApiStatusPayload = {
enable_api: boolean
}
export type AppDetail = {
access_mode?: string | null
app_model_config?: ModelConfig | null
created_at?: number | null
created_by?: string | null
description?: string | null
enable_api: boolean
enable_site: boolean
icon?: string | null
icon_background?: string | null
id: string
mode_compatible_with_agent: string
name: string
tags?: Array<Tag>
tracing?: JsonValue | null
updated_at?: number | null
updated_by?: string | null
use_icon_as_answer_icon?: boolean | null
workflow?: WorkflowPartial | null
}
export type AudioTranscriptResponse = {
text: string
}
@ -1212,6 +1212,29 @@ export type ModelConfig = {
provider: string
}
export type DeletedTool = {
provider_id: string
tool_name: string
type: string
}
export type Site = {
chat_color_theme?: string | null
chat_color_theme_inverted: boolean
copyright?: string | null
custom_disclaimer?: string | null
default_language: string
description?: string | null
icon?: string | null
icon_background?: string | null
icon_type?: string | null
readonly icon_url: string | null
privacy_policy?: string | null
show_workflow_steps: boolean
title: string
use_icon_as_answer_icon: boolean
}
export type Tag = {
id: string
name: string
@ -1250,29 +1273,6 @@ export type WorkflowOnlineUsersByApp = {
users: Array<WorkflowOnlineUser>
}
export type DeletedTool = {
provider_id: string
tool_name: string
type: string
}
export type Site = {
chat_color_theme?: string | null
chat_color_theme_inverted: boolean
copyright?: string | null
custom_disclaimer?: string | null
default_language: string
description?: string | null
icon?: string | null
icon_background?: string | null
icon_type?: string | null
readonly icon_url: string | null
privacy_policy?: string | null
show_workflow_steps: boolean
title: string
use_icon_as_answer_icon: boolean
}
export type AdvancedChatWorkflowRunForListResponse = {
conversation_id?: string | null
created_at?: number | null
@ -2707,7 +2707,7 @@ export type PostAppsErrors = {
}
export type PostAppsResponses = {
201: AppDetail
201: AppDetailWithSite
}
export type PostAppsResponse = PostAppsResponses[keyof PostAppsResponses]

View File

@ -834,6 +834,35 @@ export const zAppIconPayload = z.object({
icon_type: zIconType.nullish(),
})
/**
* DeletedTool
*/
export const zDeletedTool = z.object({
provider_id: z.string(),
tool_name: z.string(),
type: z.string(),
})
/**
* Site
*/
export const zSite = z.object({
chat_color_theme: z.string().nullish(),
chat_color_theme_inverted: z.boolean(),
copyright: z.string().nullish(),
custom_disclaimer: z.string().nullish(),
default_language: z.string(),
description: z.string().nullish(),
icon: z.string().nullish(),
icon_background: z.string().nullish(),
icon_type: z.string().nullish(),
icon_url: z.string().nullable(),
privacy_policy: z.string().nullish(),
show_workflow_steps: z.boolean(),
title: z.string(),
use_icon_as_answer_icon: z.boolean(),
})
/**
* Tag
*/
@ -888,35 +917,6 @@ export const zImport = z.object({
status: zImportStatus,
})
/**
* DeletedTool
*/
export const zDeletedTool = z.object({
provider_id: z.string(),
tool_name: z.string(),
type: z.string(),
})
/**
* Site
*/
export const zSite = z.object({
chat_color_theme: z.string().nullish(),
chat_color_theme_inverted: z.boolean(),
copyright: z.string().nullish(),
custom_disclaimer: z.string().nullish(),
default_language: z.string(),
description: z.string().nullish(),
icon: z.string().nullish(),
icon_background: z.string().nullish(),
icon_type: z.string().nullish(),
icon_url: z.string().nullable(),
privacy_policy: z.string().nullish(),
show_workflow_steps: z.boolean(),
title: z.string(),
use_icon_as_answer_icon: z.boolean(),
})
/**
* AgentConfigSnapshotSummaryResponse
*/
@ -2038,30 +2038,6 @@ export const zModelConfig = z.object({
provider: z.string(),
})
/**
* AppDetail
*/
export const zAppDetail = z.object({
access_mode: z.string().nullish(),
app_model_config: zModelConfig.nullish(),
created_at: z.int().nullish(),
created_by: z.string().nullish(),
description: z.string().nullish(),
enable_api: z.boolean(),
enable_site: z.boolean(),
icon: z.string().nullish(),
icon_background: z.string().nullish(),
id: z.string(),
mode_compatible_with_agent: z.string(),
name: z.string(),
tags: z.array(zTag).optional(),
tracing: zJsonValue.nullish(),
updated_at: z.int().nullish(),
updated_by: z.string().nullish(),
use_icon_as_answer_icon: z.boolean().nullish(),
workflow: zWorkflowPartial.nullish(),
})
/**
* AppDetailWithSite
*/
@ -2092,6 +2068,30 @@ export const zAppDetailWithSite = z.object({
workflow: zWorkflowPartial.nullish(),
})
/**
* AppDetail
*/
export const zAppDetail = z.object({
access_mode: z.string().nullish(),
app_model_config: zModelConfig.nullish(),
created_at: z.int().nullish(),
created_by: z.string().nullish(),
description: z.string().nullish(),
enable_api: z.boolean(),
enable_site: z.boolean(),
icon: z.string().nullish(),
icon_background: z.string().nullish(),
id: z.string(),
mode_compatible_with_agent: z.string(),
name: z.string(),
tags: z.array(zTag).optional(),
tracing: zJsonValue.nullish(),
updated_at: z.int().nullish(),
updated_by: z.string().nullish(),
use_icon_as_answer_icon: z.boolean().nullish(),
workflow: zWorkflowPartial.nullish(),
})
/**
* ConversationDetail
*/
@ -3638,7 +3638,7 @@ export const zPostAppsBody = zCreateAppPayload
/**
* App created successfully
*/
export const zPostAppsResponse = zAppDetail
export const zPostAppsResponse = zAppDetailWithSite
export const zPostAppsImportsBody = zAppImportPayload